$30 off During Our Annual Pro Sale. View Details »

April Flutter Meetup - State Management Introduction

GDG Montreal
April 18, 2023
23

April Flutter Meetup - State Management Introduction

GDG Montreal

April 18, 2023
Tweet

Transcript

  1. State Management
    Introduction
    By Ali Yazdi
    [email protected]
    linkedin.com/in/aliyazdi75

    View Slide

  2. Contents
    ● Widget states
    ● InheritedWidget
    ● State management
    ● ChangeNotifier
    ● Provider
    ● Design pattern or Architecture

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. 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();
    assert(result != null, 'No StudentStatefound in context');
    return result!;
    }
    @override
    bool updateShouldNotify(StudentState old) => grade != old.grade;
    }
    final studentState = StudentState.of(context);

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  12. ChangeNotifier
    A simple and straightforward class
    included in the flutter sdk to manage
    states in the application

    View Slide

  13. ChangeNotifier
    The shopping cart in an e-commerce app:
    ● Add product
    ● Remove product
    ● Checkout the cart

    View Slide

  14. ChangeNotifier
    class CartState extends ChangeNotifier {
    /// Internal, private state of the cart.
    final List _items = [];
    /// An unmodifiable view of the items in the cart.
    UnmodifiableListView 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();
    }
    }

    View Slide

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

    View Slide

  16. ChangeNotifier
    class MyCart extends StatefulWidget {
    const MyCart({Key? key}) : super(key: key);
    @override
    _MyCartState createState() => _MyCartState();
    }
    class MyCartState extends State 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);
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  19. Provider class MyCart extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return Consumer(
    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(context, listen: false)!.add(Item(name: 'Phone'));
    Provider.of(context, listen: false)!.removeAll();
    ChangeNotifier Example Vs Provider Example

    View Slide

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

    View Slide

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

    View Slide

  22. The Only Resource: flutter.dev

    View Slide

  23. Thanks for your attention 💙
    QA
    Email: [email protected]
    Linkedin: linkedin.com/in/aliyazdi75

    View Slide