State modelləşdirmə: status enum vs sealed class
Rəsmi bloc sənədi state modelləşdirməyə ayrı bölmə həsr edir və iki yanaşma göstərir; hər ikisi tövsiyədir, məcburiyyət deyil.
1. Tək konkret sinif + status enum. Bütün vəziyyətlər üçün bir sinif; enum cari statusu göstərir; sahələr nullable olur və statusa görə şərh edilir. Sənədin qeydi: bu yanaşma vəziyyətlər bir-birini tam istisna etmədikdə və çoxlu paylaşılan sahə olduqda daha yaxşı işləyir.
2. Sealed sinif + alt siniflər. Hər vəziyyət ayrı tipdir. Vəziyyətlər bir-birini istisna edirsə uyğundur; Dart 3-ün pattern matching-i ilə kompilyator bütün halların əhatə olunduğunu yoxlayır.
Sənəd hər iki halda praktik əlavələri də sadalayır: package:equatable-dan Equatable genişləndirmək, @immutable annotasiyası, copyWith metodu və mümkün olduqda const konstruktor.
enum TodoStatus { initial, loading, success, failure }
final class TodoState extends Equatable {
const TodoState({
this.status = TodoStatus.initial,
this.todos = const [],
this.exception,
});
final TodoStatus status;
final List<Todo> todos;
final Exception? exception;
TodoState copyWith({
TodoStatus? status,
List<Todo>? todos,
Exception? exception,
}) {
return TodoState(
status: status ?? this.status,
todos: todos ?? this.todos,
exception: exception ?? this.exception,
);
}
// Yeni sahə əlavə edildikdə props-a da əlavə etmək YADDAN ÇIXMAMALIDIR:
// əks halda == iki fərqli state-i bərabər sayır və UI yenilənmir.
@override
List<Object?> get props => [status, todos, exception];
}Yanaşma 1: status enum — sahələr paylaşılır.
sealed class TodoState {
const TodoState();
}
final class TodoInitial extends TodoState {
const TodoInitial();
}
final class TodoLoadInProgress extends TodoState {
const TodoLoadInProgress();
}
final class TodoLoadSuccess extends TodoState {
const TodoLoadSuccess(this.todos);
final List<Todo> todos;
}
final class TodoLoadFailure extends TodoState {
const TodoLoadFailure(this.message);
final String message;
}
// UI: kompilyator bütün halların əhatə olunduğunu yoxlayır.
// Yeni state sinfi əlavə edildikdə bu switch kompilyasiya olunmur —
// səhv runtime yerinə compile-time-da görünür.
Widget build(BuildContext context) {
return BlocBuilder<TodoBloc, TodoState>(
builder: (context, state) => switch (state) {
TodoInitial() => const SizedBox.shrink(),
TodoLoadInProgress() => const TodoSkeleton(),
TodoLoadSuccess(:final todos) => TodoListView(todos: todos),
TodoLoadFailure(:final message) => ErrorView(message: message),
},
);
}Yanaşma 2: sealed siniflər — vəziyyətlər bir-birini istisna edir.
| Kriteriya | Status enum | Sealed siniflər |
|---|---|---|
| Vəziyyətlər bir-birini istisna edir | Zəif uyğun — nullable sahələr yığılır | Təbii uyğun |
| Çoxlu paylaşılan sahə (filtr, səhifə, sorğu) | Rahat — `copyWith` bir yerdə | Təkrarlanma yaranır |
| Yükləmə zamanı köhnə məlumatı göstərmək | Asan: `status: loading` + mövcud `todos` | Əlavə iş: state-ə köhnə dəyəri daşımaq lazımdır |
| Kompilyator yoxlaması | Yoxdur — `switch` üzərində `default` qalır | Var — bütün hallar məcburidir |
| Mövcud koda tədricən əlavə etmək | Asandır | Refactoring tələb edir |
Equatable ilə ən çox rastlanan bug: state sinfinə yeni sahə əlavə olunur, lakin props siyahısına yazılmır. Nəticədə iki fərqli state == üzrə bərabər sayılır, bloc təkrar state qaydasına görə dəyişikliyi buraxmır və UI heç vaxt yenilənmir. Bu səhvin qarşısını almaq üçün ya freezed kimi kod generasiyası (props əl ilə yazılmır), ya da state siniflərini örtən test istifadə olunur.
📚 Mənbələr və sənədlər
- State modelləşdirmərəsmibloclibrary.dev
İki yanaşmanın rəsmi izahı, üstünlük və çatışmazlıqları.
- package:equatablerəsmipub.dev
props mexanizmi və == müqayisəsinin necə qurulduğu.
- package:freezedrəsmipub.dev
copyWith, == və sealed union-ların kod generasiyası ilə yazılması.
- Adlandırma konvensiyaları (state-lər)rəsmibloclibrary.dev