Follow

Follow
State Management in Flutter & their Libraries- Why you need to know as Flutter Developer

State Management in Flutter & their Libraries- Why you need to know as Flutter Developer

What is State Management & Why you need to know and Learn as Flutter Developer - Concepts of Packages in Flutter

Kamran Mansoor's photo
Kamran Mansoor
Dec 15, 2022

18 min read

Play this article

Table of contents

Flutter

Flutter is a mobile app development framework created by Google. It uses the Dart programming language and allows for the creation of natively compiled apps for mobile, web, and desktop from a single codebase. It provides a rich set of customizable widgets that can be used to build beautiful and responsive user interfaces. Flutter is known for its fast development cycle, fast performance, and attractive, customizable user interface.

What is State Management & Why you need to know

State management refers to the process of managing and organizing the data and state of an application. This is an important concept in software development, as it allows developers to effectively manage the changing state of an application and ensure that it behaves as expected.

There are many different approaches to state management, and the right solution will depend on the specific needs of your application. Some common techniques include:

  • Storing a state in a central location, such as a database or store, and accessing it through a global object or state management library.

  • Using a declarative approach, such as with a reactive programming library, automatically update the state of an application based on user interactions or data changes.

  • Employing functional programming techniques, such as immutability and pure functions, to manage the state in a predictable and deterministic way.

Overall, state management is an important part of creating well-designed, maintainable, and scalable applications. By carefully managing the state of your application, you can make it easier to understand, debug, and extend, and help ensure that it continues to work reliably and efficiently.

State Management is Flutter

In Flutter, state management is the process of managing and organizing the data and state of an application. This can be accomplished in various ways, depending on the specific needs of the application.

Flutter provides a few different options for managing the state, each with its own trade-offs and benefits.

There are many different approaches to state management in Flutter, and the right solution will depend on the specific needs of your application. By carefully managing the state of your application, you can create more maintainable, scalable, and efficient code.

Why do we need State Management in Flutter?

State management is an important aspect of app development, particularly in apps with complex and dynamic user interfaces. In Flutter, state management is the process of managing the state of the widgets in your app so that the user interface can be updated dynamically as the state of the app changes. This is important because the user interface of an app is typically built using widgets, which are essentially reusable building blocks for the user interface. By managing the state of these widgets, you can ensure that the user interface is always up-to-date and reflects the current state of the app.

There are several reasons why state management is important in Flutter:

  1. It allows you to build more complex and dynamic user interfaces. By managing the state of your widgets, you can easily update the user interface in response to user actions or data changes.

  2. It makes your code more organized and easier to maintain. By centralizing the management of the state of your widgets, you can avoid having to update the state of each widget individually, which can make your code more difficult to understand and maintain.

  3. It can improve the performance of your app. State management techniques can help you avoid rebuilding the entire user interface when only a small part of it needs to be updated. This can help to reduce the amount of time it takes for your app to render the user interface and improve the overall performance of your app.

Overall, state management is an essential part of app development in Flutter, and it can help you to build more dynamic and efficient user interfaces for your apps.

Example of State Management

Here is an example of state management in Flutter using the ScopedModel package:

import 'package:scoped_model/scoped_model.dart';

class CounterModel extends Model {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopedModel<CounterModel>(
      model: CounterModel(),
      child: Scaffold(
        appBar: AppBar(title: Text('Counter')),
        body: ScopedModelDescendant<CounterModel>(
          builder: (context, child, model) {
            return Center(
              child: Text('${model.count}'),
            );
          },
        ),
        floatingActionButton: ScopedModelDescendant<CounterModel>(
          builder: (context, child, model) {
            return FloatingActionButton(
              onPressed: model.increment,
              child: Icon(Icons.add),
            );
          },
        ),
      ),
    );
  }
}

In this example, we define a CounterModel class that extends Model from the scoped_model package. The CounterModel class has a _count property that tracks the current count and a increment the method that increases the count by one.

We then use the ScopedModel widget to provide the CounterModel to the widgets in our CounterPage. The ScopedModelDescendant widget is used to access the model from within a widget, and we use it to update the Text widget with the current count and to call the increment method when the FloatingActionButton is pressed.

This example shows how the ScopedModel the package can be used to manage the state of a simple counter application in Flutter. And don't worry about ScopedModelDescendant as I'm going to explain this thing more and you will get to know about flutter packages.

SetState()

In Flutter, setState is a method that allows a developer to indicate the internal state of a StatefulWidget has changed and needs to be "rebuilt" or "redrawn" on the screen.

When a widget's state changes, the framework "marks" the widget as "dirty" and schedules a rebuild of the widget tree. This rebuilding process is what causes the widget to be redrawn on the screen with the new state.

