0

Easy dialog management in Flutter

 3 years ago
source link: https://dev.to/danielcardonarojas/easy-dialog-management-in-flutter-559i
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Easy dialog management in Flutter

Sep 3

・3 min read

Having worked with dialogs for a while I have encountered multiple scenearios where I wish I could control dialogs programatically through a controller, similar to how other standard widgets like textfields or scrollviews can be controlled.

It would be cool if we could toggle the visibility of a dialog or group of dialogs through some kind of dialog controller.

Having this would allow to define and manage dialogs in a centralized place and hook statemangement to perform the side effect of displaying dialogs.

Solution

Since we can't modify the API of standard widgets like Alert and creating subclasses for all types of alerts would be cumbersome.

The solution proposed here won't look identical to the API you would get with TextField where you can set a controller property on Widget itself.

We would like our solution to fulfill the following requirements:

  • Don't disable normal dialog interactions like closing from buttons.
  • Can return data from dialog through the controller or by popping with value
  • Can show and hide multiple times
  • Showing a new dialog will hide the previous

Brief overview

I'll just mention briefly the classes involved and the different scenearios where you could use them before diving deep into the explanation of how this works.

  • DialogController: Registers a single dialog and gives the ability to toggle its visibility.
  • StyledDialogController<Style>: Registers a multiple dialogs associated with some style type (typically an enum)
  • StyledDialogs: A mixin wrapping a StyledDialogController for use in statefullwidgets.

Note: Only one dialog can be shown at a time which is what you normally want

Usage

Suppose we have some status property in our statemangement that we will use to show different types of dialogs.

We might want to display a dialog if this property changes to some especific values.

enum Status { loading, error, success, iddle }
Enter fullscreen modeExit fullscreen mode

Lets start by creating a styled controller and registering some dialogs in a stateful widget page:

@override
void initState() {
  // ....

  styledDialogController = StyledDialogController<Status>();

  styledDialogController.registerDialogOf(
    style: Status.error,
    builder: showErrorDialog,
  );

  styledDialogController.registerDialogOf(
    style: Status.success,
    builder: showSuccessDialog,
  );

  styledDialogController.registerDialogOf(
    style: Status.loading,
    builder: showLoadingDialog,
  );

  //...
}
Enter fullscreen modeExit fullscreen mode

These builders are just functions that display a dialog:

Future<void> showLoadingDialog() {
  return showDialog(
    context: context,
    builder: (context) => AlertDialog(
        content: SizedBox(
      height: 32,
      child: Center(child: CircularProgressIndicator()),
    )),
  );
}
Enter fullscreen modeExit fullscreen mode

With this in place you can now present and hide dialogs with which ever mechanism you wish:

styledDialogController.showDialogWithStyle(Status.error);
Enter fullscreen modeExit fullscreen mode

or close the dialog with:

styledDialogController.closeVisibleDialog();
// or
styledDialogController.closeVisibleDialog('some return value');
Enter fullscreen modeExit fullscreen mode

For example hook up state management to show some of these dialogs automatically like this:

// Using mobx
void initState() {
  //...

  mobx.reaction<Status>((_) => _store.status, (newValue) {
    if (newValue == Status.iddle) {
      closeVisibleDialog();
    } else {
      showDialogWithStyle(newValue);
    }
  });

}
Enter fullscreen modeExit fullscreen mode

How it works

The main idea behind this is to use the normal future returned by showDialog from Flutter in combination with a hidden Completer in a coordinated manner.

DialogController

import 'dart:async';

typedef DialogShowHandler<T> = Future<T> Function();

class DialogController {
  late DialogShowHandler _showDialog;
  late Function _closeDialog;
  Completer? _dialogCompleter;

  /// Registers the showing and closing functions
  void configureDialog({
    required DialogShowHandler show,
    required Function close,
  }) {
    if (_dialogCompleter != null && !_dialogCompleter!.isCompleted) {
      _closeDialog();
    }
    _showDialog = show;
    _closeDialog = close;
  }

  Future<T?> show<T>() {
    final completer = Completer<T>();

    _dialogCompleter = completer;

    _showDialog().then((value) {
      if (completer.isCompleted) return;
      completer.complete(value);
    });

    return completer.future;
  }

  void close<T>([T? value]) {
    if (_dialogCompleter?.isCompleted ?? true) return;

    _dialogCompleter?.complete(value);
    _closeDialog();
    _dialogCompleter = null;
  }
}

Enter fullscreen modeExit fullscreen mode

StyledDialogController

StyledDialogController just wraps a DialogController and records different dialog showing functions for each style.

class StyledDialogController<S> {
  Map<String, DialogShowHandler> _styleBuilders = {};

  S? visibleDialogStyle;

  DialogController _dialogController = DialogController();

  void registerDialogOf(
      {required S style, required DialogShowHandler builder}) {
    final styleIdentifier = style.toString();
    _styleBuilders[styleIdentifier] = builder;
  }

  Future<T?> showDialogWithStyle<T>(
    S style, {
    required Function closingFunction,
  }) {
    visibleDialogStyle = style;
    final styleIdentifier = style.toString();
    final builder = _styleBuilders[styleIdentifier];

    if (builder == null) return Future.value(null);

    _dialogController.configureDialog(
      show: builder,
      close: closingFunction,
    );
    return _dialogController.show();
  }

  void closeVisibleDialog<T>([T? value]) {
    visibleDialogStyle = null;
    _dialogController.close(value);
  }
}
Enter fullscreen modeExit fullscreen mode

Some other features can be built upon this base setup like preregistering dialog styles on a specialized StyledDialogController and using it through out many pages, or having a base page type that already includes this ability among others.

Hope you liked this post. Let me know your thoughts on this or if you have other ideas improving this setup.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK