Observers
Observer Widget​
Observer({required Widget Function(BuildContext context) builder})
​
One of the most visual reactions in the app is the UI. The Observer widget (which is part of the
package), provides a granular observer of the observables used in itsbuilder
function. Whenever
these observables change, Observer
rebuilds and renders.Immediate context for
builder
​The key thing to note is that the
builder
function will only track the observables in its immediate execution context. If an observable is being used in a nested function, it will not be tracked. Make sure to dereference (a.k.a. read) an observable in the immediate execution context. If no observables are found when running thebuilder
function, it will warn you on the console.This is one of the most common gotchas when using MobX. Just because you have nested functions inside a
builder
, which are reading an observable, it does not actually track the observable. It can appear deceiving but the fact is, the observable was not in the immediate execution context. Be watchful for this scenario, especially when yourObserver
-wrappedWidgets
are not updating properly.
Below is the Counter example in its entirety.
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
part 'counter.g.dart';
class Counter = CounterBase with _$Counter;
abstract class CounterBase with Store {
int value = 0;
void increment() {
value++;
}
}
class CounterExample extends StatefulWidget {
const CounterExample({Key key}) : super(key: key);
_CounterExampleState createState() => _CounterExampleState();
}
class _CounterExampleState extends State<CounterExample> {
final _counter = Counter();
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Observer(
builder: (_) => Text(
'${_counter.value}',
style: const TextStyle(fontSize: 20),
)),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _counter.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
ReactionBuilder widget​
If you ever ran into a need for running a reaction when a Widget loads, you most
likely created a StatefulWidget
and put your reaction in the initState()
.
This forces you to make a wrapper widget, whose sole purpose is to run the
reaction and dispose it as part of the dispose()
method. Rather that doing
that ceremony everytime, there is a simpler way of handling this with the
ReactionBuilder
.
ReactionBuilder({required ReactionDisposer Function(BuildContext context) builder, required Widget child})
​
Here is a snippet from the ConnectivityExample:
class ConnectivityExample extends StatelessWidget {
const ConnectivityExample(this.store, {Key? key}) : super(key: key);
final ConnectivityStore store;
Widget build(BuildContext context) => ScaffoldMessenger(
child: ReactionBuilder(
builder: (context) {
return reaction((_) => store.connectivityStream.value, (result) {
final messenger = ScaffoldMessenger.of(context);
messenger.showSnackBar(SnackBar(
content: Text(result == ConnectivityResult.none
? 'You\'re offline'
: 'You\'re online')));
}, delay: 4000);
},
child: Scaffold(
appBar: AppBar(
title: const Text('Settings'),
),
body: const Padding(
padding: EdgeInsets.all(16),
child: Text(
'Turn your connection on/off for approximately 4 seconds to see the app respond to changes in your connection status.'),
),
)),
);
}
Notice the use of the ReactionBuilder
that sets up the reaction in the
builder()
method.
This simplifies the outer widget and keeps it as a StatelessWidget
. Without
the ReactionBuilder
, you would have to create a wrapper StatefulWidget
,
prepare the reaction in the initState()
and then dispose it in the dispose()
of the widget.
Use the ReactionBuilder
to keep your UI tree as a set of StatelessWidgets
.