The setState method is used to indicate to the framework that the internal state of a StatefulWidget has changed and that the widget tree needs to be rebuilt. This method takes a callback function that returns the new, updated state for the widget.

For example, consider the following CounterWidget class:

class CounterWidget extends StatefulWidget {
  @override
  _CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text('Counter: $_counter'),
        RaisedButton(
          onPressed: _incrementCounter,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

In this example, the _CounterWidgetState class has a _counter property that tracks the current value of the counter. The _incrementCounter method is called when the RaisedButton is pressed, and it uses setState to increment the _counter property by one.

When setState is called, the framework marks the CounterWidget as dirty and schedules a rebuild of the widget tree. This causes the CounterWidget to be redrawn on the screen with the updated _counter value.

Overall, setState is an important method for managing the state of a StatefulWidget in Flutter. It allows a developer to indicate that the internal state of a widget has changed and that the widget tree needs to be rebuilt to reflect the new state.

Inherited Widget and Inherited Model

In Flutter, InheritedWidget and InheritedModel are two classes that can be used to share data and state between widgets in the widget tree.

InheritedWidget is a special type of widget that can be used to provide data to its descendant widgets. It does this by "inheriting" the data down the widget tree, making it available to any descendant widget that wants to access it. InheritedWidget is a useful tool for sharing data between widgets that are not necessarily in the same branch of the widget tree.

InheritedModel is a subclass of InheritedWidget that extends the basic functionality of InheritedWidget by allowing different types of data to be inherited down the widget tree. This can be useful when different parts of an application require different types of data, as it allows each part of the application to access only the data that it needs.

Here is an example of using InheritedWidget and InheritedModel in Flutter:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return InheritedModel<MyModel>(
      model: MyModel(),
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyModel extends InheritedModel<MyModel> {
  final int count;

  MyModel({this.count = 0});

  @override
  bool updateShouldNotify(MyModel oldWidget) {
    return count != oldWidget.count;
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('My App')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('The count is ${MyModel.of(context).count}'),
            MyCounterButton(),
          ],
        ),
      ),
    );
  }
}

class MyCounterButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        MyModel.update(context, (model) => model.count++);
      },
      child: Text('Increment'),
    );
  }
}

In this example, we define a MyModel class that extends InheritedModel. The MyModel class has a count property that tracks the current value of a counter.

We then use the InheritedModel widget to provide the MyModel to the widgets in the widget tree. The MyHomePage widget uses the MyModel.of static method to access MyModel and display the current count. The MyCounterButton widget uses the MyModel.update static method to increment the count property by one.

This example shows how InheritedWidget and InheritedModel can be used to share data and state(s) between widgets in a Flutter application.

Provider

In Flutter, Provider is a package that provides an easy and convenient way to manage the state of an application. It uses a reactive approach to state management, allowing developers to create "providers" that hold and manage the state of an application, and "consumers" that can access and use that state from within a widget.

Provider is built on top of the InheritedWidget class, which allows the state managed by a provider to be inherited down the widget tree and accessed by descendant widgets. This allows the state of an application to be managed in a central, predictable, and declarative way.

Here is an example of using Provider in a Flutter application:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyModel(),
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyModel extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('My App')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('The count is ${context.watch<MyModel>().count}'),
            MyCounterButton(),
          ],
        ),
      ),
    );
  }
}

class MyCounterButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        context.read<MyModel>().increment();
      },
      child: Text('Increment'),
    );
  }
}

In this example, we define a MyModel class that extends ChangeNotifier from the provider package. The MyModel class has a count property that tracks the current value of a counter, and an increment method that increments the count property by one.

We then use the ChangeNotifierProvider widget to provide the MyModel to the widgets in the widget tree. The MyHomePage widget uses the context.watch method to access the MyModel and display the current count. The MyCounterButton widget uses the context.read method to call the increment method on the MyModel.

This example shows how Provider can be used to manage the state of a Flutter application in a simple and convenient way. By using Provider, developers can create providers that hold and manage the state of an application, and consumers that can access and use that state from within a widget.

Riverpod

Riverpod is a state management library for Flutter that provides a simple and powerful way to manage the state of an application. It is built on top of the Provider package, which provides a reactive approach to state management, and adds additional features and functionality on top of Provider.

One of the key features of Riverpod is its use of "providers" to manage the state of an application. A provider is an object that holds and manages the state of an application and can be accessed and used by descendant widgets in the widget tree.

Riverpod provides a number of different types of providers, each with its own specific use cases and benefits. For example, the StateProvider class can be used to manage the state of a StatefulWidget, the FutureProvider class can be used to manage the result of a Future object, and the ValueProvider class can be used to manage a simple value or object.

