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 ๐คฉ
Every week I dive headfirst into a topic, uncovering every hidden nook and shadow, to deliver you the most interesting insights
Not convinced? Well, let me tell you more about it
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 ๐