built_viewmodel_generator 1.2.6

  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • --

Deprecated #

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.

built_viewmodel.dart #

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.

Let's start #

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

Use the stream #

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.

Don't forget ... #

... to call dispose()

Add this to your State class

@override
void dispose() {
  widget.model.dispose();
  super.dispose();
}

Advanced feature #

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.

1.2.6 - 2018/09/14 #

  • this library is deprecated from now and will not be supported anymore

1.2.1 - 2018/04/10 #

  • small enhancements

1.2.0 - 2018/04/09 #

1.1.1 - 2018/04/04 #

  • handle import prefixes
  • optimise code generator to pass static analysis check

1.1.0 - 2018/04/03 #

  • API breaking change: rename listener classes to match Dart naming guide

1.0.1 - 2018/04/02 #

  • small enhancements

1.0.0 - 2018/04/01 #

  • first release

example/lib/main.dart

// Copyright (c) 2018., Ralph Bergmann.
// All rights reserved. Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

library main;

import 'dart:async';

import 'package:built_viewmodel/built_viewmodel.dart';
import 'package:flutter/material.dart';

part 'main.g.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(primarySwatch: Colors.blue),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final MyHomePageViewModel model = new MyHomePageViewModel();
  final String title;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(title),
      ),
      body: new ViewModelConnector(
        viewModel: model,
        onInit: (MyHomePageViewModel viewModel) => viewModel.setCounter(0),
        builder: (MyHomePageViewModel viewModel) => new Center(
              child: new Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  new Text('You have pushed the button this many times:'),
                  new StreamBuilder(
                    stream: viewModel.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}";
                      }
                    },
                  ),
                ],
              ),
            ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: () => model.increaseCounter(),
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}

abstract class MyHomePageViewModel extends ViewModel<MyHomePageViewModelController> {
  Stream<int> get counter;

  int _count;

  @OnListenHandler('counter')
  void onListen() => print('start listening');

  @OnCancelHandler('counter')
  void onCancel() => print('cancel listening');

  void setCounter(int value) {
    _count = value;
    controller.counter.add(value);
  }

  void increaseCounter() {
    if (_count != null) setCounter(_count + 1);
  }

  factory MyHomePageViewModel() = _$MyHomePageViewModel;

  MyHomePageViewModel._();
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  built_viewmodel_generator: ^1.2.6

2. Install it

You can install packages from the command line:

with pub:


$ pub get

Alternatively, your editor might support pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:built_viewmodel_generator/built_viewmodel_generator.dart';
  
Version Uploaded Documentation Archive
1.2.6 Sep 14, 2018 Go to the documentation of built_viewmodel_generator 1.2.6 Download built_viewmodel_generator 1.2.6 archive
1.2.5 Sep 13, 2018 Go to the documentation of built_viewmodel_generator 1.2.5 Download built_viewmodel_generator 1.2.5 archive
1.2.4 May 16, 2018 Go to the documentation of built_viewmodel_generator 1.2.4 Download built_viewmodel_generator 1.2.4 archive
1.2.3 May 16, 2018 Go to the documentation of built_viewmodel_generator 1.2.3 Download built_viewmodel_generator 1.2.3 archive
1.2.2 Apr 12, 2018 Go to the documentation of built_viewmodel_generator 1.2.2 Download built_viewmodel_generator 1.2.2 archive
1.2.1 Apr 10, 2018 Go to the documentation of built_viewmodel_generator 1.2.1 Download built_viewmodel_generator 1.2.1 archive
1.2.0 Apr 9, 2018 Go to the documentation of built_viewmodel_generator 1.2.0 Download built_viewmodel_generator 1.2.0 archive
1.1.2 Apr 5, 2018 Go to the documentation of built_viewmodel_generator 1.1.2 Download built_viewmodel_generator 1.1.2 archive
1.1.1 Apr 4, 2018 Go to the documentation of built_viewmodel_generator 1.1.1 Download built_viewmodel_generator 1.1.1 archive
1.1.0 Apr 2, 2018 Go to the documentation of built_viewmodel_generator 1.1.0 Download built_viewmodel_generator 1.1.0 archive

All 15 versions...

Awaiting analysis to complete.