marcglasberg / async_redux

Flutter Package: A Redux version tailored for Flutter, which is easy to learn, to use, to test, and has no boilerplate. Allows for both sync and async reducers.
Other
234 stars 40 forks source link

Display Dialog or SnackBar from Action's before method #21

Closed jans-y closed 5 years ago

jans-y commented 5 years ago

Hi,

I really enjoy using your library. It makes it so much easier to use the Redux with Flutter. Thank you.

I would like to ask how can I get a correct context to be able to display Dialog or SnackBar from the before method inside an abstract class.

abstract class BarrierAction extends ReduxAction<AppState> {
  Future before() async {
    dispatch(SetLoadingAction(true));
    if (!hasConnection) {
      showDialog(
          context: --> ???,
          builder: /* check your internet connection modal */
       )
  }
}

I was hoping something like navigatorKey.currentState.context would work but I wasn't successful.

I have also tried passing current context to the Action itself -> store.dispatch(SomeAction(context)) but I don't know how to pass it into the before method without overriding it, which adds (maybe) unnecessary complexity.

Thank you for your advice.

gadfly361 commented 5 years ago

@jans-y (Caution this may be bad advice 🤷‍♂)

I am personally trying to remove any reference to context whether it be directly (by passing context to an action), or indirectly (by using a global key such as navigatorKey and getting the context that way) to Actions.

Instead, I am leveraging Events to signal that something like that needs to be done, and then consuming the events in a didUpdateWidget method.

For example:

@override
void didUpdateWidget(MyWidget oldWidget) {
  super.didUpdateWidget(oldWidget);
  consumeEvents(context);
}

void consumeEvents(BuildContext context) {
  if (widget.showDialogEvt.consume()) { // code to showDialog here }
}

So you can have a setShowDialogAction that sets the showDialogEvt in its reduce method ... and then you can call setShowDialogAction in the before method of another action

marcglasberg commented 5 years ago

@gadfly361 is right. Usually Events are the way to go when you need to display dialogs. Why? Actions belong to the "business layer", and they should not know about Flutter widgets, let alone contexts, which are in the "client/UI layer". So you should use an Event to signal to some widget that it needs to open a dialog.

Just to complement Matthew's answer:

abstract class BarrierAction extends ReduxAction<AppState> {
  Future before() async {
    dispatch(SetLoadingAction(true));
    if (!hasConnection) return state(hasNoConnectionEvt: Event());
    else return null;    
  }
}

One of your widgets should be listening to store.state.hasNoConnectionEvt, and will consume the Event and show a dialog, as Matthew explained.

However, if all you want is a dialog with some text that displays an error message, then there is no need to do any of that. You can just throw an UserException:

abstract class BarrierAction extends ReduxAction<AppState> {
  Future before() async {
    dispatch(SetLoadingAction(true));
    if (!hasConnection) throw UserException("Please check your internet connection.");
    else return null;    
  }
}

This UserException will be caught by AsyncRedux, which will display a dialog with the message. For more details on how to setup AsyncRedux to display these dialogs, see this: https://pub.dev/packages/async_redux#user-exceptions

jans-y commented 5 years ago

Thank you for your guidance.

This library is evolving so rapidly that I have must missed the UserException.

It works ok, but I will probably end up building my own "UserException" class which will show Cupertino dialog on iOS and possibly a SnackBar.

marcglasberg commented 5 years ago

@jans-y That surely is a good idea of improving the UserExceptionDialog class. Please share your implementation here if you can. Thanks.

jans-y commented 4 years ago

Hi,

I forked it locally and did this:


    BuildContext context,
    UserException userException,
  ) {
    if (Platform.isIOS) {
      showCupertinoDialog(
        context: context,
        builder: (BuildContext context) => CupertinoAlertDialog(
          title: Text(userException.dialogTitle()),
          content: Text(userException.dialogContent()),
          actions: [
            CupertinoDialogAction(
              child: Text("OK"),
              onPressed: () => Navigator.of(context).pop(),
            )
          ],
        ),
      );
    } else {
      showDialog(
        context: context,
        builder: (BuildContext context) => AlertDialog(
          title: Text(userException.dialogTitle()),
          content: Text(userException.dialogContent()),
          actions: [
            FlatButton(
              child: Text("OK"),
              onPressed: () => Navigator.of(context).pop(),
            )
          ],
        ),
      );
    }
  }```
marcglasberg commented 4 years ago

@jans-y Added this to version 2.3.3.

jans-y commented 4 years ago

Hi,

Thank you for adding the code I proposed before. I have started to experiment with Flutter for web and found out that it has to be update to not break web compilation:

➕add import import 'package:flutter/foundation.dart';

Change condition 📱 if (Platform.isIOS) { ⬇️ if (!kIsWeb && (Platform.isMacOS || Platform.isIOS)) {

I would create PR but I never did it on GitHub on someone's others project.

Thank you.

marcglasberg commented 4 years ago

Done: version 2.4.3.