-
Notifications
You must be signed in to change notification settings - Fork 10
Architecture
Nikita Sirovskiy edited this page May 6, 2022
·
4 revisions
⚠️ This page is being written on the go along with the ticket #213
Component. | Library |
---|---|
State Management | controllable |
Dependency Injection | get_it + injectable |
Navigation | auto_route |
Localization | TBD |
The app is divided in three modules:
- Data — contains all the models used in the app;
- Domain — contains services interfaces, their implementations and use cases;
- Presentation — the UI, the platform specific implementations of services, all the controllers, localization etc.
Every screen is a so-called «feature». Home
— is a parent feature while App Picker
and Settings
are sub-features of Home
.
The file structure for a feature is the following:
feature_name
controller
/* feature controller files */
widgets
/* feature sub widgets */
feature_page.dart
feature_body.dart
feature_listener.dart // if needed
Page
widgets contains the higher configuration of a page: Provider
/ Scaffold
/ SafeArea
/ Listeners
/ Inherited
— it all goes there. The UI of the page itself goes to the Body
widget.
So a page is usually something like this:
return XProvider<MyController>(
create: (_) => injector(),
child: Builder(
// To access MyController from the listener.
builder: (context) {
return const Scaffold(
body: SafeArea(
child: MyControllerListener(
listenable: context.myController,
child: MyBody(),
);
},
},
),
);
Reasons:
- The page configuration is easily found in one file;
- Imagine the tree from the example below will be put above instead of just creating
MyBody
. That would create a terrible waterfall widget.
Body
widgets contain all the UI of the page.
❗️ One important requirement is to decompose widgets making them as small as possible. So instead of:
build:
return Column(
children: [
Text(
someText,
style: AppStyle.first,
),
TextButton(
onPressed: () {
/* Do something */
},
child: Text(
pressMeToDoSomething,
style: AppStyle.first,
),
),
Column(
// More waterfalls ...
),
],
);
We do this:
build:
return Column(
children: const [
FeatureTitleText(),
FeatureDoSomethingButton(),
FeatureMoreWaterfalls(),
],
);
Reasons:
- The tree is more readable: in the latter example you can clearly see what the tree contains, e.g. the title, the button and some waterfall widgets. In the example above you would need to get into the details like reading the text to understand what's there in the tree;
- Moreover, we exclude possible waterfalls from the tree because all the nesting goes to other classes;
- The tree is less complex on every level;
- To avoid unnecessary rebuilds (notice: the whole list is
const
!); - To keep the possible widget logic (like animation controllers etc) separately from other stateless things.
🌱 Leafy Launcher
- Join the Telegram chat, @leafy_launcher;
- Send us an email to [email protected].