Flutter State Management Approaches with Examples(setState, BLoC / Rx, Provider, Riverpod).

Geno Tech
App Dev Community
Published in
6 min readApr 18, 2021

--

Flutter Knowledge sharing #16

State Management is one of the most popular topics which Flutters developers talking about. Therefore we need to understand what is it and what are the ways to approach it. There have few types of approaches to achieve this concept. What are they? and how they use?. Let’s discuss this story. Learning about state management is obvious in Flutter learning. You can choose it base on the architecture of your project. Because every usage of these approaches do not only depend on how it’s easy or hard to use. If you want to get a broad knowledge about these all techniques, You better follow the topics one by one. Here I am briefing all together.

What is a State?

People have many different answers to this question. But the most precious answer I have learnt is, The state is a behaviour of the App at a given moment.

As an example, think about an application login state, after the user logs in to the application, the application content change as to the logged user rather than a guest/new user. In the same way at every time like, when we click a button, change a text, change a list etc, we have to change the app state.

Flutter is a UI toolkit in which flutter is mainly build on widgets. the widget has two types mainly. They are statefulwidget and stateless widget. With these statelesswidgets, we cannot change any content of it. But in statefulwidget we can change it contains dynamically. Let’s see the example below. here I get a statefulwidget and changes the content of that using setState() method.

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
String msg = 'Some Text Display Here';
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter - setState'),
),
body: Container(
child: Center(
child: Text(msg,
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed:(){
setState(() {
// Here we change the text state.
msg = "We changed that Text";
});
},
),
),
);
}
}

Then we will see other various state management approaches. We can discuss here few types of them.

  1. provider
  2. GetX
  3. BLoC/Rx
  4. Riverpod

Provider State Management Approach

Here I provide a simple example of how to work with the provider library. This is actually a simple State Management approach we can see. Provider pattern used inside the BLoC state management. By design, Provider is an improvement over InheritedWidget and as such, it depends on the widget tree. Let’s see how we use this in development. First, you need to import the provider package under dependencies.

dependencies:
flutter:
sdk: flutter
provider: ^4.1.2

Then need to create the Counter.dart file. The Counter file store the current state of app context.

import 'package:flutter/material.dart';

class Counter extends ChangeNotifier {
var _count = 0;
int get getCounter {
return _count;
}

void incrementCounter() {
_count += 1;
notifyListeners();
}
}

Then need to do changes in main.dart file as follows. Here the MultiProvider wrapped the Material App widgets and different providers at a single level.

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

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Counter(),
),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: "AndroidVille Provider Pattern"),
),
);
}
}

class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({this.title});
void _incrementCounter(BuildContext context) {
Provider.of<Counter>(context, listen: false).incrementCounter();
}

@override
Widget build(BuildContext context) {
var counter = Provider.of<Counter>(context).getCounter;
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _incrementCounter(context),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

GetX State Management Approach

GetX is another fast-growing state management technique. Therefore I used it in my latest projects. GetX is more simple than Provider. But you want to make your own opinion and select one of the best. Therefore I can’t give the best solution for you exactly. However, I recommend that the GetX is more efficient and user friendly.

GetX has 3 basic principles. This means that these are the priority for all resources in the library: PRODUCTIVITY, PERFORMANCE AND ORGANIZATION.

You can see the following example. It contains a floating action button to count and a button to route to another page.

First, get the dependency.

dependencies:
flutter:
sdk: flutter
get:

Create a class named Controller.

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

class Controller extends GetxController{
var count = 0.obs;
increment() => count++;
}

Then the main.dart file as follows.

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'Controller.dart';

void main() {
runApp(GetMaterialApp(home: Home()));
}

class Home extends StatelessWidget {

@override
Widget build(context) {

// Instantiate your class using Get.put() to make it available for all "child" routes there.
final Controller c = Get.put(Controller());

return Scaffold(
// Use Obx(()=> to update Text() whenever count is changed.
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),

// Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context
body: Center(child: ElevatedButton(
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}

class Other extends StatelessWidget {
// You can ask Get to find a Controller that is being used by another page and redirect you to it.
final Controller c = Get.find();

@override
Widget build(context){
// Access the updated count variable
return Scaffold(body: Center(child: Text("${c.count}")));
}
}

BLoC/Rx State Management Approach

Let us see, how to perform implementations with BLoC. While someone says, Other state management packages are easy, someone says BLoC is helping to make a perfect architecture for your project. But BLoC is more difficult to learn. BloC does involve a lot of boilerplate code but from a code readability and maintainable perspective, I think BLoC is a very good selection(and you can use extensions simply to auto-generate the boilerplate code for you). I find BLoC to be more helpful and less challenging to change events.

The following Example shows the increment and decrement buttons how work using the BLoC package. First, you need to import libraries.

dependencies:
flutter:
sdk: flutter
flutter_bloc:

Then we need to create two Files (counter_event.dart and counter_bloc.dart). Inside the counter_event.dart, we mentioned the events which we want to perform. Then we put those events into BLoC pattern when we want.

abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

class DecrementEvent extends CounterEvent {}

Then the counter_bloc.dart file gives us the output through the sink and get the input from the stream as follows. Here I give you an example to get you a brief idea about coding with different methods.

import 'dart:async';

import 'counter_event.dart';


class CounterBloc {
int _counter = 0;

final _counterStateController = StreamController<int>();
StreamSink<int> get _inCounter => _counterStateController.sink;
// For state, exposing only a stream which outputs data
Stream<int> get counter => _counterStateController.stream;

final _counterEventController = StreamController<CounterEvent>();
// For events, exposing only a sink which is an input
Sink<CounterEvent> get counterEventSink => _counterEventController.sink;

CounterBloc() {
// Whenever there is a new event, we want to map it to a new state
_counterEventController.stream.listen(_mapEventToState);
}

void _mapEventToState(CounterEvent event) {
if (event is IncrementEvent)
_counter++;
else
_counter--;

_inCounter.add(_counter);
}

void dispose() {
_counterStateController.close();
_counterEventController.close();
}
}

Riverpod State Management Approach

Riverpod is the same as the Provider. You can practise it very quickly. Provider has completely extended by Riverpod. Therefore It is better to use this in your new project rather than Provider. I am not too much familiar with Riverpod. We will discuss more this in future in details. I will put a link to follow Riverpod more and more.

Conclusion

Here I tried to give you a brief idea about a few and popular state management packages in Flutter. I hope you got some knowledge to use in your next Flutter project. Please feel free to ask any question you will face in the response section below.
Happy Coding !!!!
Found this post useful? Kindly tap the 👏 button below! :)

--

--

Geno Tech
App Dev Community

Software Development | Data Science | AI — We write rich & meaningful content on development, technology, digital transformation & life lessons.