ExotikArch – Flutter setState on steriods ⚡
ExotikArch for Flutter. Deliver production apps fast with flutter.
- Global State Management
- Navigation Service
- Helper Widgets, i.e. loaders and processors.
- Seamless Exception Handling with zero code repeats
- Support for Isolates to offload API calls.
*Note
ExotikArch exists in form of this app project at the moment. If this project gathers enough attention then I’ll make a package out of it and publish that over to pubdev.
Run it
flutter run
Modules
State Managment
ExotikArch uses the built-in setState function to cater for the global state managment. It is designed to work well with MVC pattern. We make controllers to handle the business logic. To construct a simple controller just extend it with ExoController
import 'package:exotik_todos_app/exotik_arch/exotik_arch.dart';
class TodosController extends ExoController<TodosRetainer> {
void Function(void Function() fn)? setState;
TodosRetainer? retainer;
TodosController(
this.setState,
{
TodosRetainer?initRetainer
}
) : super(
setState,
initRetainer: initRetainer ?? TodosRetainer()
);
void dispose() async {
super.dispose();
}
}
The ExoController
takes in a generic type which will serve as a model for controller data. For instance in case of this TodosController
, this TodosRetainers
reference is defined as,
class TodosRetainer {
late List<Todo> todosList;
TodosRetainer(){
todosList = [];
}
}
Its a convention to keep retainer references in the same file as the controllers. To call the controller from the UI use this syntax,
late TodosController todosController;
@override
void initState() {
todosController = TodosController(
this.setState,
initRetainer: anaGlobalRetainers.todosRetainer
);
super.initState();
}
Note that there is a initRetainer being passed to TodosContoller
constructor. That’s how the global state is supported. All the global retainers are put in a AnaGlobalRetinaters
reference and passed to a controller on demand. If a initRetainer
is passed then controller is initialized with existing state, hence the global state effect.
Navigation Service
Navigate to anywhere from anywhere. Just use,
import 'package:exotik_todos_app/exotik_arch/exotik_arch.dart';
ExoNavService.push(
MainPage()
);
Helper Widgets
ExotikArch ships with 2 main helper widgets. These are constructed on a very simple idea. All most 90% of apps do only 2 things. They load and display data. Or they submit data to server and wait for response. ExotikArch treats them as data loading and process handling respectively. To load the data there is a helper widget called ExoDataLoader
. It takes in a reference of ExoControler
. And whenever the controller is in loading state it renders a spinner automatically and then stops once data has been rendered.
We expose the logic in controllers, and UI is updated automatically.
Future<void> getTodos({
bool reload = true
}) async {
try {
if(!reload){
if(retainer!.todosList.isNotEmpty){
return;
}
}
List<Todo> _todosList;
exoDataLoaderHelper.startLoading(); // Start Data Loader
_todosList = await apiInterface.getTodos();
exoDataLoaderHelper.stopLoading(); // Stop Data Loader
if(_todosList != null){
retainer!.todosList = _todosList;
}
} on ExoException catch (e, s){
exoDataLoaderHelper.showError(e); // Handle Error Messages
return null;
}
return;
}
Same goes for processes. There is ExoProcessHandler
, which will display a overlay spinner and stops it once the process has been finished.
Future<bool> login(
String email,
String password,
{
bool rememberMe: true
}
) async {
try{
AppUser? _appUser;
exoProcessHandlerHelper.startLoading(); // Start Process Loader
_appUser = await apiInterface.logIn(
email,
password,
);
exoProcessHandlerHelper.stopLoading(); // Stop Process Loader
appUser = _appUser;
if(appUser != null){
if(rememberMe){
appUser?.cache();
}
return true;
} else {
return false;
}
} on ExoException catch (e, s){
exoProcessHandlerHelper.showError(e); // Handle Error Messages
return false;
}
}
And ExoDataLoader
, ExoProcessHandler
are called just like regular widgets,
@override
Widget build(BuildContext context) {
return ExoProcessHandler(
exoProcessHandlerHelper: todosController.exoProcessHandlerHelper,
child: Container(),
);
}
@override
Widget build(BuildContext context) {
return ExoProcessHandler(
exoProcessHandlerHelper: authController.exoProcessHandlerHelper,
child: Container()
);
}
Exception Handling
ExotikArch has a built-in mechanism to handle all the exceptions internally. This works v.i.a contollers and helper widgets. All the exceptions are parsed to one single class ExoException
which has a friendlyErrorMessage
field in it. This message is automatically displayed when something goes wrong on ExoProcessHandler
or ExoDataLoader
widgets. Flash messages are constructed and prompted to user.
Work offload – Support for Isolates
Isolates support is very basic at the moment. We use build-in compute()
function for that. Which will spawn the isolates, make a network request in it and then despawn it. The implementation for that is found is api_interface.dart
file.
ComputeRequest computeRequest = ComputeRequest(
requestURL,
RequestType.POST,
body: body,
);
CDioResponse<Map<String, dynamic>> response = await compute<ComputeRequest, CDioResponse<Map<String, dynamic>>>(
cRequest,
computeRequest
); // Execute the network request With Isolate
CDioResponse<Map<String, dynamic>> response = await cRequest(
computeRequest
); // Execute the Network Request WithOut Isolate
Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.