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
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:
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.
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.
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.