How is like to build a mobile app in 2024? ๐ค
This week I implemented a complete mobile app with Flutter using all the latest packages and features ๐ก
Check daily activities @FlutterDev app โ ๐ฆ Local sqlite database using `drift` โก๏ธ State management using `signals` ๐งฌ Functional API using `fpdart`
This is how cross-platform mobile app development looks like ๐๐
Tech stack
- Flutter: still the best choice for cross-platform app development, large community, continuous improvements
- Dart: the engine behind Flutter, Dart keeps getting better as well, and the planned new features have the potential to take it to the next level ๐
Setup
What I like about Flutter is how easy is to install and configure. This is still true today ๐๐ผโโ๏ธ
- Flawless integration with VSCode and continuous updates and bug fixes
- Simple and clear CLI (
flutter
anddart
commands) - Complete devtool for easy debugging and testing
Did you know you can create a flutter project only for a specific platform? ๐ก Use --platform when running flutter create Useful to avoid having folders and files for all platforms when unnecessary ๐๐ผโโ๏ธ
Get started
Goal for this project: use all the newest packages and test them out ๐ก
Flutter app in 2024 ๐ ๐งฌ fpdart ๐๏ธ go_router (with go_router_builder) ๐ฅถ freezed (with json_serializable) ๐ฆ drift (sqlite3) ๐ get_it โก๏ธ signals ๐งฑ fast_immutable_collections
The building blocks of a Flutter app are:
- Routing: define and navigate between screens
- Data models: from plain dart
class
to Records,sealed
,typedef
and more - State management: keep data and widgets in sync
- API: business logic, http, storage, the backbone of the app
This is how I put all of these pieces together to make a complete app ๐
There is more ๐คฉ
Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.
Implementation
Routing first. There are countless of solutions for routing in Flutter, this time I went with go_router
.
My objective is to have type-safe navigation, I used
go_router_builder
to generate type-safe routes ๐ ๏ธ
part 'router.g.dart';
@TypedGoRoute<HomeRoute>(path: '/home')
class HomeRoute extends GoRouteData {
const HomeRoute();
@override
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}
@TypedGoRoute<TrackingRoute>(path: '/')
class TrackingRoute extends GoRouteData {
const TrackingRoute();
@override
Widget build(BuildContext context, GoRouterState state) => const TrackingScreen();
}
Second, data models. Since Dart 3 there are many features to structure data:
- Plain class
- Records
sealed
classesenum
typedef
enum Emoji {
smile,
rocket,
dart;
@override
String toString() => switch (this) {
Emoji.smile => "๐",
Emoji.rocket => "๐",
Emoji.dart => "๐ฏ",
};
}
typedef Day = ({int day, int month, int year});
class EventModel extends Equatable {
final int id;
final int activityId;
final Day createdAt;
const EventModel({
required this.id,
required this.activityId,
required this.createdAt,
});
@override
List<Object?> get props => [id, activityId, createdAt];
}
API: SQLite and Functional Programming
Countless of options also to implement the business logic of the app ๐ค
This time I went with drift
and sqlite3
for storage:
- Model database with SQL and relational tables
- Map tables to data models
- Reactive changes on insert, delete, and update โก๏ธ
@DriftDatabase(tables: [Activity, Event])
class Database extends _$Database {
Database() : super(_openConnection());
@override
int get schemaVersion => 1;
}
I then use functional programming with fpdart
to connect widgets and database (type-safe API ๐ช):
ReaderTaskEither<Database, ApiError, int> addActivity({
required String name,
required Emoji emoji,
}) =>
ReaderTaskEither.Do(
(_) async {
final db = await _(ReaderTaskEither.ask());
return _(
ReaderTaskEither.fromTaskEither(
db.query(
() => db.into(db.activity).insert(
ActivityCompanion.insert(
name: name,
emoji: emoji,
),
),
),
),
);
},
);
State management: signals
๐
New entry for state management: signals
New state management in @FlutterDev: signals โก๏ธ I used signals to implement a complete Flutter app ๐ฒ This is what I learned, and how I am using it ๐๐งต
signals
is easy to implement, less code, many features. Give it a try ๐ค
Complete app
The last step is connecting all together:
- Define a new route
- Define a new data model
- Define a table in the database to store the data
- Create the API to read, update, delete data
- Get the data from the API and display widgets based on the current state
- Update the state on each user interaction
Future<void> onAdd(BuildContext context) =>
addEvent(activityId: activityModel.id, day: day).match<void>(
(apiError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Error while adding new event for ${activityModel.name}: $apiError",
),
),
);
},
(id) {
print(id); /// Success โ
},
).run(getIt.get<Database>());
I wrote a complete step-by-step article on how to build a Flutter app using all the latest packages.
This article covers all the details on how to go from zero to a complete Flutter app ๐
Takeaways
- Flutter is still the best solution from cross-platform app development
- Dart is good and it's getting better, give it a try
- The Flutter ecosystem is full of great packages for most usecases
- It easy to get started and go from zero to a complete cross-platform app
You can read the full code on the open source repository ๐
Some interesting updates and new projects coming in the next weeks (the Effect Days are approaching ๐)
See you next ๐