A production app is defined by what happens when the network drops, a token expires, a payment returns late, or a user resumes an interrupted workflow—not by its screen count.
My process starts with the states the product must protect, then creates only the architectural boundaries needed to make those states testable and maintainable.
Start with workflows and state transitions
I map actors, records, and allowed transitions before selecting packages. An order may be draft, awaiting payment, accepted, preparing, assigned, delivered, cancelled, or refunded. Each transition needs an owner and a visible pending or failure state.
This exposes permissions, retries, notifications, and recovery work that a screen inventory hides.
- Who can create or change this record?
- Which transitions require a server or provider?
- Can an action be safely attempted twice?
- What should the user see while the result is uncertain?
Create boundaries where change is likely
I separate presentation, application state, domain decisions, and data access when each boundary removes a real coupling. A repository earns its place when it normalizes provider data, centralizes failures, or makes a workflow testable without widgets.
abstract interface class OrdersRepository {
Future<Result<Order>> create(OrderDraft draft);
Stream<Order> watch(String orderId);
}
sealed class Result<T> {}
final class Success<T> extends Result<T> {
Success(this.value);
final T value;
}
final class Failure<T> extends Result<T> {
Failure(this.message, {this.canRetry = true});
final String message;
final bool canRetry;
}Closing perspective
A strong Flutter codebase makes product states explicit, provider failures containable, and releases repeatable.