Easy Ways to Pass and Receive Data with Flutter Stateful and Stateless Widgets or Pages

2021 Novice App Developers Guide

Yashwardhan Deshmukh
The Startup

--

This is the simplest and complete 2021 guide for novice flutter developers who are having trouble with passing/fetching data between their flutter app pages. With the help of this, I’ll also explain a little about writing cleaner code at the end. I personally remember having trouble with this concept as a beginnner and wasted a lot of time reading something i would never comprehend, so im here writing this to make it simpler for others to learn! Note: I wont be covering complex state management libraries like BLoC, Redux etc.

Stateful VS Stateless

Just for revision’s sake,

Stateful widgets are mutable and they update their data every time a setState((){data;}) is called.

Stateless widgets on the other hand are immutable, i.e they contain data that shouldn't change during runtime.

Module 1: Passing Data to another Stateful Widget Class.

You can find my raw files for this as a github gist here → https://git.io/JLdca

Here, we have two files (excluding main.dart): page1.dart and page2.dart . The goal is to pass Color and String datatype values from page 1 to page 2 and display a container and text having the color and the color name that we chose on page1.

So let's start, shall we?

  • page1.dart
Raised Buttons

Here on the first page, in the onPressed callbacks of the two RaisedButtons, we call the Navigator.push (to navigate to the second screen), passing the respective values of the variables, directly to the constructor of the second page. Note that the BuildContext (here context) is the same.

import 'page2.dart';
class PageOne extends StatefulWidget {
@override
_PageOneState createState() => _PageOneState();}
class _PageOneState extends State<PageOne> {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Choose color to be passed to next page'),
RaisedButton( //button 1
color: Colors.pink,
onPressed: () {
Navigator.push(
context,

MaterialPageRoute(
builder: (_) => PageTwo(
passedColor: Colors.pink,
passedColorName: 'Pink',

),),);},

child: Text('Pink')),
RaisedButton( //button 2
color: Colors.blueGrey,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PageTwo(
passedColor: Colors.blueGrey,
passedColorName: 'Blue',

),),);},

child: Text('Blue'),
),],),);}}

In the highlighted code above, we can observe that, depending on what button is pressed, different values (Colors.pink and ‘Pink’ or Colors.blueGrey and ‘Blue’) are given to the variables passedColor (Color datatype) and passedColorName (String datatype),

After these values are passed to page2.dart, the next goal is to consume these values on the second page, for which we will be using key: key, and then either use widget.passedColor / widget.passedColorName (where we will use the raw passed values without modification), or store them in other variables (in this case we can modify the passed values). I’ll explain everything in a bit.

  • page2.dart

To use these values for coloring the container on our second page, we use keys! As Emily from the Google team quoted,

Keys preserve state when widgets move around in your widget tree. In practice, this means they can be useful to preserve the user’s scroll location or keep state when modifying a collection.

Scratching your head mate? Don't worry hahaaha; it’ll all come to you with practice and time, for now, all you have to do is analyze and remember the syntax that I have highlighted in the code and if you want to go into the details, which I strongly recommend you do, read Emily's article on medium or watch her youtube: https://youtu.be/kn0EOS-ZiIc

note: below, i am talking about the two approaches that a begginer would comprehend, without going into a bit complicated stuff like initState.

So, There are two approaches when it comes to consuming the values,

  1. Directly using the passed value

In this case, we just use the passed value from page 1 to page 2 (never change/modify it).

We declare a final property with respective datatypes and add them to the constructor as a parameter. Now the data it’s accessible in the build function!

class PageTwo extends StatefulWidget {final Color passedColor;
final String passedColorName;

const PageTwo(
{Key key, this.passedColor, this.passedColorName})

: super(key: key);
//super is used to call the constructor of the base class which is the StatefulWidget here @override
_PageTwoState createState() => _PageTwoState();
}
class _PageTwoState extends State<PageTwo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page 2'),
automaticallyImplyLeading: false, //optional: removes the default back arrow
),
body: Column(
children: [
Container(
height: 400,
width: double.infinity,
color: widget.passedColor,
),
Text('${widget.passedColorName} color was passed'),
//${} sign here prints the value of variable inside it. RaisedButton(
color: Colors.grey,
onPressed: () {
Navigator.pop(context);
},
child: Text('<- Go back'))
],),);},}

So, having access to the data in the build, we can just set the container’s color to widget.passedColor and it should set the value to the value we chose on page1! YIPPEE, it works!

‘${widget.passedColorName} color was passed’ (assuming user selected the blue button)

But… what if we wanted to modify the color on the second page? For this, we use the second approach

2. Storing the passed value in a different variable before using it

class PageTwo extends StatefulWidget {
final Color passedColor;
final String passedColorName;
const PageTwo({Key key, this.passedColor, this.passedColorName})
: super(key: key);
@override
_PageTwoState createState() => _PageTwoState(
passedColor: this.passedColor, passedColorName: this.passedColorName
);
}

