Notifier = view model: Riverpod-da presentation qatı
Rəsmi bələdçidə view model iki iş görür: repository-dən gələn məlumatı ekran state-inə çevirir və view rebuild olarkən state-i saxlayır. Riverpod-da bu rolu Notifier / AsyncNotifier oynayır.
Uyğunluq belədir:
AsyncNotifier<T>— məlumat yükləyən ekranlar üçün:buildmetoduFuture<T>qaytarır,AsyncValueavtomatik idarə olunur.Notifier<T>— sinxron state üçün: form, filtr, seçim.- Command rolunu notifier-in metodları oynayır:
refresh(),submit(),toggleFilter(...).
Notifier-in məsuliyyəti:
- Repository (ya da use-case) çağırmaq.
- Nəticəni ekran state-inə çevirmək.
- İstifadəçi hərəkətlərini qəbul etmək (command-lar).
- Ekrana bağlı state-i saxlamaq: filtr, seçilmiş element, səhifə nömrəsi.
Notifier-in məsuliyyəti olmayan şeylər: HTTP, JSON, cache siyasəti (bunlar data qatındadır), biznes qaydaları (domain modelində ya da use-case-də), naviqasiya və dialoq (bunlar view-un işidir — növbəti mövzu).
// fayl: lib/ui/orders/order_list_notifier.dart
part 'order_list_notifier.g.dart';
@riverpod
class OrderListNotifier extends _$OrderListNotifier {
static const _pageSize = 20;
@override
Future<OrderListState> build() async {
// İlk yükləmə: repository çağırılır, nəticə ekran state-inə çevrilir.
final orders = await _repository.fetchMine(pageSize: _pageSize);
return OrderListState(
orders: orders,
filter: OrderFilter.all,
hasMore: orders.length == _pageSize,
);
}
OrderRepository get _repository => ref.read(orderRepositoryProvider);
// ── Command: filtr dəyişikliyi (yalnız lokal state) ──
void setFilter(OrderFilter filter) {
final current = state.value;
if (current == null) return;
state = AsyncValue.data(current.copyWith(filter: filter));
// DİQQƏT: filtr serverə getmirsə, siyahı yenidən yüklənmir.
// Süzgəc `build`-də hesablanır — bax: aşağıdaki `visibleOrders`.
}
// ── Command: növbəti səhifə ──
Future<void> loadMore() async {
final current = state.value;
if (current == null || !current.hasMore || current.isLoadingMore) return;
// Köhnə siyahı ekranda qalır, altında spinner görünür.
state = AsyncValue.data(current.copyWith(isLoadingMore: true));
final next = await _repository.fetchMine(
page: current.page + 1,
pageSize: _pageSize,
);
state = AsyncValue.data(current.copyWith(
orders: [...current.orders, ...next],
page: current.page + 1,
hasMore: next.length == _pageSize,
isLoadingMore: false,
));
}
// ── Command: yazma əməliyyatı, nəticəni view-a qaytarır ──
Future<CancelOutcome> cancel(String orderId) async {
final result = await _repository.cancel(orderId);
switch (result) {
case Ok(:final value):
// Optimistik deyil: server cavabı ilə siyahı yenilənir.
final current = state.value;
if (current != null) {
state = AsyncValue.data(current.copyWith(
orders: [
for (final o in current.orders)
if (o.id == orderId) value else o,
],
));
}
return CancelOutcome.success;
case Error(error: OrderNotCancellableFailure()):
return CancelOutcome.notCancellable;
case Error():
return CancelOutcome.failed;
}
}
}
// ── Törəmə dəyər: süzgəcdən keçmiş siyahı ──
// Ayrı provider kimi yazmaq notifier-i sadə saxlayır və
// yalnız filtr dəyişdikdə yenidən hesablanır.
@riverpod
List<Order> visibleOrders(Ref ref) {
final state = ref.watch(orderListNotifierProvider).value;
if (state == null) return const [];
return switch (state.filter) {
OrderFilter.all => state.orders,
OrderFilter.active =>
state.orders.where((o) => o.status.isActive).toList(),
OrderFilter.completed =>
state.orders.where((o) => o.status == OrderStatus.completed).toList(),
};
}Notifier-in tam nümunəsi: yükləmə, filtr, səhifələmə və bir yazma əməliyyatı. Diqqət et — heç bir HTTP, JSON ya da `BuildContext`.
| Rəsmi bələdçidə | Riverpod-da | Qeyd |
|---|---|---|
| View model | `Notifier` / `AsyncNotifier` | Bir ekrana bir notifier — əlaqə bir-birədir |
| View model-in state-i | `state` sahəsi (`AsyncValue<T>` ya da `T`) | Immutable olmalıdır — `copyWith` ilə yenilənir |
| Command | Notifier-in public metodu | `Future<void>` ya da nəticə (`enum`) qaytarır |
| View model-in asılılıqları | `ref.watch(...)` / `ref.read(...)` | `watch` — dəyişiklikdə yenidən qurulur; `read` — bir dəfəlik |
| Loading / error state-i | `AsyncValue` | `build`-dən atılan exception avtomatik `AsyncError` olur |
| Törəmə dəyər (hesablanmış) | Ayrı provider | Notifier-i sadə saxlayır, yalnız asılılıq dəyişdikdə hesablanır |
`ref.watch` yoxsa `ref.read`? Bu, notifier yazarkən ən çox səhv edilən yerdir və qaydası sadədir:
- `build` içində —
ref.watch(...): asılılıq dəyişdikdə notifier yenidən qurulur. Repository provider-i dəyişdikdə (məsələn override edildikdə) state avtomatik yenilənir. - Metodların içində (command-larda) —
ref.read(...): bir dəfəlik çağırış. Command-dawatchyazmaq abunəlik yaradır və gözlənilməz təkrar qurulmalara səbəb olur.
Törəmə dəyərləri ayrı provider-də saxlamaq. Süzgəc, sıralama, cəm hesablama kimi işləri notifier-in içində saxlamaq onu şişirdir. Ayrı provider (visibleOrders, cartTotal) iki fayda verir: notifier sadə qalır və hesablama yalnız asılılıq dəyişdikdə aparılır.
Bir ekran, bir notifier. Rəsmi bələdçidə view ilə view model arasındaki əlaqə bir-birədir. Bir notifier-i iki ekranda paylaşmaq istəyirsənsə, deməli paylaşılan hissə əslində repository-yə aiddir — çünki paylaşılan məlumat data qatının işidir.
Praktika. Bir ekranın notifier-ini yaz: build-də repository çağırışı, iki command (biri lokal state dəyişir, biri repository-ni çağırır) və bir törəmə provider (süzgəc ya da cəm).
Sonra notifier üçün test yaz — ProviderContainer.test(overrides: [repositoryProvider.overrideWithValue(Fake...)]) ilə.
Hazır sayılır: notifier faylında http, jsonDecode, BuildContext, Navigator, ScaffoldMessenger yoxdur, və testdə heç bir widget qaldırılmır.
📚 Mənbələr və sənədlər
- Case study: UI qatırəsmidocs.flutter.dev
View model-in məsuliyyətləri və view ilə bir-birə əlaqəsi.
- Riverpod: provider növlərirəsmiriverpod.dev
`@riverpod` ilə funksiya və sinif provider-lərinin yazılışı, `build` metodu.
- Riverpod: ref qaydalarırəsmiriverpod.dev
`ref.watch`, `ref.read`, `ref.listen` arasındaki fərq və hər birinin işlədilmə yeri.
- Arxitektura tövsiyələri: MVVMrəsmidocs.flutter.dev
View model işlətməyin niyə ən yüksək prioritetli tövsiyə olduğu.