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

April Flutter Meetup - State Management Introdu...

GDG Montreal
April 18, 2023
38

April Flutter Meetup - State Management Introduction

GDG Montreal

April 18, 2023
Tweet

More Decks by GDG Montreal

Transcript

  1. Contents • Widget states • InheritedWidget • State management •

    ChangeNotifier • Provider • Design pattern or Architecture
  2. Thinking declaratively // Imperative style b.setColor(red) b.clearChildren() ViewC c3 =

    new ViewC(...) b.add(c3) // Declarative style return ViewB( color: red, child: const ViewC(), ); In order to lighten the burden on developers from having to program how to transition between various UI states, Flutter, by contrast, lets the developer describe the current UI state and leaves the transitioning to the framework like opacity animation.
  3. Widget state: stateless • Suitable for immutable state • The

    build method of a stateless widget is typically only called in three situations: ◦ the first time the widget is inserted in the tree ◦ when the widget's parent changes its configuration ◦ when an InheritedWidget it depends on changes • Sub widgets can change like Stream widget • If the 2nd and 3rd happen frequently, isolate the widget to optimize the performance class Frog extends StatelessWidget { const Frog({ Key? key, this.color = const Color(0xFF2DBD3A), this.child, }) : super(key: key); final Color color; final Widget? child; @override Widget build(BuildContext context) { return Container(color: color, child: child); } }
  4. Widget state: stateful • Suitable for mutable state • State

    is information that: ◦ Can be read synchronously when the widget is built ◦ Might change during the lifetime of the widget • Notify state changes using State.setState • Calling setState notifies the framework that the internal state of this object has changed, and it rebuilds the whole widget • It is an error to call setState in build method or after the framework calls dispose, so use if(mounted) if you use it in async function
  5. Performance considerations • Widgets that use State.setState or depend on

    InheritedWidgets. These will typically rebuild many times during the application's lifetime • Push the state to the leaves • If a subtree does not change, cache the widget (By final or const) that represents that subtree and re-use it each time it can be used • Avoid changing the depth of any created subtrees or changing the type of any widgets in the subtree • In general, avoid using stateful widget except for ui only use-case like animations or app lifecycle Bad Structure Example Vs Better Structure Example
  6. InheritedWidget • Base class for widgets that efficiently propagate information

    down the tree, like Theme.of(context) • To obtain the nearest instance of a particular type of inherited widget from a build context, use BuildContext.dependOnInheritedWid getOfExactType. • Inherited widgets, when referenced in this way, will cause the consumer to rebuild when the inherited widget itself changes state
  7. InheritedWidget class StudentState extends InheritedWidget { const StudentState({ Key? key,

    required this.grade, required Widget child, }) : super(key: key, child: child); final int grade; static StudentState of(BuildContext context) { final StudentState? result = context.dependOnInheritedWidgetOfExactType<StudentState>(); assert(result != null, 'No StudentStatefound in context'); return result!; } @override bool updateShouldNotify(StudentState old) => grade != old.grade; } final studentState = StudentState.of(context);
  8. Performance considerations • Widget 1 changes its state on InheritedWidget

    frequently • Widget 2 is not dependent on the state of its parent InheritedWidget • So widget 2 always rebuilds itself because of widget 1 • Isolate widget 1 and its state from the other widgets
  9. State management There comes a time when you need to

    share application state between screens, across your app. There are many approaches you can take, and many questions to think about.
  10. E ephemeral state Vs A pp state • Ephemeral state

    (sometimes called UI state or local state) is the state you can neatly contain in a single widget like: ◦ current page in a PageView ◦ current progress of a complex animation ◦ current selected tab in a BottomNavigationBar • State that is not ephemeral, that you want to share across many parts of your app, and that you want to keep between user sessions, is what we call application state like the shopping cart in an e-commerce app • There are two conceptual types of state in any Flutter app: ◦ Ephemeral state can be implemented using State and setState(), and is often local to a single widget ◦ The rest is your app state
  11. ChangeNotifier The shopping cart in an e-commerce app: • Add

    product • Remove product • Checkout the cart
  12. ChangeNotifier class CartState extends ChangeNotifier { /// Internal, private state

    of the cart. final List<Item> _items = []; /// An unmodifiable view of the items in the cart. UnmodifiableListView<Item> get items => UnmodifiableListView(_items); /// The current total price of all items (assuming all items cost $42). int get totalPrice => _items.length * 42; /// Adds [item] to cart. This and [removeAll] are the only ways to modify the /// cart from the outside. void add(Item item) { _items.add(item); // This call tells the widgets that are listening to this model to rebuild. notifyListeners(); } /// Removes all items from the cart. void removeAll() { _items.clear(); // This call tells the widgets that are listening to this model to rebuild. notifyListeners(); } }
  13. ChangeNotifier class CartStateScope extends InheritedNotifier<CartState> { const CartStateScope({ super.key, super.notifier,

    required super.child, }); static CartState of(BuildContext context) { final result = context.dependOnInheritedWidgetOfExactType<CartStateScope>()?.notifier; assert(result != null, 'No CartStateScope found in context'); return result!; } } CartStateScope.of(context).add(Item(name: 'Phone')); CartStateScope.of(context).removeAll();
  14. ChangeNotifier class MyCart extends StatefulWidget { const MyCart({Key? key}) :

    super(key: key); @override _MyCartState createState() => _MyCartState(); } class MyCartState extends State<MyCart> with ChangeNotifier { late CartState cartState; @override void initState() { cartState = CartState()..addListener(notifyListeners); super.dispose(); } @override void dispose() { cartState.removeListener(notifyListeners); super.dispose(); } @override Widget build(BuildContext context) { final totalPrice = CartStateScope.of(context).totalPrice; return Text(totalPrice); } }
  15. ChangeNotifier Advantages: • Simple and easy to use within the

    flutter framework • Suitable for the simple and small features like theming, navigation, fonts, localization Disadvantages: • Using StatefulWidget • Boilerplate of writing each InheritedWidget and adding and removing listeners
  16. Solution Using dedicated packages for the state managements like Provider:

    • A wrapper around InheritedWidget to make them easier to use and more reusable. • Simplified allocation/disposal of resources • A vastly reduced boilerplate over making a new class every time • But it’s hard to implement a design pattern or architecture
  17. Provider class MyCart extends StatelessWidget { @override Widget build(BuildContext context)

    { return Consumer<CartState>( builder: (context, cart, child) => Stack( children: [ // Use SomeExpensiveWidget here, without rebuilding every time. if (child != null) child, Text("Total price: ${cart.totalPrice}"), ], // Build the expensive widget here. child: SomeExpensiveWidget(), ), ); } } Provider.of<CartModel>(context, listen: false)!.add(Item(name: 'Phone')); Provider.of<CartModel>(context, listen: false)!.removeAll(); ChangeNotifier Example Vs Provider Example
  18. Design pattern or Architecture The goal of using most of

    design patterns is to separate Data Model and View, why: • Make a clean structure for beautiful and easy to read • More comprehensible business logic • Separate features to simultaneously programming • Using reusable components • High scalability • Writing test for each component
  19. List of state management approaches The norm is, no matter

    what you choose, business requirements should be in the priority, like: • Speed • Scalability • Performance • Stability • Maintainability • Quality • Quantity