Here is an example of using Riverpod in a Flutter application:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final count = useProvider(countProvider);

    return Scaffold(
      appBar: AppBar(title: Text('My App')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('The count is $count'),
            MyCounterButton(),
          ],
        ),
      ),
    );
  }
}

class MyCounterButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final model = useProvider(myModelProvider);

    return RaisedButton(
      onPressed: () {
        model.increment();
      },
      child: Text('Increment'),
    );
  }
}

final countProvider = StateProvider((_) => 0);

final myModelProvider = Provider((ref) {
  return MyModel(
    increment: () {
      ref.read(countProvider).state++;
    },
  );
});

class MyModel {
  final Function increment;

  MyModel({this.increment});
}

In this example, we define a MyModel class that has an increment a method that increments the current value of a counter. We then use the Provider widget to create a myModelProvider that holds and manages an instance of the MyModel class.

We also define a countProvider that uses the StateProvider class to manage the current value of the counter. The MyHomePage widget uses the useProvider hook to access the countProvider and display the current count. The MyCounterButton widget uses the useProvider hook to access the myModelProvider and call the increment method on the MyModel instance.

This example shows how Riverpod can be used to manage the state of a Flutter application in a simple and powerful way. By using providers, developers can create objects that hold and manage the state of an application.

BLoC

The BLoC (Business Logic Components) pattern is a popular architectural pattern for managing state in Flutter applications. It uses reactive programming to manage state in a central, predictable, and declarative way.

In the BLoC pattern, the business logic of an application is separated from the user interface, allowing the user interface to be updated based on the state of the application. This separation of concerns makes it easier to test and maintain the application.

The flutter_bloc package is a popular package for implementing the BLoC pattern in a Flutter application. It provides a convenient way to create BLoCs (Business Logic Components), which are objects that manage and control the state of an application.

Here is an example of using the flutter_bloc package to implement the BLoC pattern in a Flutter application:

First, define an enum for the different events that the bloc can receive:

Copy codeenum CounterEvent { increment, decrement }

Next, define a class for the state of the app. This class will have a counter field that represents the current value of the counter:

Copy codeclass CounterState {
  final int counter;

  CounterState(this.counter);
}

Then, create a bloc class that extends the Bloc base class provided by the flutter_bloc package. This class will define the mapEventToState method, which takes an event and maps it to a new state:

Copy codeclass CounterBloc extends Bloc<CounterEvent, CounterState> {
  @override
  CounterState get initialState => CounterState(0);

  @override
  Stream<CounterState> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.increment:
        yield CounterState(state.counter + 1);
        break;
      case CounterEvent.decrement:
        yield CounterState(state.counter - 1);
        break;
    }
  }
}

Finally, in your Flutter app, use the Provider package to make the bloc available to all the widgets that need it. Then, use the BlocBuilder widget provided by the flutter_bloc package to build the user interface and to respond to events emitted by the user:

