Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pop screen using Mobx in flutter

Tags:

flutter

dart

mobx

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();
  }
}
like image 534
Haaris Ahamed Avatar asked Mar 04 '23 22:03

Haaris Ahamed


1 Answers

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);
  }
}
like image 57
Alexander Arendar Avatar answered Mar 16 '23 23:03

Alexander Arendar