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.

Avatar for Paolo Rotolo

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); }); } }