Flutter 3.27 in Production: What Building Khotian Taught Me
Eighteen months shipping Flutter in production — from the Impeller renderer stabilising to Dart 3's sealed classes changing how I model state. Real lessons, not benchmarks.
Khotian is a personal finance app I’ve been building and maintaining for a few years now. It’s live on the Play Store, has real users, and has been through multiple Flutter major version upgrades. Here’s what I’ve learned shipping Flutter in production.
Impeller is finally stable
The renderer switch from Skia to Impeller was the most impactful invisible change in recent Flutter versions. The jank on first frame — that annoying pause on shader compilation — is gone on Android with Impeller enabled. I finally removed the “enable Impeller” opt-in flag from my Android manifests and made it default.
The result: smoother animations, more consistent 60fps performance on mid-range devices, and no more shader warmup hacks. If you’re still on Skia for Android, test Impeller on your target device class. The improvement is real.
Dart 3 sealed classes changed how I model state
Before Dart 3, managing UI state in Khotian used a mix of abstract classes and when() methods via freezed. It worked, but the boilerplate was heavy and adding a new state variant required touching too many files.
With sealed classes, this became both cleaner and safer:
sealed class TransactionState {}
final class TransactionLoading extends TransactionState {}
final class TransactionLoaded extends TransactionState {
final List<Transaction> transactions;
TransactionLoaded(this.transactions);
}
final class TransactionError extends TransactionState {
final String message;
TransactionError(this.message);
}
The exhaustive switch in the UI layer is now enforced by the compiler:
return switch (state) {
TransactionLoading() => const LoadingIndicator(),
TransactionLoaded(:final transactions) => TransactionList(transactions),
TransactionError(:final message) => ErrorView(message),
};
If I add a new variant to TransactionState and forget to handle it in a switch, the build fails. That’s the kind of compiler-as-collaborator experience I want.
Records are underrated
Dart 3 records don’t get as much attention as pattern matching, but I use them constantly now. Returning multiple values from a function without creating a dedicated class:
(double total, int count) aggregateTransactions(List<Transaction> txns) {
return (
txns.fold(0.0, (sum, t) => sum + t.amount),
txns.length,
);
}
final (total, count) = aggregateTransactions(transactions);
Clean, typed, no boilerplate.
What I’d tell someone starting a Flutter project in 2025
- Enable Impeller on Android from day one. Don’t plan to migrate later.
- Use sealed classes for all your bloc/cubit states. The exhaustiveness check pays dividends.
- Budget for device testing. Flutter performance is hardware-dependent in ways that desktop previews hide.
- Don’t fear the widget tree. Extract to methods first, extract to classes when the state requires it.
Flutter’s maturity curve has been real. The gaps that existed in 2021 — font rendering, platform fidelity, tooling — have largely closed. It’s now a credible first choice for teams that want one codebase without sacrificing quality.