This library is deprecated from now and will not be supported anymore. Please use the BLoC pattern (Build reactive mobile apps with Flutter (Google I/O '18)) instead, which does (more or less) the same but (for now) without code generation.
The built_viewmodel.dart
package provides a way to create ViewModel
classes.
It is a little bit inspired by the Android ViewModel but just a little bit ;-)
Usually, you call setState()
whenever the state of the Widget should change.
But in my opinion, it is not a good idea to call setState()
when just a small part of the whole state has changed
(read more: How fast is Flutter? I built a stopwatch app to find out.,
Avoiding Empty State Callbacks).
This package creates lots of Streams (as much as you need) to feed StreamBuilders.
Create an abstract class, e.g. MyHomePageViewModel
:
library main;
import 'dart:async';
import 'package:built_viewmodel/built_viewmodel.dart';
part 'main.g.dart';
abstract class MyHomePageViewModel implements ViewModel<MyHomePageViewModelController> {
factory MyHomePageViewModel() = _$MyHomePageViewModel;
MyHomePageViewModel._();
}
And add a reference of it to the Widget:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final MyHomePageViewModel model = new MyHomePageViewModel();
final String title;
@override
_MyHomePageState createState() {
model.setCounter(0);
return new _MyHomePageState();
}
}
For now, the ViewModel makes nothing. You have to add your needed stream(s):
abstract class MyHomePageViewModel implements ViewModel<MyHomePageViewModelController> {
Stream<int> get counter; // new
factory MyHomePageViewModel() = _$MyHomePageViewModel;
MyHomePageViewModel._();
You also need to add a code generator. Create a tool/build.dart
file with the following content:
import 'dart:async';
import 'package:build_config/build_config.dart';
import 'package:build_runner/build_runner.dart';
import 'package:built_viewmodel_generator/built_viewmodel_generator.dart';
import 'package:source_gen/source_gen.dart';
final builders = [
applyToRoot(
new PartBuilder([
new BuiltViewModelGenerator(),
]),
generateFor: const InputSet(
include: const [
'lib/*.dart',
],
),
),
];
Future main(List<String> args) async {
await build(
builders,
deleteFilesByDefault: true,
verbose: false,
);
}
Execute the tool/build.dart
file and it will generate a main.g.dart
file which has all the logic for your stream(s).
To use the stream(s) you need a StreamBuilder where you have to set the stream to the one from the ViewModel:
new StreamBuilder(
stream: widget.model.counter,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('not connected');
case ConnectionState.waiting:
return new Text('awaiting interaction...');
case ConnectionState.active:
return new Text('counter: ${snapshot.data}');
case ConnectionState.done:
return new Text('counter: ${snapshot.data} (closed)');
default:
throw "Unknown: ${snapshot.connectionState}";
}
},
),
Whenever you send a new value to the stream the content of the StreamBuilder will change but only this small part and not the whole state.
To do that add a void setCounter(int value)
method to the ViewModel ...
abstract class MyHomePageViewModel implements ViewModel<MyHomePageViewModelController> {
Stream<int> get counter;
void setCounter(int value) => controller.counter.add(value); // new
factory MyHomePageViewModel() = _$MyHomePageViewModel;
MyHomePageViewModel._();
}
... and just call it.
... to call dispose()
Add this to your State
class
@override
void dispose() {
widget.model.dispose();
super.dispose();
}
Sometimes you need to know the state of the stream, for example, to start a download.
You can create methods and annotate them with one of this annotations: @OnListenHandler
, @OnPauseHandler
, @OnResumeHandler
, @OnCancelHandler
.
These annotations have a name parameter which must match with the name of the stream.
For example, if you have a counter stream Stream<int> get counter
,
and you want to know when something is listening for it the annotation should look like this: @OnListenHandler('counter')
.
abstract class MyHomePageViewModel implements ViewModel<MyHomePageViewModelController> {
Stream<int> get counter;
@OnListenHandler('counter') // new
onListen() => print('start listening for updates'); // new
void setCounter(int value) => controller.counter.add(value);
factory MyHomePageViewModel() = _$MyHomePageViewModel;
MyHomePageViewModel._();
}
You can find the full example here.