Copy codeclass MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<CounterBloc>(
      create: (context) => CounterBloc(),
      child: MaterialApp(
        home: Scaffold(
          body: BlocBuilder<CounterBloc, CounterState>(
            builder: (context, state) {
              return Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text(
                      '${state.counter}',
                      style: TextStyle(fontSize: 24.0),
                    ),
                    RaisedButton(
                      onPressed: () {
                        context.bloc<CounterBloc>().add(CounterEvent.increment);
                      },
                      child: Text('Increment'),
                    ),
                    RaisedButton(
                      onPressed: () {
                        context.bloc<CounterBloc>().add(CounterEvent.decrement);
                      },
                      child: Text('Decrement'),
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

In this example, the CounterBloc maintains the current value of the counter in response to increment and decrement events. The user interface is built by the BlocBuilder widget, which automatically rebuilds itself when the bloc emits a new state. This allows the user interface to stay up-to-date with the latest state of the app.

GetIt

GetIt is a dependency injection (DI) solution for Flutter. It provides a simple and convenient way to access and manage the dependencies (such as services or repositories) of an app.

To use GetIt, you first need to import the get_it package in your pubspec.yaml file and run flutter packages get to install it. Then, in your app code, create an instance of the GetIt class and use its registerSingleton method to register the dependencies that you want to inject. This method takes Type and an instance of the corresponding dependency as an argument.

Let us discuss example on GetIt:

Copy codefinal getIt = GetIt.instance;

void setup() {
  // Register a service as a singleton
  getIt.registerSingleton(MyService());
}

To access a registered dependency, you can use the GetIt.instance.get() method, which takes the Type of the dependency as an argument. This method will return the instance of the dependency that you registered earlier. For example:

Copy codefinal service = GetIt.instance.get<MyService>();

Alternatively, you can use the lazySingleton getter to get a Lazy instance of the dependency. This instance can be used to access the dependency in a more convenient way. For example:

Copy codefinal service = GetIt.instance.lazySingleton<MyService>();

With GetIt, you can easily manage and access the dependencies of your app in a centralized and maintainable way. This can make it easier to write testable and modular code in Flutter.

MobX

MobX is a state management solution for Flutter that uses the Reactive Programming approach. This approach allows you to define the way your app's state changes in response to user actions, system events, or any other form of input.

To use MobX, you first need to import the mobx and flutter_mobx packages in your pubspec.yaml file and run flutter packages get to install them. Then, you can create a Store class that represents the state of your app. This class can be annotated with @store to make it observable by the flutter_mobx package. For example:

Copy codeimport 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = _Counter with _$Counter;

abstract class _Counter with Store {
  @observable
  int value = 0;

  @action
  void increment() {
    value++;
  }

  @action
  void decrement() {
    value--;
  }
}

In this example, the Counter class has an @observable value field that represents the current value of the counter. It also has two @action methods, increment and decrement, which updates the value in response to user actions.

To use this Store in your Flutter app, you can use the Observer widget provided by the flutter_mobx package. This widget will automatically rebuild itself when the Store emits a new state. For example:

Copy codeclass MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Counter();

    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                '${counter.value}',
                style: TextStyle(fontSize: 24.0),
              ),
              RaisedButton(
                onPressed: counter.increment,
                child: Text('Increment'),
              ),
              RaisedButton(
                onPressed: counter.decrement,
                child: Text('Decrement'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

In this example, the Observer widget automatically rebuilds itself when the counter Store emits a new state in response to the increment and decrement actions. This allows the user interface to stay up-to-date with the latest state of the app.

Overall, MobX provides a simple and powerful way to manage the state of your Flutter app using the Reactive Programming approach. It can help you write modular, testable, and maintainable code in Flutter.

GetX

GetX is a state management solution for Flutter that uses the Reactive Programming approach. It provides a simple and powerful way to manage the state of your app and to build reactive user interfaces.

To use GetX, you first need to import the get and flutter_getx packages in your pubspec.yaml file and run flutter packages get to install them. Then, you can create a Controller class that represents the state of your app. This class can be annotated with @singleton to make it a singleton, which means that there will be only one instance of the Controller in your app. For example:

Copy codeimport 'package:get/get.dart';

part 'counter.g.dart';

class CounterController = _CounterController with _$CounterController;

abstract class _CounterController with GetxController {
  @singleton
  final value = 0.obs;

  @action
  void increment() {
    value.value++;
  }

  @action
  void decrement() {
    value.value--;
  }
}

In this example, the CounterController class has a final value = 0.obs field that represents the current value of the counter. It also has two @action methods, increment and decrement, that update the value in response to user actions. The obs suffix indicates that the value is an Observable, which means that it can be observed by the flutter_getx package.

To use this Controller in your Flutter app, you can use the GetX widget provided by the flutter_getx package. This widget will automatically rebuild itself when the Controller emits a new state. For example:

Copy codeclass MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              GetX<CounterController>(
                builder: (counter) {
                  return Text(
                    '${counter.value.value}',
                    style: TextStyle(fontSize: 24.0),
                  );
                },
              ),
              RaisedButton(
                onPressed: Get.find<CounterController>().increment,
                child: Text('Increment'),
              ),
              RaisedButton(
                onPressed: Get.find<CounterController>().decrement,
                child: Text('Decrement'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

In this example, the GetX widget automatically rebuilds itself when the CounterController Controller emits a new state in response to the increment and decrement actions. This allows the user interface to stay up-to-date with the latest state of the app.

Overall, GetX provides a simple and powerful way to manage the state of your Flutter app using the Reactive Programming approach. It can help you write modular, testable, and maintainable code in Flutter.

Conclusion

To conclude, state management is an important aspect of app development in Flutter, and it is a crucial tool for building complex and dynamic user interfaces.

  • By managing the state of your widgets, you can ensure that your app's user interface is always up-to-date and reflects the current state of the app.

  • This can help to make your code more organized and easier to maintain.

  • Also, it can also improve the performance of your app.

Overall, state management is an essential part of app development in Flutter, and it is an important skill for any Flutter developer to learn.

Quote of the Day

People don't care about what you say, they care about what you build. - Mark Zuckerberg

Motivation for Flutter Developer

"Flutter is a game-changing technology that allows you to create beautiful, fast, and expressive apps for both iOS and Android with a single codebase. It is an exciting time to be a Flutter developer and I can't wait to see what the future holds for this amazing technology." - Anonymous Flutter developer.

Share this