A state machine for MobX management, that can be used in almost any application state
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 list, action buttons, with shimmers, refresh logic, etc.
Features
Requirements
This package just work with the MobX. And minimum Dart SDK 2.16.
Usage
Simple states
To deal with simple logics, like showing a list of string.
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 wants 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 soluction. Just set on the initState
and remimber to dispose it on 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.