simply 2.0.4
simply: ^2.0.4 copied to clipboard
Flutter made simple, Simply is a light-weight package for creating production-ready applications easily and quickly.
Simply #
Motiviation #
Simply is built to help creating production-ready flutter applications faster and easier, it handles tpoics 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:
- Execute asynchrounous operations, like initialize database or log something to a remote API.
- Rebuild the whole app when the user changes the language or the theme.
- Retrieve values from environment file
.envasynchrounously - ...
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 overriden:
initialize(optional) this method handles two functionalities:- Inject all the dependencies that your UIs/views will need (to be explained below).
- Execute all the time-consuming functionalities that needs to be called at the start of your app like preparing database, or connecting to remote API.
buildAppthis corresponds to the normalbuildmethod where you can build your app with two main differences:- It's called each time the app is reloaded not just once (to be explained below)
- You can consume services from the
SimpleServiceProviderclass which are initialized in the
splashPagethis is a screen that the user can see while your time-consuming functionalities take place, you can useSimpleSplashPageif you don't want to implement custom splash page.startupErrorPagein case any initialization error takes place, this is what the user will see, you can always useSimpleStartupErrorPageif you don't want to implement custom page for that.
Dependency Injection #
Simply passes down the depenencies 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 functoinalities, 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());