class _PageTwoState extends State<PageTwo> {
Color passedColor;
String passedColorName;
_PageTwoState({this.passedColor, this.passedColorName});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page 2'),
automaticallyImplyLeading: false, //to disable going back
),
body: Column(
children: [
Container(
height: 400,
width: double.infinity,
color: passedColor,
),
Text('$passedColorName color was passed'),
//$ and ${} is the same thing, {} is just used if its not one word
RaisedButton(
color: Colors.grey,
onPressed: () {
Navigator.pop(context);
},
child: Text('<- Go back'))
],),);}}

The additional steps this approach has from approach 1, are the ones highlighted above,

i.e We have to add parameters in _PageTwoState() which passes the values, which then we set to a different value (passedColor and passedColorName in this case), which will now behave as though they were initialized with those passed values

Congratulations on completing the first module! Such a quick learner! Now put on those programmer shades and navigate with me to the next module. Got the pun? HA ha! Ha HA! Ahem, excuse me.

Module 2: Fetching Data from another Stateful Widget Class.

You can find my raw files for this as a github gist here → https://git.io/JLdue

What if we needed to pass data back to page one, for e.g we were making a settings/theme page for a small app. I will show you just that.

note: since flutter by default doesnt persist app data on reload, you will have to use plugins like shared preferences to wrap simple data into platform-specific persistent storage (NSUserDefaults on iOS, SharedPreferences on Android, etc.). For larger databases use sqflite and path/path provider, but thats not important now.

Here we also have two files(excluding main.dart): page1.dart and page2.dart

The goal here is to navigate to page 2, check what boolean value has the user set the Cupertino switches to, and if the value is true, then change the color of the respective container displayed on page 1 to pink.

I used Cupertino toggles instead of buttons (which would have been way easier), just to show the possibilites and also to teach a little about switches, because why not? More the knowledge the merrier it is, right?!

Two containers, which change color to pink if the respect switch is active on the second page
  • page1.dart

Here we will first wrap the Navigator.push inside an async function and await for its result. Then on the second screen, we will pass the boolean value of the switch to the Navigator.pop back to the first screen.

Basically async and await here wait for the function to first finish (do all the business on page2) before returning anything as a value.

import 'package:flutter/material.dart
class PageOne extends StatefulWidget {
@override
_PageOneState createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> {
bool firstSquareColor = false; //since blue by default
bool secondSquareColor = false;
//since blue by default
_navigateNextPageAndRetriveValue(BuildContext context) async {
final List nextPageValues = await Navigator.push(
context,
MaterialPageRoute(builder: (_) => PageTwo()),
);
setState(() {
firstSquareColor = nextPageValues[0];
//first element is stored at the 0th index for a list
secondSquareColor = nextPageValues[1];
});
} //code continues below

This above piece of code, using async and await function, takes the values popped from the page2 screen.

Now to store these values (firstSquareColor and secondSquareColor), we use a list (here called nextPageValues), which stores values as its elements.

@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
height: 150, width: 150,
color: firstSquareColor == false ? Colors.blue : Colors.pink),
Container(
height: 150, width: 150,
color: secondSquareColor == false ? Colors.blue : Colors.pink),
//code continues below

The code above is for the two colored containers which check if the values firstSquareColor and secondSquareColor (which we set as the values we will get from page two later) are true or false, using something called as a ternary operator in flutter,. Ternary operators have the syntax → condition ? trueStatement : falseStatement . Hence they function as a shorter form of an if else statement.

If the value is true, that means the switch on next page has returned true (its toggled on), so we now set the color to pink.

Blue changes to pink if the value returned from the next page is true
        Container(
height: 50, width: 150,
child: RaisedButton(
elevation: 15,
color: Colors.grey[700],
child: Text(
'Next Page ->',
style: TextStyle(color: Colors.black, fontSize:
17),),
onPressed: () {
_navigateNextPageAndRetriveValue(context);
},
),),
],),);}}

This above grey container is the navigator for the next page. We have already made a function _navigateNextPageAndRetrieveValue, which we will now call and pass the same context to it.

RaisedButton that has an onPressed () => _navigateNextPageAndRetrieveValue(context);
  • page2.dart

Now that we have written the code in page1.dart to accept values using async-await and change container color according to the values accepted,

Our next goal is to read the value that the user has given to the Cupertino toggle on page2, which we will store in new boolean variables (called firstValue and secondValue), then Navigator.pop these values as a list to page1.

class PageTwo extends StatefulWidget {
@override
_PageTwoState createState() => _PageTwoState();
}
class _PageTwoState extends State<PageTwo> {
bool firstValue = false;
bool secondValue = false;
//code continues below

NOTE: Unless stored in device persistent storage using plugins like shared preferences, these two values above (firstValue and secondValue) will always default back to ‘false’ on app restart / re-navigation to the page.

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page 2'),
automaticallyImplyLeading: false, //to disable going to page1 when used swipe right ios gesture on the screen, because we want to pop the values, when we navigate back, which we are only doing it here with a RaisedButton
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Flip to change first square color ',
textAlign: TextAlign.center,),
Container(
child: CupertinoSwitch( //first switch
value: firstValue,
onChanged: (bool value) {
setState(
() {
firstValue = value;
},);},),
),],),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Flip to change second square color ',
textAlign: TextAlign.center,
),
Container(
child: CupertinoSwitch( //second switch
value: secondValue,
onChanged: (bool value) {
setState(
() {
secondValue = value;
},);},
),),],),
//code continues below
Cupertino switches (remember to import Cupertino package)

