monstar-lab-oss / flutter-template Goto Github PK
View Code? Open in Web Editor NEWFlutter template using CLEAN architecture 🎯
Flutter template using CLEAN architecture 🎯
We've looked at:
Logger looks best and pretty extensible. The problem is initialization, but @nivisi had an idea of using mixins + configuration via a global function.
presentation/resources/colors.dart
Most of our dependencies are not up to date. We should update them to be around the latest version. For example, currently I cannot launch the watch
command with build_runner
due to outdated dependencies.
These are pretty common and we recreate them almost on every project. Would be cool to have them in-place and to design them after we create a project from the template.
The most common parameters would be:
String text
VoidCallback onPressed
bool isEnabled
bool isLoading
As most of the time we will use preconfigured button sizes, paddings etc, we could create a named constructor .custom
to be able to configure the following parameters:
double paddingVertical
double paddingHorizontal
Color backgroundColor
Color foregroundColor
TextStyle textStyle
And also a constructor for a custom child, if we ever need to use sth like an icon, e.g. withChild
So the interface is the following. Same for SecondaryButton
.
final defaultButton = PrimaryButton(
String text, {
VoidCallback? onPressed,
bool isEnabled = true,
bool isLoading = false,
}
);
final defaultButtonWithChild = PrimaryButton.withChild(
{
VoidCallback? onPressed,
bool isEnabled = true,
bool isLoading = false,
required Widget child,
}
);
final customButton = PrimaryButton.custom(
String text, {
VoidCallback? onPressed,
bool isEnabled = true,
bool isLoading = false,
TextStyle? textStyle,
double paddingVertical = 4.0, // or whatever
double paddingHorizontal = 12.0, // or whatever
Color? backgroundColor, // in the build method, we will be able to use this custom color
Color? foregroundColor, // in the build method, we will be able to use this custom color
}
);
final customButtonWithChild = PrimaryButton.customWithChild(
{
VoidCallback? onPressed,
bool isEnabled = true,
bool isLoading = false,
TextStyle? textStyle,
double paddingVertical = 4.0, // or whatever
double paddingHorizontal = 12.0, // or whatever
Color? backgroundColor, // in the build method, we will be able to use this custom color
Color? foregroundColor, // in the build method, we will be able to use this custom color
required Widget child,
}
);
This widget is just an encapsulation for the NotificationListener<ScrollNotification>
. It will expose a callback onLoadMore
that will be called when a user scrolls to nearly the bottom of the screen.
flutter_gen is used to generate classes for accessing resources easily, so we don't need to maintain things like AppIcons
or AppImages
. The generator will take care of it creating convenient interface for accessing assets like this:
Assets.icons.add.svg(),
Assets.images.background.image()
For new devs that are trying to run the template, the docs are not very straightforward.
For example, in this step:
Execute a run configuration of choice
$ flutter run --flavor staging -t lib/main_staging.dart
$ flutter run --flavor production -t lib/main_production.dart
It is suggesting to use a flavor, I'm assuming this is here because it's a template and will be used for an actual app that does have flavors, however the template app does not have any Android or iOS flavors and to run the app you have to use:
$ flutter run -t lib/main_staging.dart
$ flutter run -t lib/main_production.dart
Maybe we can add a disclaimer to remove the flavor if you're doing development on the template, or we can add the the flavors (Not sure if this is a good idea for a template).
Another thing is that code generation step is missing, I did figure this out on my own, however some junior devs might not, I suggest we add it.
Execute
$ flutter pub run build_runner build
to generate the required files
Also, code generation caused conflicts with some files for some reason, I had to run $ flutter pub run build_runner build --delete-conflicting-outputs
to re-generate those files.
Something worth mentioning:
The Flutter version to be used is not mentioned, I have Flutter v3.7.8
installed and faced an issue when first trying to run the code generation command, the first thing I usually do in such a case is to remove the pubspec.lock
file, run a $ flutter clean
command, sometimes I also delete some generated iOS and android files, and then run flutter pub get again.
This solved the issue for me, it would be a good idea to add a debugging section for juniors to avoid unnecessary questions.
Context:
Basically it would save us time and improve alignment to add some default vscode setup shared across the team.
We can start slim with less opinionated stuff, but lets kick off the discussion here.
Retrofit is a cool library for auto generating Dio clients. Definitely worth using.
The current template doesn't bring much value. It results in us skipping the checkmarks and no one reading the PR description, although it may contain some notes about the PR and the ticket.
To make the PR template more contextualized. We can put there the implementation details, steps to test and so on.
With this, we will make the life easier for the reviewers as they will be able to jump into the context of the task from the PR description.
I would also suggest to upload a quick video demo of what has been changed/implemented.
Potential candidates for this
Introduce/presentation/home
, /data
, /domain
directories and move classes accordingly.
main_common.dart
main_staging.dart
main_production.dart
/presentation/app.dart
/presentation/app_flavor.dart
! This should not include flavouring of iOS or Android sub projects ! This task is project specific.
we can drop the context based switching here as I don't see a need for having multiple variants of text styles just
static const headline1 = TextStyle(
fontWeight: FontWeight.w300,
fontSize: 34,
);
should do
If we create a call method in a class, the class becomes callable and we are able to use it like so:
class MyClass {
void call();
}
final myClass = MyClass();
myClass(); // <——
We could use it with the use cases:
_myUseCase.run(); // instead of this
_myUseCase(); // we will use this.
As we follow conventional commits, it would be nice to use this format for PR names as well.
fix/feat/chore/etc(TICKET-XXX): Name of the ticket
fix/feat/chore/etc: Changes description
To force that, we can introduce a GitHub action that validates it. I've something similar in my pet project where I validate PRs. But's a bit more complex and checks for ticket name and for the description.
Theme extensions is a cool way of managing colors, typography etc. We can still make it available via build context extensions like this:
final colors = context.colors;
final typography = context.typography;
Video: https://youtu.be/8-szcYzFVao
API: https://api.flutter.dev/flutter/material/ThemeExtension-class.html
Currently we only have the master
, but I think we should add all three branches that we normally might use in a project.
These would be:
production
staging
development
(as default)Anyone with write and admin access could change and add these branches 😄
This would also fit with our CI configurations
I think it would be good to have a yaml file so we can easily setup workflows
We need to create our own cubit and state bricks so we can reuse them across the apps
Problem: some buttons on the screen might not be implemented yet. QAs might expect that it is though and report that something is broken.
Solution: To avoid this, we can introduce a generic dialog that would tell that «This is not implemented yet». Then we will explicitly mark that something is not done ye.t
assets/fonts
assets/icons
assets/images
presentation/resources/fonts.dart
presentation/resources/icons.dart
presentation/resources/images.dart
presentation/resources/resources.dart
A widget that adds a preconfigured shimmering parent.
A widget for building the actual shapes, e.g.
/// Pre-configured widget for adding loading shapes to the screen.
class PlaceholderShape extends StatelessWidget {
const PlaceholderShape({
super.key,
this.width,
this.height,
this.borderRadius,
this.color,
});
const PlaceholderShape.square({
super.key,
double? size,
this.borderRadius,
this.color,
}) : width = size,
height = size;
/// A rectangular shape that has the size of the text with the given style.
PlaceholderShape.fromText({
super.key,
required String text,
required TextStyle textStyle,
this.borderRadius,
this.color,
}) : width = text.getSize(textStyle).width,
height = text.getSize(textStyle).height;
final double? width;
final double? height;
final double? borderRadius;
final Color? color;
@override
Widget build(BuildContext context) {
// TODO: Setup the default colour.
final defaultColor = Colors.white54;
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(borderRadius ?? .0)),
child: SizedBox(
width: width,
height: height,
child: ColoredBox(color: color ?? defaultColor),
),
);
}
}
So it can be used like this:
AppShimmer(
child: Column(
children: [
LoadingPlaceholderShape(...),
LoadingPlaceholderShape(...),
],
),
// or
AppShimmer.column(
children: [
LoadingPlaceholderShape(...),
LoadingPlaceholderShape(...),
],
),
// or
AppShimmer.row(
children: [
LoadingPlaceholderShape(...),
LoadingPlaceholderShape(...),
],
),
We can create a basement for the sign in / sign out system:
As we reimplement this system almost 100% of time, creating it in the template will allow us to deliver the sign in / sign out functionality on new projects very fast.
When creating a screen, sometimes we need to add a lot of top-level configuration widgets:
// screen.dart
@override
Widget build(BuildContext context) {
// 1
return FocusScopeDismissible(
// 2
child: BlocProvider<Bloc>(
create: (context) => injector(),
// Aand the actual UI comes at this point...
// 3
child: Scaffold(
// Aand the core part of it is here
body: Column(
children: const [],
),
),
),
);
}
At least 3 top-level things just to make the screen work normally. And it can be more.
The body of the screen could be moved to a separate widget:
// screen.dart
@override
Widget build(BuildContext context) {
return FocusScopeDismissible(
child: BlocProvider<Bloc>(
create: (context) => injector(),
child: Scaffold(
body: const ScreenBody(),
),
),
);
}
...
class ScreenBody extends StatelessWidget {
const ScreenBody({super.key});
@override
Widget build(BuildContext context) {
// The bloc's data could still be accessible via selectors and bloc builders
final stateData = context.select((Bloc bloc) => bloc.state.data);
return const Text("I'm the actual UI of the screen!");
}
}
We can write a simple script that will generate the files on command from our IDE. I have already written a very similar script for a VSCode extension for my package, it is here. It can be a create_feature
, not a module.
Vibration is quite a common thing for mobile apps. Triggered from pull to refresh, on tap on specific buttons like pin buttons, on error or whatsoever — it enhances UX for a user. There's a good article in the human interface guidelines about playing haptics.
On the native side we're getting this... Uhm natively with the native components, but on Flutter, we don't. It expands the list of things that make Flutter apps feel "off". But it's quite easy to exclude from the list.
There is a good package, flutter_vibrate. It supports playing custom vibration as well as using the predefined native vibrations, like error, success etc. I propose to add a class with static method that encapsulates usage of this package, and also to define guidelines on when we should add vibration and what exact vibration.
What I can think of at the moment:
Of course we should standardize it as much as possible so devs are not bothered with adding this themselves. Plus, it will guarantee that we're always covering vibration for the same components all across the app.
Size getSize(
TextStyle style, {
int maxLines = 1,
double minWidth = .0,
double maxWidth = double.infinity,
}) {
if (isNullOrBlank) {
return Size.zero;
}
final text = TextSpan(text: this, style: style);
final textPainter = TextPainter(
maxLines: maxLines,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
text: text,
);
textPainter.layout(minWidth: minWidth, maxWidth: maxWidth);
return textPainter.size;
}
The base use case classes like UseCase
, StreamUseCase
etc bring us only one benefit: they keep the use cases interface consistent. They force us to use the run
method.
But we never operate with use cases using their base classes, e.g. you will never see
final UseCase<int, int> _calculateSomethingUseCase = injector<CalculateSomethingUsecase>();
It will always be:
final CalculateSomethingUseCase _calculateSomethingUseCase = injector<CalculateSomethingUseCase>();
But by using the base class we are forced to always have a single parameter in the run
method. If we'd need two or more parameters, we would be forced to create an Input
class. This is:
// Bad
_myUseCase.run(MyInput(param1: 0, param2: 1));
// Good
_myUseCase.run(param1: 0, param2: 1);
Remove the inheritance from all the use cases. But then we will have to somehow ensure that all the use cases follow the same interface, having a public run
method.
We could use this GitHub action to validate our pull requests to have the same use case structure. You can find an example here, it has a valid PR and an invalid PR.
But it is less convenient than a linter rule that could highlight the error in our IDE right when we code, not when we submit a PR.
So... We can create a linter rule that would validate our use case classes. custom_lint could be used for this purpose — but it needs investigation.
SplashScreens are not needed via NStack anymore and is discouraged officially by Google (At least the manual timed screens).
We need to:
lib/nstack/nstack.json
The following should be added to .gitignore :
**/*.freezed.dart
And the following command then ran :
git rm -r --cached .
git add .
And the result committed and merged.
I have a local branch with these changes if you will permit me to open a PR and create a remote branch.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.