Live coding and code review scenarios
In the live coding round the goal is not working code but thinking out loud. A structure that works:
1. Clarify the requirement (30 seconds): "Should this screen work offline? Should previous data stay visible while loading? Is there pagination?"
2. Write the model first: the state class and its possible states. That signals you know how to model state.
3. The UI second: map state to UI with switch/pattern matching.
4. Don't forget error and empty states — most candidates write only the happy path; it is the fastest visible difference.
5. At least verbalise the test: "My first test for this notifier would be the empty-list case."
6. Say the trade-off out loud: "I chose an optimistic update here because the action is reversible."
// 1) Model: mümkün olmayan vəziyyətlər ifadə edilə bilməz.
// AsyncValue loading/error/data-nı özü daşıyır, ona görə əlavə model lazım deyil.
// 2) State qatı.
class Articles extends AsyncNotifier<List<Article>> {
@override
Future<List<Article>> build() {
return ref.watch(articleRepositoryProvider).fetchAll();
}
Future<void> refresh() async {
state = const AsyncLoading<List<Article>>().copyWithPrevious(state);
state = await AsyncValue.guard(
() => ref.read(articleRepositoryProvider).fetchAll(),
);
}
}
final articlesProvider =
AsyncNotifierProvider<Articles, List<Article>>(Articles.new);
// 3) UI: dörd hal — data, boş, xəta, ilk yükləmə.
class ArticlesPage extends ConsumerWidget {
const ArticlesPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final articles = ref.watch(articlesProvider);
return Scaffold(
body: RefreshIndicator(
onRefresh: () => ref.refresh(articlesProvider.future),
child: switch (articles) {
AsyncValue(:final value?) when value.isEmpty =>
const EmptyView(message: 'Hələ məqalə yoxdur'),
AsyncValue(:final value?) => ArticleListView(items: value),
AsyncValue(:final error?) => ErrorView(
message: '$error',
onRetry: () => ref.invalidate(articlesProvider),
),
_ => const ArticleSkeleton(),
},
),
);
}
}A typical task: a list with loading, error and retry. The Riverpod version.
enum ArticlesStatus { initial, loading, success, failure }
final class ArticlesState extends Equatable {
const ArticlesState({
this.status = ArticlesStatus.initial,
this.items = const [],
this.error,
});
final ArticlesStatus status;
final List<Article> items;
final String? error;
ArticlesState copyWith({
ArticlesStatus? status,
List<Article>? items,
String? error,
}) => ArticlesState(
status: status ?? this.status,
items: items ?? this.items,
error: error,
);
@override
List<Object?> get props => [status, items, error];
}
sealed class ArticlesEvent {}
final class ArticlesStarted extends ArticlesEvent {}
final class ArticlesRefreshRequested extends ArticlesEvent {}
class ArticlesBloc extends Bloc<ArticlesEvent, ArticlesState> {
ArticlesBloc(this._repository) : super(const ArticlesState()) {
on<ArticlesStarted>(_onLoad);
// Təkrar sorğuların qarşısını alır.
on<ArticlesRefreshRequested>(_onLoad, transformer: droppable());
}
final ArticleRepository _repository;
Future<void> _onLoad(ArticlesEvent event, Emitter<ArticlesState> emit) async {
emit(state.copyWith(status: ArticlesStatus.loading, error: null));
try {
final items = await _repository.fetchAll();
emit(state.copyWith(status: ArticlesStatus.success, items: items));
} catch (error) {
// Köhnə items saxlanılır: ekran boşalmır.
emit(state.copyWith(status: ArticlesStatus.failure, error: '$error'));
}
}
}The same task in BLoC — with a status enum.
| Code review red flag | Why it is a problem |
|---|---|
| `Navigator`, `showDialog` or `showSnackBar` inside `build` | When the rebuild repeats, so does the effect |
| Reading a value in `build` with `read`/`context.read<T>().state` | No subscription — the UI keeps a stale value (the docs call it error prone) |
| Mutating a state collection in place (`items.add(...)`) | `==` sees no difference: bloc swallows the change and `select` never fires |
| A new field missing from `Equatable`'s `props` | Two different states compare equal — a silent bug |
| Creating a notifier/provider inside `build` | A new object per rebuild: state is lost and the old one leaks |
| Using `context` after an `await` without a `mounted` check | A crash on a disposed widget; the lint rule catches it |
| A global provider for ephemeral state (a form, scroll) | Wide rebuilds and state leaking across routes; the Riverpod docs warn against it |
| No transformer specified in a critical flow | Two taps → two orders/payments |
The most commonly lost points in live coding come from not writing the error and empty states. The difference between a candidate who writes only the happy path and one who covers four cases (first load, empty, error, refresh) is judged less as code quality than as product thinking. One sentence is enough: "We also need an empty list and an error screen — I'm adding those."
📚 Sources and documentation
- Flutter architecture: case studyofficialdocs.flutter.dev
How the layers look in real code — a good model for live coding.
- Riverpod: pull-to-refreshofficialriverpod.dev
- Bloc: modeling stateofficialbloclibrary.dev
- Flutter: testing overviewofficialdocs.flutter.dev