The value is set to true in the first case, so the boolean variable firstValue is also set to true, in the second case the value is defaulted to false, since it seems to be untouched by the user, so secondValue remains set to false.

            Container(
height: 60,
width: 170,
child: RaisedButton(
elevation: 15,
color: Colors.grey[700],
child: Text(
'<- Save temporarily and go to previous Page',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.black,
fontSize: 15),
),
onPressed: () {
Navigator.pop(context, [firstValue, secondValue]
);
//multiple values are passed using a list
},
),),
],),);}}

Finally!, we put these values in Navigator.pop(context, [valueOne, valueTwo]) as shown above, which should come inside an onPressed of a RaisedButton.

RaisedButton that should Navigator.pop the values

SUMMARY TIME!

In page1.dart, we had a function named _navigateNextPageAndRetriveValue (called by an onPressed of a RaisedButton later in the code with passed context), which created a new list called nextPageValues.

This new list awaited for the Navigator.push to pop the result passed as a list in page2.dart. On receiving the list of boolean values, it stored them as its elements, which later are then given to a different boolean variables (firstSquareColor and secondSquareColor) using firstSquareColor = nextPageValues[0]; and secondSquareColor = nextPageValues[1];

Then we used ternary operaters so know the value of these variables and set the color respectively!

Congrats! you completed yet another module, what a legend.

Now that we have completed the harder part, we come to a part that shows how easy it is to pass data between stateless widgets since there is not much need here as the Widget being Stateless, itself wouldn't refresh during runtime. I will also cover how to write clean code and be a better developer in the next and last module.

Module 3: Passing data between Stateless Widgets and making custom widgets to write cleaner code

You can find my raw files for this as a github gist here → https://git.io/JLdHK

The goal here is to make a custom widget that displays a container and the color passed to it, this not only helps us write less code but also makes it easier to debug and understand later on.

Four different colored containers
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
appBar: AppBar(
title: Text('Stateless/Clean Code'),
),
body: StatelessOne(),),);}
}
class StatelessOne extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'Widget color can be customised without \nhaving to retype the entire code'),
StatelessTwo(myColor: Colors.green),
StatelessTwo(myColor: Colors.pink),
StatelessTwo(myColor: Colors.blue),
StatelessTwo(myColor: Colors.orange),
//calling the custom widget which we will make below ],),);}
}

As we can see from the above app screenshot, we need to make four similar containers which only differ in color. Hence, instead of writing the whole container code and giving each of them the same properties like height and width again and again, we come with a cleaner solution.

We make a custom widget! and using my extreme creative skills name it StatelessTwo.

As you can see below, this new custom widget has a final class property of type Color called myColor. Then we have a constructor that will construct the object. Now inside the build, we will put the part that was supposed to be repetitive, container in this case, and assign the myColor variable to the values that are meant to be changed later. Of course, the container color in this case.

You’re all set! , now just call this widget as a child inside your StatelessOne by typing StatelessTwo(myColor: Colors.yourchoice); Also now this custom widget should work just like a container that has fixed height and width but variable color.

class StatelessTwo extends StatelessWidget {
final Color myColor;
StatelessTwo({this.myColor});

@override
Widget build(BuildContext context) {
return Container(
height: 120,
width: 250,
color: myColor,
child: Center(child: Text('Your Repetitive Widget')),
);}
}

Bonus Knowledge! : Positional arguments vs Named Arguments for the constructor:

Positional Arguments: These simply take values in the same order as they are passed in. Syntax:

1)StatelessTwo(this.myColor, this.someOtherValue); → Constructor. 2)StatelessTwo(yourColor, yourSomeOtherValue); → Calling.

Since we have only one value (myColor) that is passed, we can use Positional Arguments, but i have used named, since i personally feel its a good practice.

Named Arguments: Sometimes we have too many arguments and it becomes hard to remember the order, hence in such cases we use named arguments. Here the order in which we pass them doesnt matter. Syntax:

1)StatelessTwo({this.myColor, this.someOtherValue}); → Constructor. 2)StatelessTwo(someOtherValue: yourSomeOtherValue, myColor: yourColor); → Calling. (see we can interchange like this and it wouldnt matter)

To conclude,

With the help of this article, I hope you understood :

  1. About the simple ways in which we could pass and fetch values from widgets, may it be a Stateful or a Stateless one.
  2. How to write cleaner code and divide your app into custom widgets.

Thank you for reading! If u have any doubts/suggestions or found something I wrote incorrectly in this article, you can feel free to put it in the comments or email me: yaashwardhan@gmail.com. I have linked the respective GitHub gist in every module. Just remember,

“Knowledge is Power. Knowledge Shared is Power Multipled”

--

--

Yashwardhan Deshmukh
The Startup

What does it mean to be conscious? Would neural networks ever be conscious? What is the true meaning of life?