Introduction to State Management in Flutter

State Management in Flutter

In this blog, we will get familiar with state management in flutter. We would cover the basics of state objects along with how to manage them using the most used provider package.

Introduction
State Management with Provider
Starting Screen
Provider Class
ChangeNotifierProvider
Consumer

Introduction

From pressing a button to making a change to display data, everything we do in Flutter deals with State Management. Therefore, managing this data in the most appropriate way saves us from writing a lot of boilerplate code.

Talking about state management solutions, there are a lot of them which have been developed over the years and are based on the same concept of modifying the state in the cleanest way. But for this blog, we will be talking about providers as it is recommended by Google as well.

What is state object in Flutter?

State in Flutter refers to the data stored inside a widget that can be modified depending on the operation. When the state of your app changes (for example, you switch to dark mode from light mode), you change the state which in turn redraws the user interface.

State Management with Provider

Provider mainly provides a central point to manage the state and write the logic. In Provider, widgets listen to changes in the state and update as soon as they are notified. Therefore, instead of the entire widget tree rebuilding when there is a state change, only the affected widget is changed. This helps in the application memory management by reducing the amount of work which makes it run faster and smoother.

Let's move on to the core concepts of provider and then take a simple practical example to understand it better. Shall we?

Three core concepts:

  1. ChangeNotifier: This stores the state and then informs the widgets consuming it.
  2. ChangeNotifierProvider: This widget basically makes the ChangeNotifier accessible to the underlying widgets in the tree.
  3. Consumer: A widget that listens to state changes and updates the UI accordingly
Showtime

Now let’s get started with a small example. The first thing to do is to add the provider dependency block in your pubspec.yaml file:

dependencies:
provider: ^6.0.2

Just for implementation purposes, I will be writing all the code in the main.dart file but you could always separate the provider class in another file.

This is the starter code to our application which just asks the user for two inputs which are Name and GR No and stores them in their respective TextEditingController and assigns it to the text field controller.

Starting Screen

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: HomeScreen(),
);
}
}

class HomeScreen extends StatelessWidget {
TextEditingController nameController = TextEditingController();
TextEditingController grController = TextEditingController();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('DSC VIT Pune'),
),
body: Center(
child: Column(
children: [
Form(
child: Consumer<UserDetailsProvider>(
builder: (context, provider, child) {
return Column(
children: [
TextFormField(
controller: nameController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Enter Name'),
),
SizedBox(
height: 10,
),
TextFormField(
controller: grController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Enter GR no.'),
),
SizedBox(
height: 15,
),
FlatButton(
child: Text('Submit'),
color: Colors.blue,
)
],
);
}),
),
SizedBox(
height: 20,
),
],
),
),
);
},
}

Our goal is that when we press the submit button, we want the Text widgets to show “Hi (Name from textfield)” and “Your GR number is (GR number from text field)” below the submit button.

Provider Class

Now, we will create a new class called UserDetailsProvider. This class will declare all the methods dealing with handling the state.

This class extends the ChangeNotifier class; ChangeNotifier provides access to the notifyListeners method which we will use to notify the listening widgets to rebuild when the state changes

class UserDetailsProvider extends ChangeNotifier {
int _gr = 0;
String _userName = '';
int get userGr => _gr;
String get userName => _userName;
void updateGr(int gr) {
_gr = gr;
notifyListeners();
}

void updateName(String name) {
_userName = name;
notifyListeners();
}
}

Here, we have two methods that take name and gr as the parameter. After the name and gr number are updated, we call the notifyListeners method, which informs the listening widgets about a change in the state and triggers a rebuild of relevant widgets

ChangeNotifierProvider

Now that we have the provider class ready, we need to link this class to the screen by using ChangeNotifierProvider. For this, we wrap the entire HomeScreen with this.

This provides the instance of ChangeNotifier to the screen and thus we can access the state data of the ChangeNotifier in the screen for which, the code is given below

Consumer

It's not necessary that the entire HomeScreen UI will be using state data from the ProviderClass, and thus they don't need to be rebuilt. So the Consumer widget allows observing the state changes from ChangeNotifier in a particular part of the UI and only that observing part of the UI will get re-rendered.

So we wrap the Column which contains the two text fields and the button with Consumer so it has access to the provider class.

Next, for the onPressed function of the submit button, we assign the name and gr number converted to string to their respective variables and then use the provider to call updateGr and updateName passing these two values as the parameters.

class HomeScreen extends StatelessWidget {
TextEditingController nameController = TextEditingController();
TextEditingController grController = TextEditingController();

@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<UserDetailsProvider>(
create: (context) => UserDetailsProvider(),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(
title: Text('DSC VIT Pune'),
),
body: Center(
child: Column(
children: [
Form(
child: Consumer<UserDetailsProvider>(
builder: (context, provider, child) {
return Column(
children: [
TextFormField(
controller: nameController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Enter Name'),
),
SizedBox(
height: 10,
),
TextFormField(
controller: grController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Enter GR NO'),
),
SizedBox(
height: 15,
),
FlatButton(
onPressed: () {
final String name = nameController.text.trim();
final int gr = int.parse(grController.text.trim());
provider.updateGr(gr);
provider.updateName(name);

},
child: Text('Submit'),
color: Colors.blue,
)
],
);
}),
),
SizedBox(
height: 20,
),
],
),
),
);
},
));
}

Finally, let's get this working.

Now once I click submit, I want the name and number to show below the Submit button as I mentioned before. For this, We just need to add two text widgets that Consume the details from the state class and show them below. Let's see how that works:

Consumer<UserDetailsProvider>(
builder: (context, provider, child) {
return Column(
children: [
Text(
'Hi ' + provider.userName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
'Your GR Number is' +
provider.userGr.toString() +

style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w400,
),
),
],
);
},
),

Awesome! That’s working exactly as we wanted it to. This concludes most of the common things required to use the provider package in your next application. Hope this helped you to brush up on your basics about state management!

--

--