A powerful state machine for MobX management, which can be used in almost any application state.

It has 3 states - loading, success, error - and is perfect to be used on infinite lists, action buttons, with shimmers, refresh logic, etc.

Requirements

This package just works with the MobX. And minimum Dart SDK 2.16.

Getting started

Basically you can switch between the three states to control your data.

On data loading, call:

dataState.setLoadingState();

On get data successfully:

dataState.setSuccessState(data);

On get an error:

dataState.setErrorState(error);

And the DataState will handle the rest.

To deal with the widgets on each state, you can listen the state - or the computed getters isLoading, isSuccess, isError - at your Observer widget, for example:

Observer(
  builder: (_) => ElevatedButton(
    child: controller.loginState.isLoading
        ? const CircularProgressIndicator()
        : const Text('Button'),
    onPressed: () {},
  ),
)

Or you can use the features below.

Features

Simple states

To deal with simple logics, like showing a list of strings.

Crate the DataState on the mobx controller file:

final dataState = DataState<List<String>>();

Future<void> fetchData() async {
  dataState.setLoadingState();

  try {
    final data = await Future.delayed(const Duration(seconds: 3))
        .then((_) => ['String 1', 'String 2', 'String 3']);
    dataState.setSuccessState(data);
  } catch (e) {
    dataState.setErrorState(e);
  }
}

Then add on your view the Observer to present the list:

final controller = ControllerInstance();

@override
void initState() {
  fetchData();
  //...
  super.initState();
}

@override
Widget build(context) {
  //...
  Observer(
    builder: (context) => controller.dataState.handleState(
      loading: () {
        return const CircularProgressIndicator();
      },
      success: (data) {
        return ListView.builder(
          shrinkWrap: true,
          itemCount: data.length,
          itemBuilder: (context, index) => Text(
            data[index],
            textAlign: TextAlign.center,
          ),
        );
      },
      error: (error) {
        return const Text('Error');
      },
    ),
  ),
}

See the full code here.

Reloadable states

Use the handleStateLoadableWithData method when you want to use data on the loading widget callback. On an infinite list or refresh logic for example.

Observer(
  builder: (context) => controller.dataState.handleStateLoadableWithData(
    loading: (data) {
      return Stack(
        children: [
          if (data != null) list(data),
          Positioned.fill(
            child: Container(
              color: Colors.black45,
              alignment: Alignment.center,
              child: const CircularProgressIndicator()),
          ),
        ],
      );
    },
    success: (data) => list(data),
    error: (error) {
      return const Text('Error');
    },
  ),
);

See the full example code here.

States using reaction

To handle states only once after it changes, the handleReactionState is the solution. Just set it on the initState and remember to dispose it on the dispose method.

For example, to show a full dialog:

List<ReactionDisposer>? reactionsDisposers;

@override
void initState() {
  reactionsDisposers = [
    controller.dataState.handleReactionState(
      loading: loadingDialog,
    )
  ];
  super.initState();
}

@override
void dispose() {
  reactionsDisposers?.forEach((dispose) {
    dispose();
  });
  super.dispose();
}

void loadingDialog(bool show) {
  if (show) {
    showDialog(
      barrierDismissible: false,
      context: context,
      builder: (BuildContext context) {
        return Dialog(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                CircularProgressIndicator(),
                SizedBox(width: 32),
                Text("Loading"),
              ],
            ),
          ),
        );
      },
    );
  } else {
    Navigator.of(context).pop();
  }
}

See the full example code here.

Libraries

data_state