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 its builder
function. Whenever
these observables change, Observer
rebuilds and renders.
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 the builder
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 your Observer
-wrapped Widgets
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),
),
);
}
Performance optimizations​
Mostly you try to make Observer
as small as possible. But sometimes you want to exclude
the widget subtree from being rebuilt and improve the performance.
For that you can use an Observer.withBuiltChild
constructor. It takes builder
and child
arguments and internally uses the same technique as in AnimatedBuilder
Here is a brief example:
final obsColor = Observable(Colors.green);
Observer.withBuiltChild(
builder: (context, child) {
return Container(
padding: const EdgeInsets.all(16),
color: obsColor.value, // this widget will be rebuilt when the color value changes
child: child,
);
},
child: ListView.builder( // this part will not be rebuilt
shrinkWrap: true,
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
),
)
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
.