Skip to content

09 Flutter - State

State

flutter

UI - f(state)

  • Flutter UI is function of state - UI = f(state)
  • Declarative style vs imperative
    • Declarative - Flutter builds its user interface to reflect the current state of your app
  • There is no TextField.setText()

For example, in Flutter it’s okay to rebuild parts of your UI from scratch instead of modifying it. Flutter is fast enough to do that, even on every frame if needed.

Types of state

  • State - data needed to rebuild UI at any moment in time
  • Ephemeral state - state contained in a single widget setState()
  • App state – state shared across many parts of app (sometimes also called shared state)
    • Package "provider"

Keep the state above the widgets that use it (lifting state up)

Addin package

https://pub.dev/packages/provider

pubspec.yaml

1
2
3
4
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.5

Using callbacks

  • NB! Widgets are immutable – they get destroyed and replaced by new ones.
  • Dart’s functions are first class objects, so you can pass callbacks around.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@override
Widget build(BuildContext context) {
  return SomeWidget(
    // Construct the widget, passing it a reference to the method below.
    MyListItem(myTapCallback),
  );
}

void myTapCallback(Item item) {
  print('user tapped on $item');
}

Provider 1

  • Passing around callbacks – there is lot of them - finally. No good.

  • Flutter has special low-level widgets for state handling

    • InheritedWidget
    • InheritedNotifier
    • InheritedModel
  • Easy to use wrapper around these is

    • Provider

Provider 2

  • ChangeNotifier provides change notification to its listeners. You can subscribe to its changes. (It is a form of Observable).

  • ChangeNotifierProvider is the widget that provides an instance of a ChangeNotifier to its descendants.

  • Consumer – get access to state, widget is recreated when state changes

  • Provider.of – get access to state, widget is NOT recreated when state changes

Model - POCO

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Counter {
  bool bit0 = false;
  bool bit1 = false;
  bool bit2 = false;

  Counter({this.bit2, this.bit1, this.bit0});

  void addToBit0() {
    bit0 = !bit0;
    if (!bit0){
      addToBit1();
    }
  }

  void addToBit1(){
    bit1 = !bit1;
    if (!bit1){
      addToBit2();
    }
  }

  void addToBit2(){
    bit2 = !bit2;
  }
}

ChangeNotifier

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import 'package:flutter/material.dart';
import '../models/counter.dart';

class CounterModel extends ChangeNotifier {
  final Counter _counter = Counter();

  bool get bit0 => _counter.bit0;
  bool get bit1 => _counter.bit1;
  bool get bit2 => _counter.bit2;

  void addToBit0() {
    _counter.addToBit0();
    notifyListeners();
  }
...
  void add(){
    _counter.addToBit0();
    notifyListeners();
  }
    void reset(){
    _counter.bit0 = false; _counter.bit1 = false; _counter.bit2 = false;
    notifyListeners();
  }
}

State lifting

flutter

ChangeNotifierProvider

State in HomeScreen

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Binary counter'),
        ),
        body: ChangeNotifierProvider<CounterModel>(
          builder: (context) => CounterModel(),
          child: Center(

Consumer

Widget is recreated on every state change!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class SingleBitt extends StatelessWidget {
  final int bitNo;

  SingleBitt({@required this.bitNo});

  @override
  Widget build(BuildContext context) {
    return Consumer<CounterModel>(
      builder: (context, state, child) {
        return Container(
...
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Text(
                this.getBitStr(state),

Provider.Of

Widget is not recreated on state change (possibly)!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class ButtonAdd extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Text('Add 1'),
      onPressed: () {
        Provider.of<CounterModel>(context, listen: false).add();
      },
    );
  }
}

Recap

State inside widget – StatefulWidget and setState((){…})

App State – use Provider package

  • ChangeNotifier – define ModelClass (extend or mixin ChangeNotifier)
  • ChangeNotifierProvider - state holder in upper component
  • Consumer - widget accesses state, gets recreated on state change
  • Provider.of(context, listen: false) – access state in widgets, which are not UI dependent of state (action buttons for example)

Multiple states, proxy

MultiProvider

1
2
3
4
5
6
7
return MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (_) => User()),
        ChangeNotifierProvider(builder: (_) => Customer()),
        ChangeNotifierProvider(builder: (_) => Group())
      ],
      child: MaterialApp(

ProxyProvider – generic provider that builds a value based on other providers

1
2
3
4
5
ProxyProvider<model1, model2, combinedModel>  

ProxyProvider<Api, HomeModel>(
      builder: (context, api, homeModel) => HomeModel(api: api),
)