Welcome to this blog post about Flutter MobX. Before diving into the core concepts, it's important to understand that Flutter MobX is more than just another state management library. It's a transparent functional reactive programming (TFRP) library that seamlessly unifies and simplifies state management. With MobX, Flutter developers can focus purely on the 'how' of application state and behavior, and less on the 'when' of state updates and visual reactions, making it a very effective library for state management.
To kickstart your journey with Flutter MobX, let's first set up a new Flutter project. Download and install Dart and Flutter SDKs on your machine first. Then, proceed to create a new Flutter project using the command line or your favorite IDE.
1 flutter create mobx_example 2
After creating the project, update the pubspec.yaml file in the project's root directory to include MobX and Flutter MobX dependencies.
1 dependencies: 2 flutter: 3 sdk: flutter 4 mobx: ^2.2.0 5 flutter_mobx: ^2.1.0 6
Remember to fetch the dependencies by running flutter packages get in the terminal.
To get a grip on this state management library, the core concepts we need to understand are Observables, Actions, Computeds, and Reactions.
These entities represent the reactive state in our application. Observables change and trigger reactions when they do. They help us maintain our core state.
Actions are methods in our MobX store that modify the state. They cause changes in the observable state and trigger reactions. For instance, we could have an increment action for a simple counter.
These represent derived state in our applications. They are functions that automatically update when underlying observables change. Computed values are like the 'getter' methods for reactive data in our MobX store.
Most visual reactions to observables will be handled by the Observer widget, a granular observer that reacts to relevant observable changes and re-builds to deliver UI updates.
Here's a peek into how to define the core state in a Dart file:
1 abstract class CounterBase with Store { 2 3 int value = 0; 4 5 6 void increment() { 7 value++; 8 } 9 } 10
In this abstract class CounterBase, we have a simple reactive counter. value is an Observable and increment() is an Action.
The Observer widget in MobX, which is a part of the build method, ensures that whenever any observable it’s tracking observables values changes, it triggers a new build. It's a reaction to changes in the state.
1 class CounterExample extends StatelessWidget { 2 const CounterExample({Key? key}) : super(key: key); 3 4 5 Widget build(BuildContext context) { 6 return Scaffold( 7 appBar: AppBar( 8 title: Text('MobX Counter'), 9 ), 10 body: Center( 11 child: Column( 12 mainAxisAlignment: MainAxisAlignment.center, 13 children: <Widget>[ 14 const Text( 15 'You have pushed the button this many times:', 16 ), 17 Observer( 18 builder: (_) => Text( 19 '${counter.value}', 20 style: Theme.of(context).textTheme.headline4, 21 )), 22 ], 23 ), 24 ), 25 floatingActionButton: FloatingActionButton( 26 onPressed: counter.increment, 27 tooltip: 'Increment', 28 child: const Icon(Icons.add), 29 ), 30 ); 31 } 32 } 33
In the CounterExample StatelessWidget class, we have our Observer wrapped around a text widget, which displays the value of our counter. The builder function produces a widget tree. The Observer detects when observables referenced within its build function change and re-renders the widget tree.
Let’s illustrate how to use Flutter MobX with a simple counter example. We’ll start by creating a new file named counter.dart in the lib folder.
1 import 'package:mobx/mobx.dart'; 2 3 part 'counter.g.dart'; 4 5 class Counter = CounterBase with _$Counter; 6 7 abstract class CounterBase with Store { 8 9 int value = 0; 10 11 12 void increment() { 13 value++; 14 } 15 } 16
In the above code, CounterBase is an abstract class that we're augmenting with _$Counter to get a concrete class.
To generate the necessary MobX code, you'll need to run the build_runner command in the terminal:
1 flutter packages pub run build_runner build 2
Then, instantiate the Counter class in the main.dart file.
1 void main() { 2 final counter = Counter(); 3 runApp(MyApp(counter: counter)); 4 } 5
Within the application, the counter can be accessed via the widget's build BuildContext context and used to update the user interface. The Observer widget automatically tracks observables and rebuilds upon changes. Below is a practical counter example to illustrate the concept:
1 class MyApp extends StatelessWidget { 2 const MyApp({Key? key, required this.counter}) : super(key: key); 3 4 final Counter counter; 5 6 7 Widget build(BuildContext context) { 8 return MaterialApp( 9 title: 'Flutter Demo', 10 theme: ThemeData( 11 primarySwatch: Colors.blue, 12 visualDensity: VisualDensity.adaptivePlatformDensity, 13 ), 14 home: MyHomePage(counter: counter), 15 ); 16 } 17 } 18 19 class MyHomePage extends StatelessWidget { 20 const MyHomePage({ 21 Key? key, 22 required this.counter, 23 }) : super(key: key); 24 25 final Counter counter; 26 27 28 Widget build(BuildContext context) { 29 return Scaffold( 30 appBar: AppBar( 31 title: const Text('MobX Counter'), 32 ), 33 body: Center( 34 child: Column( 35 mainAxisAlignment: MainAxisAlignment.center, 36 children: <Widget>[ 37 const Text( 38 'You have pushed the button this many times:', 39 ), 40 Observer( 41 builder: (_) => Text( 42 '${counter.value}', 43 style: Theme.of(context).textTheme.headline4, 44 )), 45 ], 46 ), 47 ), 48 floatingActionButton: FloatingActionButton( 49 onPressed: counter.increment, 50 tooltip: 'Increment', 51 child: const Icon(Icons.add), 52 ), 53 ); 54 } 55 } 56
Here, we use the Observer widget in the widget build(BuildContext context) function, tracking the value Observable, and we ensure the widget rebuilds every time the value changes.
In MobX, Observers are an incredibly powerful feature. They allow components to automatically re-render upon changes in the state they depend on, even if only a small part of the state has changed. This makes state management not just simple but also efficient. Let's see how the Observer widget fits into our counter example:
1 Observer( 2 builder: (_) => Text( 3 '${counter.value}', 4 style: Theme.of(context).textTheme.headline4, 5 ), 6 ), 7
Here, we have wrapped the Text widget with an Observer. Every time our Observable counter.value changes, the Observer ensures that the anonymous function in its builder (our builder function) is called, thus reflecting the change on the UI.
How does MobX deal with complex states? MobX shines when it comes to managing large and complex state trees. Its philosophy of Transparent Functional Reactive Programming (TFRP) guarantees computeds, reactions, and observers derived from tracking the state are automatically kept consistent.
Consider a contact management application. Each contact entity could be represented as a MobX Store with different observables for the contact's attributes. Combining firstname and lastname form an Observable for the contact's full name, for instance.
1 abstract class ContactBase with Store { 2 3 String firstname = ''; 4 5 6 String lastname = ''; 7 8 9 String get fullName => '$firstname $lastname'; 10 } 11
Here, fullName is a computed Observable that automatically updates when either firstname or lastname changes.
We can test MobX managed states effectively, similar to how we can test other derived states in our Flutter project. Let's take a look at an example of testing our counter increment action.
1 void main() { 2 group('Counter Store Test', () { 3 late Counter counter; 4 setUp(() { 5 counter = Counter(); 6 }); 7 8 test('Initial Counter Value Test', () { 9 expect(counter.value, 0); 10 }); 11 12 test('Counter Increment Test', () { 13 counter.increment(); 14 expect(counter.value, 1); 15 }); 16 }); 17 } 18
In the above code, we instantiated our Counter store and tested for the initial value. We also tested the increment function, validating the updated counter value.
While working with MobX, using actions for every task that modifies the state is a good practice. It ensures consistency and order in state changes, making the app more predictable and easier to debug. A common mistake to avoid is updating observables directly outside of actions. This bypasses action handling and might lead to inconsistency in state updates.
MobX simplifies state management, elegantly unifies state and UI, and most importantly, enhances productivity. The best way to truly appreciate and effectively use this library is through experience. Develop more using Flutter MobX, experiment with different use cases, and evolve your grasp of reactive programming paradigms.
With MobX, you are only ever a few steps away from crafting stunning Flutter apps with immaculate state management! Here's to a fruitful Flutter MobX experience!
The Dart language aims to be clear and concise, not just for the machine, but also for the developer: "Do not make the machine's problem, the developer's problem". Such a principle aligns perfectly with the ethos of MobX.
Remember, the strength of MobX lies in its simplicity and ease of use. With this newfound knowledge, I'm confident that you'll be able to utilize this tool to efficiently manage the state in your Flutter applications! Happy flutteringgg! 💙
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.