How to pop screen using Mobx in flutter

4,635

Constructing an Observer instance inside the didChangeDependencies() is indeed "silly" as you have rightly noted already :) Observer is a widget and widget needs to be inserted into the widgets tree in order to do something useful. In our case non-widget Mobx reactions come to the rescue.

I will show how I did it in my code for the case of showing a Snackbar upon observable change so you will get an idea how to transform your code.

First of all, import import 'package:mobx/mobx.dart';. Then in the didChangeDependencies() create a reaction which will use some of your observables. In my case these observables are _authStore.registrationError and _authStore.loggedIn :

final List<ReactionDisposer> _disposers = [];

@override
void dispose(){
  _disposers.forEach((disposer) => disposer());
  super.dispose();
}

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  _authStore = Provider.of<AuthStore>(context);
  _disposers.add(
    autorun(
      (_) {
        if (_authStore.registrationError != null)
          _scaffoldKey.currentState.showSnackBar(
            SnackBar(
              content: Text(_authStore.registrationError),
              backgroundColor: Colors.redAccent,
              duration: Duration(seconds: 4),
            ),
          );
      },
    ),
  );
  _disposers.add(
    reaction(
      (_) => _authStore.loggedIn,
      (_) => Navigator.of(context).pop(),
    ),
  );
}

I use two types of Mobx reactions here: autorun and reaction. autorun triggers the first time immediately after you crate it and then every time the observable changes its value. reaction does not trigger the first time, only when the observable change.

Also pay attention to dispose the created reactions in the dispose() method to avoid resources leak.

Here is a code of my Mobx store class with used observables to complete the picture:

import 'package:mobx/mobx.dart';

import 'dart:convert';

part "auth_store.g.dart";

class AuthStore = AuthStoreBase with _$AuthStore;

abstract class AuthStoreBase with Store{
  
  @observable
  String token;

  @observable
  String registrationError;
  
  @observable
  String loginError;

  @action
  void setToken(String newValue){
    token = newValue;
  }

  @action
  void setRegistrationError(String newValue){
    registrationError = newValue;
  }
  
  @action
  void setLoginError(String newValue){
    loginError = newValue;
  }

  @action
  void resetLoginError(){
    loginError = null;
  }

  @computed
  bool get loggedIn => token != null && token.length > 0;

  @action
  Future<void> logOut() async{
    setToken(null);
  }
}
Share:
4,635
Haaris Ahamed
Author by

Haaris Ahamed

Updated on December 11, 2022

Comments

  • Haaris Ahamed
    Haaris Ahamed over 1 year

    I have a Food object that contains properties like name, id, calories, etc. With a series of screens, the user populates the food object properties.

    Once done, the user can press the submit button, that will call the addFood method in the store.

    The problem is, after uploading the food to the server, i want to pop the screen or show error message in toast based on the response. I just don't know how to do this.

    Following is my code (only the important bits): FoodDetailStore.dart

    class FoodDetailStore = _FoodDetailStore with _$FoodDetailStore;
    
    abstract class _FoodDetailStore with Store {
      Repository _repository;
    
      Food _food;
    
      @observable
      String msg = '';
    
      // ... Other Observables and actions
    
      @action
      addFood(bool toAdd) {
        if (toAdd) {
          _repository.addFood(food).then((docId) {
           if (docId != null) {
             // need to pop the screen
           }
          }).catchError((e) {
             // show error to the user.
             // I tried this, but it didn't work
             msg = 'there was an error with message ${e.toString()}. please try again.';
          });
        }
    
      // .. other helper methods.
    }
    

    FoodDetailScreen.dart (Ignore the bloc references, I am currently refactoring code to mobx)

    class FoodDataScreen extends StatefulWidget {
      final String foodId;
      final Serving prevSelectedServing;
      final bool fromNewRecipe;
    
      FoodDataScreen({@required this.foodId, this.prevSelectedServing, this.fromNewRecipe});
    
      @override
      _FoodDataScreenState createState() => _FoodDataScreenState(
            this.foodId,
            this.prevSelectedServing,
            this.fromNewRecipe,
          );
    }
    
    class _FoodDataScreenState extends State<FoodDataScreen> {
      final String foodId;
      final Serving prevSelectedServing;
      final bool fromNewRecipe;
    
      FoodDataBloc _foodDataBloc;
    
      _FoodDataScreenState(
        this.foodId,
        this.prevSelectedServing,
        this.fromNewRecipe,
      );
    
      FoodDetailStore store;
    
      @override
      void initState() {
        store = FoodDetailStore();
        store.initReactions();
        store.initializeFood(foodId);
        super.initState();
      }
    
     @override
     void didChangeDependencies() {
       super.didChangeDependencies();
       // I know this is silly, but this is what i tried. Didn't worked
       Observer(
        builder: (_) {
         _showMsg(store.msg);
        }
       );
     }
    
      @override
      Widget build(BuildContext context) {
        return Container(
        // ... UI  
        );
      }
    
      _popScreen() {
        _showMsg('Food Added');
        Majesty.router.pop(context);
      }
    
      _showMsg(String msg) {
        Fluttertoast.showToast(msg: msg);
      }
    
      @override
      void dispose() {
        store.dispose();
        super.dispose();
      }
    }