Simply: Flutter made simple !
Abdelrahman Shehata ????????
Senior Software Engineer @Careem (e&Uber) | Opinions are my own and not reflective of my current employer
Motivation
Simply is built to help creating production-ready flutter applications faster and easier, it handles topics such as dependency injection as well as state management and let you focus on creating beautiful UIs (the main purpose of flutter).
Normal flutter application could look like the following:
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
//All code goes here!
}
}
But this leaves us with too many logic to handle inside the app class specially as the code grows more and more, for example:
Eventually this will lead to a mess inside that?MyApp?class handling both UI logic and initialization logic, what Simply does is that it helps you organize your app better.
Getting started
All what you need to start is to use the?SimpleMaterialApp?class which is nothing more than a?StatelessWidget?that helps organizing your app.
The following is the simplest working app:
import 'package:simply/simply.dart';
void main() {
runApp(MyApp());
}
class MyApp extends SimpleMaterialApp {
@override
Future<void> initialize(SimpleServiceRegistry registery) async {}
@override
MaterialApp buildApp(SimpleServiceProvider provider, String payload)
=> MaterialApp(body: SomePage());
@override
Widget splashPage() => SimpleSplashPage();
@override
Widget startupErrorPage(String errorMessage) => SimpleStartupErrorPage(errorMessage);
}
There are 4 main methods to be overridden:
1. initialize?(optional) this method handles two functionalities:
2. buildApp?this corresponds to the normal?build?method where you can build your app with two main differences:
3. splashPage?this is a screen that the user can see while your time-consuming functionalities take place, you can use?SimpleSplashPage?if you don't want to implement custom splash page.
4. startupErrorPage?in case any initialization error takes place, this is what the user will see, you can always use?SimpleStartupErrorPage?if you don't want to implement custom page for that.
Dependency Injection
Simply passes down the dependencies along the app widget tree, however it's not directly coupled to a specific package to achieve this so it just hides the details from the consumers.
Assume we have a dependency on a certain repository responsible for fetching data from some storage, let's call it?IMenuItemsRepository, we just need it to extend the?ISimpleService?class to be able to inject it to distinguish the services from other types.
领英推荐
abstract class IMenuItemsRepository extends ISimpleService {
Future<List<String>> getMenuItemNames();
}
All what you have to do is to inject the concrete implementation of this dependency from the app level, let's say you have a class called?LocalMenuItemsRepository
class MyApp extends SimpleMaterialApp {
@override
Future<void> initialize(SimpleServiceRegistry registery) async {
registery.register<IMenuItemsRepository>(
service: LocalMenuItemsRepository(),
);
}
// ...
}
Then use the?SimpleServiceProvider?class to get the dependency from anywhere in the UI (it will be passed to the whole tree).
@override
Widget build(BuildContext context) {
var menuItemsRepo = SimpleServiceProvider.of<IMenuItemsRepository>(context);
// Use the dependency ..
}
This way you will keep your UI logic clean and decoupled from any implementation details.
Let's say later on you want to change the concrete implementation to another class called?RemoteMenuItemsRepository?all what you need to do is:
class MyApp extends SimpleMaterialApp {
@override
Future<void> initialize(SimpleServiceRegistry registery) async {
registery.register<IMenuItemsRepository>(
service: RemoteMenuItemsRepository(),
);
}
}
Or maybe you want to inject different dependencies based on the platform then you would do something like this:
class MyApp extends SimpleMaterialApp {
@override
Future<void> initialize(SimpleServiceRegistry registery) async {
if(platform == platform.android){
registery.register<IMenuItemsRepository>(
service: LocalMenuItemsRepository(),
);
}else{
registery.register<IMenuItemsRepository>(
service: RemoteMenuItemsRepository(),
);
}
}
}
And if you want to call time-consuming functionalities, you can do it within the same method, while users enjoy your SplashPage
class MyApp extends SimpleMaterialApp {
@override
Future<void> initialize(SimpleServiceRegistry registery) async {
await Firebase.initialize();
await SharedPrefernces.initialize();
await prepareLocalDatabase();
// ...
}
}
Reloading the app
You might want to reload the app for a global event like changing the language or the theme, you can do this easily from anywhere in the code as follows:
SimpleAppReloader.of(context).reload("Theme changed");
This will cause the whole app to be rebuilt and the method?buildApp?will be called with the parameter?payload?carrying whatever message you sent and also with passing the same service providers that you have previously registered.
class MyApp extends SimpleMaterialApp {
@override
MaterialApp buildApp(SimpleServiceProvider provider, String payload) {
IThemeService themeService = provider.getService<IThemeService>();
return CustomMaterialApp(
widget: const HomePage(),
theme: themeService.currentTheme,
);
}
}
Navigation
Simply provides navigation methods similar to the native one just to make sure that whatever dependencies are passed properly while navigation.
SimpleNavigator.of(context).push(view: TargetPage());