Upgrade to Pro — share decks privately, control downloads, hide ads and more …

State Management in Flutter (Flutter Festival B...

State Management in Flutter (Flutter Festival Bari)

State Management in Flutter at Flutter Festival 2 (Stateful Things) at Politecnico di Bari.

Paolo Rotolo

May 17, 2022
Tweet

More Decks by Paolo Rotolo

Other Decks in Programming

Transcript

  1. Introduction to State Management • Declarative programming (vs imperative); •

    Single source of truth; • setState; • lifting state up; • provider;
  2. // Imperative style b.setColor(red) b.clearChildren() ViewC c3 = new ViewC(...)

    b.add(c3) ViewA a ViewB b ViewC c1 ViewC c2 ViewC c3 ViewB b ViewA a
  3. // Declarative style return ViewB( color: red, child: ViewC(...), )

    ViewA a ViewB b ViewC c1 ViewC c2 ViewC c3 ViewB b ViewA a
  4. // Declarative style return ViewB( color: red, child: ViewC(...), )

    • Widgets are immutable; • Widgets are easily rebuilt;
  5. // Declarative style return ViewB( color: red, child: ViewC(...), )

    • Widgets are immutable; • Widgets are easily rebuilt; rebuild UI tree when state changes
  6. class HelloView extends StatelessWidget { String name; HelloView({Key? key, required

    this.name}) : super(key: key); @override Widget build(BuildContext context) { return Text("Hello $name!", style: const TextStyle(fontSize: 42),); } }
  7. class HelloView extends StatelessWidget { String name; HelloView({Key? key, required

    this.name}) : super(key: key); @override Widget build(BuildContext context) { return Text("Hello $name!", style: const TextStyle(fontSize: 42),); } }
  8. class HelloView extends StatelessWidget { String name; HelloView({Key? key, required

    this.name}) : super(key: key); @override Widget build(BuildContext context) { return Text("Hello $name!", style: const TextStyle(fontSize: 42),); } } HelloView(name: 'Flutter')
  9. class HelloView extends StatelessWidget { String name; HelloView({Key? key, required

    this.name}) : super(key: key); @override Widget build(BuildContext context) { return Text("Hello $name!", style: const TextStyle(fontSize: 42),); } } HelloView(name: ‘Paolo')
  10. class HelloView extends StatelessWidget { String name; HelloView({Key? key, required

    this.name}) : super(key: key); @override Widget build(BuildContext context) { return Text("Hello $name!", style: const TextStyle(fontSize: 42),); } } HelloView(name: ‘GDG')
  11. class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title})

    : super(key: key); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( int _counter = 0;
  12. class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) {

    return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ ], ), ), const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, child: const Icon(Icons.add), int _counter = 0;
  13. class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) {

    return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ ], ), ), const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, child: const Icon(Icons.add), int _counter = 0;
  14. children: <Widget>[ ], ), ), ); } const Text( 'You

    have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, child: const Icon(Icons.add), ), void _incrementCounter() { setState(() { _counter + + ; }); }
  15. const Text( 'You have pushed the button this many times:',

    ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), int _counter = 0;
  16. class EventTile extends StatefulWidget { String title; bool initialState; EventTile({Key?

    key, required this.title, required this.initialState}) : super(key: key); @override State<EventTile> createState() = > _EventTileState(isFavorite: initialState); } class _EventTileState extends State<EventTile> { _EventTileState({required this.isFavorite}) @override Widget build(BuildContext context) { return Card( child: ListTile( title: Text(widget.title), trailing: isFavorite ? Icon(Icons.favorite, color: Colors.redAccent,) : Icon(Icons.favorite_border_outlined, color: Colors.grey,), ), ); } } onTap: () { setState(() {isFavorite = !isFavorite;}); }, bool isFavorite;
  17. class EventTile extends StatefulWidget { String title; bool initialState; EventTile({Key?

    key, required this.title, required this.initialState}) : super(key: key); @override State<EventTile> createState() = > _EventTileState(isFavorite: initialState); } class _EventTileState extends State<EventTile> { _EventTileState({required this.isFavorite}) @override Widget build(BuildContext context) { return Card( child: ListTile( title: Text(widget.title), trailing: isFavorite ? Icon(Icons.favorite, color: Colors.redAccent,) : Icon(Icons.favorite_border_outlined, color: Colors.grey,), ), ); } } onTap: () { setState(() {isFavorite = !isFavorite;}); setEventAsFavorite(title); }, bool isFavorite;
  18. class EventTile extends StatefulWidget { String title; bool initialState; EventTile({Key?

    key, required this.title, required this.initialState}) : super(key: key); @override State<EventTile> createState() >
  19. class EventTile extends StatefulWidget { String title; bool initialState; EventTile({Key?

    key, required this.title, required this.initialState}) : super(key: key); @override State<EventTile> createState() >
  20. Single source of truth • Every piece of state has

    one owner; • Only the owner can make changes to state;
  21. Single source of truth • Every piece of state has

    one owner; • Only the owner can make changes to state; • Owner listens to events and makes changes in response;
  22. class EventTile extends StatefulWidget { String title; bool initialState; EventTile({Key?

    key, required this.title, required this.initialState}) : super(key: key); @override State<EventTile> createState() >
  23. class EventTile extends StatefulWidget { String title; bool initialState; EventTile({Key?

    key, required this.title, required this.initialState}) : super(key: key); @override State<EventTile> createState() >
  24. class EventTile extends StatefulWidget { String title; bool initialState; EventTile({Key?

    key, required this.title, required this.initialState}) : super(key: key); @override State<EventTile> createState() = > _EventTileState(isFavorite: initialState); } class _EventTileState extends State<EventTile> { _EventTileState({required this.isFavorite}) @override Widget build(BuildContext context) { return Card( child: ListTile( title: Text(widget.title), trailing: isFavorite ? Icon(Icons.favorite, color: Colors.redAccent,) : Icon(Icons.favorite_border_outlined, color: Colors.grey,), ), ); } } onTap: () { model.setEventAsFavorite(title); }, bool isFavorite; Lift state up
  25. class EventTile extends StatelessWidget { String title; bool favourite; EventTile({Key?

    key, required this.title, required this.favourite,}) : super(key: key); @override Widget build(BuildContext context) { return Card( child: ListTile( title: Text(title), trailing: favourite ? Icon(Icons.favorite, color: Colors.redAccent,) : Icon(Icons.favorite_border_outlined, color: Colors.grey,), onTap: () { model.setEventAsFavourite(title); }, ), ); } }
  26. class ImageWidget extends StatelessWidget { const ImageWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { return Transform.rotate( child: Image.asset("assets/image.png"), angle: 0.0, ); } } class SliderWidget extends StatelessWidget { const SliderWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Slider(value: 0.5, onChanged: (double value) { }); }
  27. class SliderWidget extends StatefulWidget { const SliderWidget({Key? key}) : super(key:

    key); @override State<SliderWidget> createState() => _SliderWidgetState(); } class _SliderWidgetState extends State<SliderWidget> { @override Widget build(BuildContext context) { return Slider(value: 0.5, onChanged: (double value) { }); } }
  28. class SliderWidget extends StatefulWidget { const SliderWidget({Key? key}) : super(key:

    key); @override State<SliderWidget> createState() => _SliderWidgetState(); } class _SliderWidgetState extends State<SliderWidget> { double rotation = 0.0; @override Widget build(BuildContext context) { return Slider(value: rotation, onChanged: (double value) { setState(() { rotation = value; }); }); } }
  29. class ImageWidget extends StatelessWidget { const ImageWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { return Transform.rotate( child: Image.asset("assets/image.png"), angle: 0.0, ); } }
  30. class ImageWidget extends StatefulWidget { const ImageWidget({Key? key}) : super(key:

    key); @override State<ImageWidget> createState() => ImageWidgetState(); } class ImageWidgetState extends State<ImageWidget> { @override Widget build(BuildContext context) { return Transform.rotate( child: Image.asset("assets/image.png"), angle: 0.0, ); } }
  31. class ImageWidget extends StatefulWidget { const ImageWidget({Key? key}) : super(key:

    key); @override State<ImageWidget> createState() => ImageWidgetState(); } class _ImageWidgetState extends State<ImageWidget> { double imageRotation = 0; @override Widget build(BuildContext context) { return Transform.rotate( child: Image.asset("assets/image.png"), angle: imageRotation, ); } }
  32. _ImageWidgetState? imageState; class ImageWidget extends StatefulWidget { const ImageWidget({Key? key})

    : super(key: key); @override State<ImageWidget> createState() { imageState = _ImageWidgetState(); return imageState!; } } class _ImageWidgetState extends State<ImageWidget> { double imageRotation = 0; @override Widget build(BuildContext context) { return Transform.rotate( child: Image.asset("assets/image.png"), angle: imageRotation, ); } }
  33. class SliderWidget extends StatefulWidget { const SliderWidget({Key? key}) : super(key:

    key); @override State<SliderWidget> createState() => _SliderWidgetState(); } class _SliderWidgetState extends State<SliderWidget> { double rotation = 0.0; @override Widget build(BuildContext context) { return Slider(value: rotation, onChanged: (double value) { setState(() { rotation = value; }); }); } }
  34. class SliderWidget extends StatefulWidget { const SliderWidget({Key? key}) : super(key:

    key); @override State<SliderWidget> createState() => _SliderWidgetState(); } class _SliderWidgetState extends State<SliderWidget> { double rotation = 0.0; @override Widget build(BuildContext context) { return Slider(value: rotation, onChanged: (double value) { setState(() { rotation = value; }); }); } }
  35. class SliderWidget extends StatefulWidget { const SliderWidget({Key? key}) : super(key:

    key); @override State<SliderWidget> createState() => _SliderWidgetState(); } class _SliderWidgetState extends State<SliderWidget> { double rotation = 0.0; @override Widget build(BuildContext context) { return Slider(value: rotation, onChanged: (double value) { setState(() { rotation = value; imageState ?. setState(() { imageState ?. imageRotation = value; }); }); }); }
  36. class SliderWidget extends StatefulWidget { const SliderWidget({Key? key}) : super(key:

    key); @override State<SliderWidget> createState() => _SliderWidgetState(); } class _SliderWidgetState extends State<SliderWidget> { double rotation = 0.0; @override Widget build(BuildContext context) { return Slider(value: rotation, onChanged: (double value) { setState(() { rotation = value; imageState ?. setState(() { imageState ?. imageRotation = value; }); }); }); }
  37. // Imperative style b.setColor(red) b.clearChildren() ViewC c3 = new ViewC(...)

    b.add(c3) ViewA a ViewB b ViewC c1 ViewC c2 ViewC c3 ViewB b ViewA a // Declarative style return ViewB( color: red, child: ViewC(...), )
  38. • Strongly coupling widgets; • Globally tracking state; • UI

    updating other UI; Bad things ImageWidget SliderWidget HomeView
  39. class HomeModel extends ChangeNotifier { double _rotation = 0; void

    setRotation(double newRotation) { _rotation = newRotation; // This call tells the widgets that // are listening to this model to rebuild. notifyListeners(); } double getRotation() => _rotation; }
  40. class HomeModel extends ChangeNotifier { double _rotation = 0; void

    setRotation(double newRotation) { _rotation = newRotation; // This call tells the widgets that // are listening to this model to rebuild. notifyListeners(); } double getRotation() => _rotation; }
  41. @override Widget build(BuildContext context) { return MaterialApp( title: 'Stateful Things',

    theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: Padding( padding: const EdgeInsets.all(32.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Divider(height: 128), ImageWidget(), Divider(height: 128), SliderWidget(), ], ),
  42. @override Widget build(BuildContext context) { return MaterialApp( title: 'Stateful Things',

    theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: Padding( padding: const EdgeInsets.all(32.0), child: ChangeNotifierProvider( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Divider(height: 128), ImageWidget(), Divider(height: 128), SliderWidget(), ], ),
  43. @override Widget build(BuildContext context) { return MaterialApp( title: 'Stateful Things',

    theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: Padding( padding: const EdgeInsets.all(32.0), child: ChangeNotifierProvider( create: (context) = > HomeModel(), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Divider(height: 128), ImageWidget(), Divider(height: 128), SliderWidget(), ], ),
  44. @override Widget build(BuildContext context) { return MaterialApp( title: 'Stateful Things',

    theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: Padding( padding: const EdgeInsets.all(32.0), child: MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => HomeModel)), ], create: (context) = > HomeModel(), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Divider(height: 128), ImageWidget(), Divider(height: 128), SliderWidget(), ], ),
  45. @override Widget build(BuildContext context) { return MaterialApp( title: 'Stateful Things',

    theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: Padding( padding: const EdgeInsets.all(32.0), child: MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => HomeModel)), ChangeNotifierProvider(create: (context) … ChangeNotifierProvider(create: (context) … ], create: (context) = > HomeModel(), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Divider(height: 128), ImageWidget(), Divider(height: 128), SliderWidget(),
  46. class ImageWidget extends StatelessWidget { const ImageWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { return Transform.rotate( child: Image.asset("assets/image.png"), angle: 0.0, ); } }
  47. class ImageWidget extends StatelessWidget { const ImageWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { return Consumer( builder: (context, model, _) { return Transform.rotate( child: Image.asset("assets/image.png"), angle: model.getRotation(), ); }, ); } }
  48. class SliderWidget extends StatelessWidget { const SliderWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { return Slider(value: 0.0, onChanged: (double value) { }); } }
  49. class SliderWidget extends StatelessWidget { const SliderWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { var model = Provider.of<HomeModel>(context, listen: true); return Slider(value: 0.0, onChanged: (double value) { }); } }
  50. class SliderWidget extends StatelessWidget { const SliderWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { var model = Provider.of<HomeModel>(context, listen: true); return Slider(value: model.getRotation(), onChanged: (double value) { }); } }
  51. class SliderWidget extends StatelessWidget { const SliderWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { var model = Provider.of<HomeModel>(context, listen: true); return Slider(value: model.getRotation(), onChanged: (double value) { model.setRotation(value); }); } }
  52. class SliderWidget extends StatelessWidget { const SliderWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { var model = Provider.of<HomeModel>(context, listen: true); return Slider(value: model.getRotation(), onChanged: (double value) { model.setRotation(value); }); } }
  53. class HomeModel extends ChangeNotifier { double _rotation = 0; void

    setRotation(double newRotation) { _rotation = newRotation; // This call tells the widgets that // are listening to this model to rebuild. notifyListeners(); } double getRotation() => _rotation; }
  54. @override Widget build(BuildContext context) { return MaterialApp( title: 'Stateful Things',

    theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: Padding( padding: const EdgeInsets.all(32.0), child: ChangeNotifierProvider( create: (context) = > HomeModel(), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Divider(height: 128), ImageWidget(), Divider(height: 128), SliderWidget(), ], ),
  55. class ImageWidget extends StatelessWidget { const ImageWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { return Consumer( builder: (context, model, _) { return Transform.rotate( child: Image.asset("assets/image.png"), angle: model.getRotation(), ); }, ); } }
  56. class SliderWidget extends StatelessWidget { const SliderWidget({Key? key}) : super(key:

    key); @override Widget build(BuildContext context) { var model = Provider.of<HomeModel>(context, listen: true); return Slider(value: model.getRotation(), onChanged: (double value) { model.setRotation(value); }); } }