40
Building scalable Flutter apps (Architecture, Styling, Conventions, State management)
(widgets|utils|pages|stores|models|..etc)
into features. We don’t need to think about how the small parts work together but how features work together to construct the app. By analyzing dependencies between features the app could auto-generate understandable diagrams for developers to learn or review the project.Infrastructure features: contains all the features that are responsible for implementing the business logic of the application (e.g: auth, http, config, user, articles, events, schools, …etc.)
App features: contains all the features that are responsible for implementing the presentation of the application (e.g: auth, home, settings, user, articles, events, schools, …etc.)
Notice that auth, user, events, articles, …etc. features can be both infrastructure and app features, so what is the difference? that’s what we will discuss in the next section (Features anatomy).

Note: An app feature may consume multiple Infrastructure features

file_name.type.dart
Create additional type names if you must but take care not to create too many.
file_name.widget.dart
file_name.style.dart
file_name.model.dart
file_name.util.dart
ViewModel
sends notifications to the view
to update the UI whenever state changes.Model
and the View
. It’s responsible for transforming the data from the Model
, it also holds the events of the View

MVVM
whenever your widget has its own events that can mutate the state directly e.g: pages, posts, ..etc.View
can't access the Model
directlyView
is devoid of any application logicViewModel
can have more than one View
.ViewModel
.class MyViewModel extends ViewModel {
int counter = 0;
// Optional
void init() {
// It's called after the ViewModel is constructed
}
// Optional
void onBuild() {
// It's called everytime the view is rebuilt
}
void increase() {
counter++;
notifyListeners();
}
}
context
inside the ViewModel
directlyclass MyViewModel extends ViewModel {
void init() {
var height = MediaQuery.of(context).size.height;
}
}
MVVM
inside your widget.class MyWidget extends StatelessWidget {
const MyWidget({Key key}) : super(key: key);
Widget build(BuildContext context) {
return MVVM<MyViewModel>(
view: (context, vmodel) => _MyView(),
viewModel: MyViewModel(),
);
}
}
View
.class _MyView extends StatelessView<MyViewModel> {
/// Set [reactive] to [false] if you don't want the view to listen to the ViewModel.
/// It's [true] by default.
const _MyView({Key key}) : super(key: key, reactive: true);
Widget render(context, vmodel) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(vmodel.counter.toString()),
SizedBox(height: 24),
RaisedButton(onPressed: vmodel.increase, child: Text('Increase')),
],
);
}
For more details, head to the package documentation
pmvvm
works perfectly especially if your app runs on multiple platforms. All you need is to create a single view model that controls all these views:
app-wide
colors, fonts, themes, and animations as an app feature called styles
. This approach will make all the widgets in the application consume the styles from a single source.colors.style.dart
abstract class CColors {
static const white0 = Color(0xffffffff);
static const black100 = Color(0xff000000);
static const blue10 = Color(0xffedf5ff);
static const blue20 = Color(0xffd0e2ff);
static const blue30 = Color(0xffa6c8ff);
}
text.style.dart
abstract class CFonts {
static const primaryRegular = 'IBMPlexSans-Regular';
static const primaryLight = 'IBMPlexSans-Light';
static const primaryMedium = 'IBMPlexSans-Medium';
static const primarySemibold = 'IBMPlexSans-SemiBold';
static const primaryBold = 'IBMPlexSans-Bold';
}
More examples can be found Here
tile.style.dart
abstract class TileStyle {
static const Map<String, dynamic> layouts = {
'tile-padding': const EdgeInsets.all(16),
};
static const Map<String, Color> colors = {
'tile-enabled-background-color': CColors.gray90,
'tile-enabled-label-color': CColors.gray30,
'tile-enabled-title-color': CColors.gray10,
'tile-enabled-description-color': CColors.gray30,
//
'tile-disabled-background-color': CColors.gray90,
'tile-disabled-label-color': CColors.gray70,
'tile-disabled-title-color': CColors.gray70,
'tile-disabled-description-color': CColors.gray70,
};
}
tile.widget.dart
class CTile extends StatelessWidget {
const CTile({
Key? key,
this.enable = true,
...
}) : super(key: key);
final bool enable;
final _colors = CTileStyle.colors;
final _layouts = CTileStyle.layouts;
Widget build(BuildContext context) {
/// styles helpers
String cwidget = 'tile';
String state = enable ? 'enabled' : 'disabled';
return IgnorePointer(
ignoring: !enable,
child: Container(
color: _colors['$cwidget-$state-background-color'],
padding: _layouts['$cwidget-padding'],
child: ....,
),
);
}
}
More examples can be found Here