20 min. • Feb 9, 2024
In a recent project, we embarked on the exciting task of enhancing an existing app with dynamic social media elements. Choosing Stream as our ally, we navigated through challenges and came up with strategies that helped us overcome them, resulting in the successful delivery of a news feed feature. Read our complex article.
12 min • Jul 27, 2023
Read about the LeanCode approach to Flutter architecture. We highlight some of the design decisions that should be made when developing a feature in mobile apps. This includes our approach to dependency injection, state management, widget lifecycle, and data fetching.
10 min • Oct 27, 2025
In this article, we’re sharing LeanCode’s 12 practical Flutter and Dart patterns that help you write less boilerplate, make your code cleaner, and catch mistakes earlier. Apply these patterns and you'll find yourself coding faster, communicating more clearly with your teammates, and spending less time debugging issues that the compiler could have caught.
Asynchronous programming in Flutter app development allows the app to perform time-consuming operations (network calls, disk access) without blocking the UI.
It does not mean running code on a separate thread by default.
Flutter is single-threaded, and this distinction is critical to understand.
Flutter renders at 60–120 FPS.
If the main thread is blocked for more than a few milliseconds, users see:
Correct async usage is essential for performance.
Flutter uses Dart's event loop with:
Futureasync / awaitStreamWhen you use await, the function pauses only while waiting for I/O (like HTTP or database calls).
The UI thread stays free to render frames.
Future<User> loadUser() async {
final data = await api.fetchUser();
return User.fromJson(data);
}This is the most common misunderstanding.
async / await does not create a new threadThat's why this code can freeze the app, even with async:
await heavyJsonParsing(); // CPU work → UI jankasync / awaitcompute, Isolate.run)Future:
Stream:
Streams must be managed carefully, especially their lifecycle.
compute or Isolate.run for expensive tasks.try-catch.FutureBuilder / StreamBuilder only for UI.Avoid async for:
onPressed: () async {
await login();
Navigator.pop(context); // crash risk
}If the widget was disposed while waiting, this will crash.
if (!context.mounted) return;Always check after await before using context.
If you call .listen() manually, you must cancel it in dispose():
subscription.cancel();Otherwise, you create memory leaks.
This is slow:
final user = await getUser();
final posts = await getPosts();This is faster:
final results = await Future.wait([
getUser(),
getPosts(),
]);Independent tasks should run in parallel.