FlutterFlutter · Chapterبەش 12 · Lectureوانە 12.1

MVVM in Flutter with ProviderMVVM لە Flutterـدا بە Provider

The MVVM layers in Flutter code: views are widgets, view models are ChangeNotifiers. The lecture's to-do app refactored in two steps (view models into the service locator, then a provider and consumer for each view), the login and to-do views as View + ViewModel, the same view without the provider package (ListenableBuilder), and Flutter's official architecture guide with repositories, commands and tests with fakes.

لە کۆدی Flutterـدا: ، ChangeNotifierن. ئەرکەکانی وانەکە بە دوو هەنگاو ڕێک دەخرێتەوە ( بۆ ناو ، پاشان provider و consumerـێک بۆ هەر )، و ئەرکەکان وەک ⁦View + ViewModel⁩، هەمان بەبێ ⁦provider (ListenableBuilder)⁩ و ڕێنماییە فەرمییەکەی Flutter بە ، و تاقیکردنەوە بە .

21 min read19 خولەک خوێندنەوە · 17 stepsهەنگاو

What you will learnچی فێر دەبیت

  • Name the Flutter class for each MVVM part: widgets, ChangeNotifier view models, model and service classes.
  • Refactor a provider app: move view models into a service locator, then give each view its own provider and consumer.
  • Write the login and to-do screens as View + ViewModel, keeping logic out of the widgets.
  • Use a view model without the provider package (ChangeNotifier + ListenableBuilder).
  • Relate the lecture's design to Flutter's guide (repositories, services, commands) and test a view model with fakes.
  • Flutter بۆ هەر بەشێکی ناو ببەیت: ، ChangeNotifier، و .
  • provider ڕێک بخەیتەوە: بگوازیتەوە بۆ ، پاشان provider و consumerـی خۆی بدەیت بە هەر .
  • شاشەکانی و ئەرکەکان وەک ⁦View + ViewModel⁩ بنووسیت و لۆژیک لە دوور بخەیتەوە.
  • بەبێ provider بەکار بهێنیت ⁦(ChangeNotifier + ListenableBuilder)⁩.
  • دیزاینەکەی وانەکە ببەستیتەوە بە ڕێنماییەکەی Flutter (، ، ) و بە تاقی بکەیتەوە.

MVVM, the Flutter wayMVVM، بە شێوازی Flutter

The shared lesson App Architecture: the MVVM Pattern explains the idea without any code: a view shows the screen, a view model holds the screen's state and prepares data, a service and a model do the real work with the data, the view knows its view model, and the view model never knows the view. This lesson shows what each of those parts is in Flutter, then rebuilds the to-do app in MVVM the way the lecture does.

وانە هاوبەشەکەی تەلارسازیی نەرمەواڵە: شێوازی MVVM بیرۆکەکە بەبێ هیچ کۆدێک ڕوون دەکاتەوە: شاشەکە پیشان دەدات، شاشەکە هەڵدەگرێت و دراوە ئامادە دەکات، و کاری ڕاستەقینە لەگەڵ دراوەکەدا دەکەن، دەناسێت و هەرگیز ناناسێت. ئەم وانەیە پیشان دەدات هەریەکێک لەو بەشانە لە Flutterـدا چییە، پاشان ئەپی ئەرکەکان بە دووبارە دروست دەکاتەوە بەو شێوەیەی وانەکە دەیکات.

MVVM partبەشی In Flutterلە FlutterـداIn our to-do appلە ئەرکەکانماندا
ViewWidgets. A whole screen is a view, and so are its big parts: an app bar, a list, one row.. شاشەیەکی تەواو و بەشە گەورەکانیشی: ، لیستێک، یەک دێڕ.LoginView, TodoListView, AppBarView, TodoViewLoginView، TodoListView، AppBarView، TodoView
View modelA ChangeNotifier. It holds the screen's state and calls notifyListeners() when it changes.ChangeNotifier. شاشەکە هەڵدەگرێت و کاتێک دەگۆڕێت notifyListeners() بانگ دەکات.LoginViewModel, TodoListViewModelLoginViewModel، TodoListViewModel
ModelPlain Dart classes, held by the view models. ئاسایی Dart، کە هەڵیان دەگرن.User, TodoUser، Todo
ServicePlain Dart classes that talk to a data source. ئاسایی Dart کە قسە لەگەڵ دەکەن.UserService, TodoServiceUserService، TodoService

Two short sentences from the lecture carry the whole idea: in Flutter, views are widgets, and ChangeNotifiers are view models; the models are held by the ChangeNotifiers. Everything else in this lesson is about connecting the two.

دوو ڕستەی کورتی وانەکە هەموو بیرۆکەکە هەڵدەگرن: لە Flutterـدا، و ChangeNotifierـەکان ؛ لەلایەن ChangeNotifierـەکانەوە هەڵدەگیرێن. هەموو ئەوەی تری ئەم وانەیە دەربارەی بەستنەوەی ئەم دووانەیە.

Quick checkپشکنینی خێراIn a Flutter MVVM app, what is a view model usually?لە Flutterـدا، زۆربەی جار چییە؟

Providers revisited: state above the whole appدیسان providerـەکان: دۆخ لە سەرووی هەموو ئەپەکەوە

The lecture starts by drawing the provider apps of the previous chapter in a new way. One MultiProvider sits above MaterialApp and holds several providers, each with a piece of state. Screens and their widgets anywhere below read the state they need. Next to the tree stands a service locator with the services and other dependencies that the providers use.

وانەکە بەوە دەست پێ دەکات کە providerـی بەشی پێشوو بە شێوەیەکی نوێ دەکێشێت. یەک MultiProvider لە سەرووی MaterialAppـەوە دادەنرێت و چەند providerـێک هەڵدەگرێت، هەریەکەیان پارچەیەک . شاشەکان و لە هەر شوێنێکی خوارەوە ئەو دەخوێننەوە کە پێویستیانە. لە تەنیشت دارەکەوە هەیە کە ئەو و ترانەی تێدایە کە providerـەکان بەکاریان دەهێنن.

To turn this into MVVM, the lecture makes two choices. Each provider is a ChangeNotifierProvider, because the UI must be able to update the state, and that is what state management needs. And each ChangeNotifier it holds is a view model with its models inside, written as Viewmodel<Model> on the slide.

بۆ گۆڕینی ئەمە بۆ ، وانەکە دوو هەڵبژاردن دەکات. هەر providerـێک ChangeNotifierProviderـە، چونکە ڕووکارەکە دەبێت بتوانێت نوێ بکاتەوە و ئەوەش ئەوەیە کە پێویستی پێیەتی. هەر ChangeNotifierـێکیش کە هەڵیدەگرێت و لەناودایە، کە لەسەر سلایدەکە بە Viewmodel<Model> نووسراوە.

Quick checkپشکنینی خێراWhy does the lecture use ChangeNotifierProvider (and not a plain Provider) for each view model?بۆچی وانەکە بۆ هەر ChangeNotifierProvider بەکار دەهێنێت (نەک Providerـی ئاسایی)؟

The demo in MVVM: two views, two view modelsنموونەکە بە MVVM: دوو دیمەن، دوو مۆدێلی دیمەن

The app stays the same: a login screen and a to-do screen. Only its inside changes. The widget tree becomes small (MaterialApp with LoginView and TodoListView), and a service locator holds a REST service (which talks to the database on the server) and two view models: one for the users and the current user, one for the to-do list. Each view, and even each part of a view, gets its own ChangeNotifierProvider and Consumer:

وەک خۆی دەمێنێتەوە: شاشەیەکی و شاشەیەکی ئەرکەکان. تەنها ناوەوەکەی دەگۆڕێت. بچووک دەبێتەوە (MaterialApp لەگەڵ LoginView و TodoListView) و REST هەڵدەگرێت (کە قسە لەگەڵ لەسەر دەکات) لەگەڵ دوو : یەکێک بۆ و ئێستا، یەکێک بۆ لیستی ئەرکەکان. هەر ، تەنانەت هەر بەشێکی ، ChangeNotifierProvider و Consumerـی خۆی وەردەگرێت:

Two widget trees and a service locator. LoginView → Scaffold → ChangeNotifierProvider<LoginViewModel> → Consumer → ListView → ListTile ('the user list'). TodoListView → Scaffold, which splits into (a) the app bar: ChangeNotifierProvider<LoginViewModel> → Consumer → AppBar, and (b) the list: ChangeNotifierProvider<TodoListViewModel> → Consumer → ListView → TodoView. Providers are purple, consumers blue. On the right the service locator (get_it) holds Services (UserService, TodoService) and View models (LoginViewModel, TodoListViewModel); an arrow and a note say every provider takes its view model from here.دوو داری ویجێتەکان و دۆزەرەوەیەکی خزمەتگوزاری. LoginView → Scaffold → ChangeNotifierProvider<LoginViewModel> → Consumer → ListView → ListTile ('لیستی بەکارهێنەرەکان'). TodoListView → Scaffold، کە دەبێت بە دوو لق: (a) شریتی ئەپ: ChangeNotifierProvider<LoginViewModel> → Consumer → AppBar و (b) لیستەکە: ChangeNotifierProvider<TodoListViewModel> → Consumer → ListView → TodoView. providerـەکان مۆرن، consumerـەکان شین. لای ڕاست دۆزەرەوەی خزمەتگوزاری (get_it) خزمەتگوزارییەکان (UserService، TodoService) و مۆدێلەکانی دیمەن (LoginViewModel، TodoListViewModel) هەڵدەگرێت؛ تیرێک و تێبینییەک دەڵێن هەموو providerـێک مۆدێلی دیمەنەکەی لێرەوە وەردەگرێت.
How the two screens are built in MVVM (redrawn from the lecture's slides).دوو شاشەکە چۆن بە دروست دەکرێن (لە سلایدەکانی وانەکەوە دووبارە کێشراوە).CêIT
  • LoginView: its provider and consumer get the user list from the login view model to build the ListView; a tap on a ListTile tells the view model to change the current user.
  • TodoListView: (a) the app bar has its own provider and consumer that get the current user (avatar and name); (b) the list has its own provider and consumer that get the to-do list; each row sends “delete to-do” and “toggle status” to the to-do view model.
  • The view models use the services, and the services talk to the database.
  • LoginView: provider و consumerـەکەی لیستی لە وەردەگرن بۆ دروستکردنی ListView؛ دەستلێدان لە ListTileـێک بە دەڵێت ئێستا بگۆڕێت.
  • TodoListView: ⁦(a)⁩ provider و consumerـی خۆی هەیە کە ئێستا وەردەگرن (وێنۆچکە و ناو)؛ ⁦(b)⁩ لیستەکە provider و consumerـی خۆی هەیە کە لیستی ئەرکەکان وەردەگرن؛ هەر دێڕێک «سڕینەوەی ئەرک» و «گۆڕینی دۆخ» دەنێرێت بۆ ئەرکەکان.
  • بەکار دەهێنن و قسە لەگەڵ دەکەن.
Quick checkپشکنینی خێراIn the MVVM version, where does the app bar get the current user's name?لە وەشانی ، ناوی ئێستا لە کوێوە وەردەگرێت؟

Step 0: write the two view modelsهەنگاوی ٠: دوو مۆدێلی دیمەنەکە بنووسە

Before moving anything, write the view models. Each one extends ViewModel, a small base class of our project: a ChangeNotifier with a busy flag that is true while data loads (its file is shown in the next lesson). The services are private fields, so a view cannot skip the view model and call a service itself. The view sees only getters and methods:

پێش گواستنەوەی هەر شتێک، بنووسە. هەریەکەیان ViewModel درێژ دەکاتەوە، بنەڕەتیی بچووکی پڕۆژەکەمان: ChangeNotifierـێک بە ئاڵایەکی busy کە لە کاتی بارکردنی دراوەدا trueـە ( لە وانەی داهاتووـدا پیشان دەدرێت). خانەی تایبەتن، بۆیە ناتوانێت بازبدات و خۆی بانگ بکات. تەنها و دەبینێت:

The login view modelمۆدێلی دیمەنی چوونەژوورەوەscreens/login/login_viewmodel.dartDART
import '../../models/user.dart';
import '../../services/user/user_service.dart';
import '../viewmodel.dart';

class LoginViewModel extends ViewModel {
  LoginViewModel(this._userService) {
    _loadUsers();
  }

  final UserService _userService; // private: the view cannot reach it
  List<User> _users = const [];
  User? _currentUser;

  List<User> get users => _users;
  User? get currentUser => _currentUser;

  Future<void> _loadUsers() async {
    setBusy(true);
    _users = await _userService.getUserList();
    setBusy(false); // also notifies the view
  }

  void logInAs(User user) {
    _currentUser = user;
    notifyListeners();
  }

  void logOut() {
    _currentUser = null;
    notifyListeners();
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 6

The service is handed in (dependency injection); loading starts at once.

دەدرێتێ ()؛ بارکردن یەکسەر دەست پێ دەکات.

Lineدێڕی 10

Private: the view cannot reach the data layer directly.

تایبەت: ناتوانێت ڕاستەوخۆ بگاتە دراوە.

Lineدێڕی 23

An event from the view becomes a change of state plus a notification.

لە دەبێت بە گۆڕانی لەگەڵ ئاگادارکردنەوەیەک.

The to-do list view modelمۆدێلی دیمەنی لیستی ئەرکەکانscreens/todolist/todolist_viewmodel.dartDART
import '../../models/todo.dart';
import '../../models/user.dart';
import '../../services/todo/todo_service.dart';
import '../login/login_viewmodel.dart';
import '../viewmodel.dart';

class TodoListViewModel extends ViewModel {
  TodoListViewModel(this._todoService, this._login) {
    _login.addListener(_userChanged); // follow the current user
    _userChanged();
  }

  final TodoService _todoService;
  final LoginViewModel _login;
  int? _userId;
  List<Todo> _todos = const [];

  User? get user => _login.currentUser;
  List<Todo> get todos => _todos;

  void _userChanged() {
    final id = _login.currentUser?.id;
    if (id == _userId) return;
    _userId = id;
    _todos = const [];
    if (id != null) _load(id);
    notifyListeners();
  }

  Future<void> _load(int userId) async {
    setBusy(true);
    final list = await _todoService.getTodoList(userId);
    if (userId != _userId) return; // another user logged in meanwhile
    _todos = list;
    setBusy(false);
  }

  Future<void> toggle(Todo todo) async {
    final changed = todo.copyWith(completed: !todo.completed);
    await _todoService.updateTodo(changed);
    _todos = [for (final t in _todos) t.id == todo.id ? changed : t];
    notifyListeners();
  }

  Future<void> delete(Todo todo) async {
    await _todoService.deleteTodo(todo.id);
    _todos = _todos.where((t) => t.id != todo.id).toList();
    notifyListeners();
  }

  Future<void> add(String title) async {
    final userId = _userId;
    if (userId == null) return;
    _todos = [..._todos, await _todoService.addTodo(userId, title)];
    notifyListeners();
  }

  @override
  void dispose() {
    _login.removeListener(_userChanged);
    super.dispose();
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 9

It follows the current user (see the next screen).

شوێنی ئێستا دەکەوێت (بڕوانە شاشەی داهاتوو).

Lineدێڕی 33

A late answer for an old user is ignored.

وەڵامێکی دواکەوتوو بۆ کۆن پشتگوێ دەخرێت.

Lineدێڕی 38

toggle, delete and add: the events the view can send.

toggle، delete و add: ئەو دەتوانێت بینێرێت.

Compare with the TodoListNotifier of the provider version: the list logic is the same. What changed is who creates it and how it learns about the current user.

بەراوردی بکە لەگەڵ TodoListNotifierـی وەشانی provider: لۆژیکی لیستەکە هەمانە. ئەوەی گۆڕاوە ئەوەیە کێ دروستی دەکات و چۆن لە ئێستا ئاگادار دەبێت.

Quick checkپشکنینی خێراWhy are _userService and _todoService private?بۆچی _userService و _todoService تایبەتن؟

Step 0: write the two view modelsهەنگاوی ٠: دوو مۆدێلی دیمەنەکە بنووسە

One view model follows anotherمۆدێلێکی دیمەن شوێنی یەکێکی تر دەکەوێت

In the provider version, a ChangeNotifierProxyProvider told the to-do list about each new user. In MVVM there is no proxy in the tree, so the to-do view model does it itself: it receives the login view model in its constructor and listens to it with addListener. When the current user changes, _userChanged loads that user's to-dos. In dispose it removes the listener again.

لە وەشانی providerـدا، ChangeNotifierProxyProviderـێک بە لیستی ئەرکەکانی دەگوت کە نوێ هاتووە. لە هیچ proxyـێک لە دارەکەدا نییە، بۆیە ئەرکەکان خۆی ئەمە دەکات: لە وەردەگرێت و بە addListener گوێی لێ دەگرێت. کاتێک ئێستا دەگۆڕێت، _userChanged ئەرکەکانی ئەو بار دەکات. لە disposeـدا لادەباتەوە.

Quick checkپشکنینی خێراWhy does TodoListViewModel call _login.removeListener(_userChanged) in dispose?بۆچی TodoListViewModel لە disposeـدا _login.removeListener(_userChanged) بانگ دەکات؟

Refactoring the provider app into MVVMڕێکخستنەوەی کۆدی ئەپی provider بۆ MVVM

Definitionپێناسە

Changing the structure of existing code (where things are created, named and kept) without changing what the program does, so that it is easier to read, test and extend.

گۆڕینی پێکهاتەی کۆدێکی هەبوو (ئەوەی شتەکان لە کوێ دروست دەکرێن، ناویان چییە و لە کوێ هەڵدەگیرێن) بەبێ گۆڕینی ئەوەی دەیکات، تا خوێندنەوە، تاقیکردنەوە و فراوانکردنی ئاسانتر بێت.

The lecture changes the to-do app in two refactoring steps. The app looks and behaves exactly the same after each step; only the place where things are created and found changes.

وانەکە ئەرکەکان بە دوو هەنگاوی دەگۆڕێت. دوای هەر هەنگاوێک ڕێک وەک پێشوو دەردەکەوێت و ڕەفتار دەکات؛ تەنها ئەو شوێنە دەگۆڕێت کە شتەکانی تێدا دروست دەکرێن و دەدۆزرێنەوە.

Quick checkپشکنینی خێراWhat does a refactoring change? چی دەگۆڕێت؟

Refactoring the provider app into MVVMڕێکخستنەوەی کۆدی ئەپی provider بۆ MVVM

Step 1: move the view models to the service locatorهەنگاوی ١: مۆدێلەکانی دیمەن بگوازەرەوە بۆ دۆزەرەوەی خزمەتگوزاری

In the provider version, create: built each notifier inside its provider, so the notifier lived and died with the tree. The lecture's first refactoring moves the view models into the service locator, next to the services. The locator now creates and owns them; the providers in the MultiProvider only hand them on with .value, which is the right constructor for objects that already exist (create or .value?). With the get_it package the registration looks like this (the next lesson explains get_it fully):

لە وەشانی providerـدا، create: هەر ChangeNotifierـێکی لەناو providerـەکەیدا دروست دەکرد، بۆیە ChangeNotifierـەکە لەگەڵ دارەکەدا دەژیا و دەمرد. یەکەم وانەکە دەگوازێتەوە بۆ ناو ، لە تەنیشت . ئێستا دروستیان دەکات و خاوەنیانە؛ providerـەکانی ناو MultiProvider تەنها بە .value دەیانگوازنەوە، کە دروستە بۆ کە پێشتر هەن (create یان ⁦.value⁩؟). لەگەڵ ⁦get_it⁩ تۆمارکردنەکە بەم شێوەیەیە (وانەی داهاتوو ⁦get_it⁩ بە تەواوی ڕوون دەکاتەوە):

The view models registered in the service locatorمۆدێلەکانی دیمەن کە لە دۆزەرەوەی خزمەتگوزاریدا تۆمار کراونapp/dependencies.dartDART
void init() {
  // … the services, as before (the next lesson shows the whole file) …

  // View models
  dependency.registerLazySingleton(
    () => LoginViewModel(dependency<UserService>()),
  );
  dependency.registerLazySingleton(
    () => TodoListViewModel(
      dependency<TodoService>(),
      dependency<LoginViewModel>(),
    ),
  );
}
Step 1: same MultiProvider, view models from the locatorهەنگاوی ١: هەمان MultiProvider، مۆدێلەکانی دیمەن لە دۆزەرەوەی خزمەتگوزارییەکەوەmain.dartDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'app/dependencies.dart' as di;
import 'screens/login/login_view.dart';
import 'screens/login/login_viewmodel.dart';
import 'screens/todolist/todolist_viewmodel.dart';

// Refactoring step 1: the view models live in the service locator.
// The providers still sit together in one MultiProvider above MaterialApp.
void main() {
  di.init();

  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider<LoginViewModel>.value(
          value: di.dependency<LoginViewModel>(),
        ),
        ChangeNotifierProvider<TodoListViewModel>.value(
          value: di.dependency<TodoListViewModel>(),
        ),
      ],
      child: const MaterialApp(home: LoginView()),
    ),
  );
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 12

Fill the service locator first.

یەکەم پڕ بکەرەوە.

Lineدێڕی 18

The provider only passes on the view model the locator owns.

providerـەکە تەنها ئەو دەگوازێتەوە کە هی .

Quick checkپشکنینی خێراAfter step 1, who creates the view models?دوای هەنگاوی ١، کێ دروست دەکات؟

Refactoring the provider app into MVVMڕێکخستنەوەی کۆدی ئەپی provider بۆ MVVM

Step 2: each view gets its own providerهەنگاوی ٢: هەر دیمەنێک providerـی خۆی وەردەگرێت

Once the locator owns the view models, the MultiProvider above MaterialApp has no job left. The second refactoring removes it: each view is wrapped in its own ChangeNotifierProvider, which takes its view model from the service locator, and a Consumer right below it rebuilds only that view. A provider now sits as close as possible to the widgets that use it, and a part of a view (an app bar, a single row) can have its own pair too.

کاتێک خاوەنی بێت، MultiProviderـەکەی سەرووی MaterialApp هیچ کارێکی نامێنێت. دووەم لای دەبات: هەر لەناو ChangeNotifierProviderـی خۆیدا دەپێچرێت، کە لە وەردەگرێت و Consumerـێک ڕاستەوخۆ لە خوارەوەیدا تەنها ئەو دووبارە دروست دەکاتەوە. ئێستا provider تا دەکرێت نزیک لەو دادەنرێت کە بەکاری دەهێنن و بەشێکی (، یەک دێڕ) دەتوانێت جووتێکی خۆی هەبێت.

Animation in three frames. Right: a service locator (get_it). Frame 1, 'Before': a MultiProvider above MaterialApp holds two ChangeNotifierProviders with create: (_) => LoginViewModel(…) and create: (_) => TodoListViewModel(…); the locator holds only services; LoginView and TodoListView read from the MultiProvider. Frame 2, 'Step 1': the locator now also holds the view models LoginViewModel and TodoListViewModel (highlighted); the providers say .value(value: dependency<LoginViewModel>()) and an arrow points to the locator. Frame 3, 'Step 2': no MultiProvider; MaterialApp has LoginView with its own ChangeNotifierProvider and Consumer, and TodoListView with two pairs, one for the app bar (LoginViewModel) and one for the list (TodoListViewModel); an arrow points to the view models in the locator.
Animation in three frames. Right: a service locator (get_it). Frame 1, 'Before': a MultiProvider above MaterialApp holds two ChangeNotifierProviders with create: (_) => LoginViewModel(…) and create: (_) => TodoListViewModel(…); the locator holds only services; LoginView and TodoListView read from the MultiProvider. Frame 2, 'Step 1': the locator now also holds the view models LoginViewModel and TodoListViewModel (highlighted); the providers say .value(value: dependency<LoginViewModel>()) and an arrow points to the locator. Frame 3, 'Step 2': no MultiProvider; MaterialApp has LoginView with its own ChangeNotifierProvider and Consumer, and TodoListView with two pairs, one for the app bar (LoginViewModel) and one for the list (TodoListViewModel); an arrow points to the view models in the locator.
The refactoring in three pictures: before, step 1 and step 2. لە سێ وێنەدا: پێشتر، هەنگاوی ١ و هەنگاوی ٢.CêIT

What do you gain? A view's provider and its consumer are now next to each other, so a reader sees at once which view model a view uses. When a view model notifies, only the consumers below its own provider rebuild. And there is no list of every provider of the app to keep in order above MaterialApp.

چی بەدەست دەهێنیت؟ ئێستا provider و consumerـی لە تەنیشت یەکترن، بۆیە خوێنەر یەکسەر دەبینێت کام بەکار دەهێنێت. کاتێک ئاگادار دەکاتەوە، تەنها ئەو consumerـانە دووبارە دروست دەکرێنەوە کە لە خوارەوەی providerـی خۆیدان. هەروەها هیچ لیستێکی هەموو providerـەکانی نییە کە لە سەرووی MaterialAppـەوە بە ڕیزی ڕابگریت.

Quick checkپشکنینی خێراAfter step 2, how many providers sit above MaterialApp?دوای هەنگاوی ٢، چەند provider لە سەرووی MaterialAppـەوە دادەنرێن؟

Tasks for the login screenئەرکەکانی شاشەی چوونەژوورەوە

The lecture lists four tasks for turning the login screen into an MVVM view:

وانەکە چوار ئەرک ڕیز دەکات بۆ گۆڕینی شاشەی بۆ :

  1. Define LoginViewModelLoginViewModel پێناسە بکە

    Done above: it holds the users and the current user.

    لە سەرەوە کرا: و ئێستا هەڵدەگرێت.

  2. Move LoginViewModel to the service locatorLoginViewModel بگوازەرەوە بۆ دۆزەرەوەی خزمەتگوزاری

    Register it with get_it, so the locator creates and owns it.

    بە ⁦get_it⁩ تۆماری بکە، تا دروستی بکات و خاوەنی بێت.

  3. The view has its own provider and consumerدیمەنەکە provider و consumerـی خۆی هەیە

    Wrap the view in ChangeNotifierProvider.value and a Consumer<LoginViewModel>.

    لەناو ChangeNotifierProvider.value و Consumer<LoginViewModel>ـدا بپێچە.

  4. Refactor DI from Provider.of() to the service locatorتێخستنی وابەستەیی لە ⁦Provider.of()⁩ـەوە بگۆڕە بۆ دۆزەرەوەی خزمەتگوزاری

    The view no longer finds the users with Provider.of<List<User>> from a provider above the app; it gets its view model from the locator.

    چیتر بە Provider.of<List<User>> لە providerـێکی سەرووی نادۆزێتەوە؛ لە وەردەگرێت.

The login view with its own provider and consumerدیمەنی چوونەژوورەوە بە provider و consumerـی خۆیەوەscreens/login/login_view.dartDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../../app/dependencies.dart';
import '../todolist/todolist_view.dart';
import 'login_viewmodel.dart';

// Refactoring step 2: the view has its own provider and consumer.
class LoginView extends StatelessWidget {
  const LoginView({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<LoginViewModel>.value(
      value: dependency<LoginViewModel>(), // from the service locator
      child: Consumer<LoginViewModel>(
        builder: (context, viewModel, child) => Scaffold(
          appBar: AppBar(title: const Text('Choose a user to log in as')),
          body: viewModel.busy
              ? const Center(child: CircularProgressIndicator())
              : ListView(
                  children: [
                    for (final user in viewModel.users)
                      ListTile(
                        leading: CircleAvatar(child: Text(user.name[0])),
                        title: Text(user.name),
                        subtitle: Text('user id: ${user.id}'),
                        onTap: () {
                          viewModel.logInAs(user); // change the current user
                          Navigator.of(context).push(
                            MaterialPageRoute<void>(
                              builder: (_) => const TodoListView(),
                            ),
                          );
                        },
                      ),
                  ],
                ),
        ),
      ),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 14

The view's own provider…

providerـی خودی …

Lineدێڕی 15

…takes the view model from the service locator…

… لە وەردەگرێت…

Lineدێڕی 16

…and its own consumer rebuilds the screen when the view model notifies.

…و consumerـی خۆی کاتێک ئاگادار دەکاتەوە شاشەکە دووبارە دروست دەکاتەوە.

Lineدێڕی 29

The view only sends an event; the view model decides what changes.

تەنها دەنێرێت؛ بڕیار دەدات چی دەگۆڕێت.

Quick checkپشکنینی خێراWhich line belongs in the login view, not in its view model?کام دێڕ هی ، نەک ؟

Tasks for the to-do list screenئەرکەکانی شاشەی لیستی ئەرکەکان

The to-do screen follows the same tasks with two view models. The app bar has its own provider and consumer of the login view model (“get user”); the list has its own provider and consumer of the to-do view model (“get the to-do list, to build the ListView”), and each row sends “delete to-do” and “toggle status”:

شاشەی ئەرکەکان هەمان ئەرکەکان پەیڕەو دەکات بە دوو . provider و consumerـی خۆی هەیە بۆ (« وەربگرە»)؛ لیستەکە provider و consumerـی خۆی هەیە بۆ ئەرکەکان («لیستی ئەرکەکان وەربگرە بۆ دروستکردنی ListView») و هەر دێڕێک «سڕینەوەی ئەرک» و «گۆڕینی دۆخ» دەنێرێت:

The to-do view: two provider-consumer pairsدیمەنی ئەرکەکان: دوو جووتی provider و consumerscreens/todolist/todolist_view.dartDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../../app/dependencies.dart';
import '../login/login_viewmodel.dart';
import 'todolist_viewmodel.dart';

class TodoListView extends StatelessWidget {
  const TodoListView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: PreferredSize(
        preferredSize: const Size.fromHeight(kToolbarHeight),
        // (a) the app bar: its own provider + consumer of LoginViewModel
        child: ChangeNotifierProvider<LoginViewModel>.value(
          value: dependency<LoginViewModel>(),
          child: Consumer<LoginViewModel>(
            builder: (context, login, child) => AppBar(
              title: Text(login.currentUser?.name ?? ''),
              actions: [
                IconButton(
                  tooltip: 'Log out',
                  icon: const Icon(Icons.logout),
                  onPressed: () {
                    login.logOut();
                    Navigator.of(context).pop();
                  },
                ),
              ],
            ),
          ),
        ),
      ),
      // (b) the list: its own provider + consumer of TodoListViewModel
      body: ChangeNotifierProvider<TodoListViewModel>.value(
        value: dependency<TodoListViewModel>(),
        child: Consumer<TodoListViewModel>(
          builder: (context, todos, child) => ListView(
            children: [
              for (final todo in todos.todos)
                ListTile(
                  title: Text(
                    todo.title,
                    style: TextStyle(
                      decoration:
                          todo.completed ? TextDecoration.lineThrough : null,
                    ),
                  ),
                  onTap: () => todos.toggle(todo), // toggle status
                  onLongPress: () => todos.delete(todo), // delete
                ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add a to-do',
        onPressed: () => dependency<TodoListViewModel>().add('New task'),
        child: const Icon(Icons.add),
      ),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 16

(a) The app bar listens only to the login view model.

⁦(a)⁩ تەنها گوێ لە دەگرێت.

Lineدێڕی 36

(b) The list listens only to the to-do view model.

⁦(b)⁩ لیستەکە تەنها گوێ لە ئەرکەکان دەگرێت.

Lineدێڕی 51

Row events go to the view model.

دێڕ دەچن بۆ .

Quick checkپشکنینی خێراThe user toggles a to-do. Which consumer rebuilds? ئەرکێک دەگۆڕێت. کام consumer دووبارە دروست دەکرێتەوە؟

Can we use a view model without Provider?دەتوانین مۆدێلی دیمەن بەبێ Provider بەکار بهێنین؟

The lecture ends with a question and two pictures. Our implementation: a ChangeNotifierProvider sits above the View, the View holds the widgets, and the view model lives in the service locator. Implementation without Provider: the View and its widgets talk directly to the view model in the service locator. The slide does not answer. The answer is yes, and it is the way Flutter's own documentation teaches MVVM today.

وانەکە بە پرسیارێک و دوو وێنە کۆتایی دێت. جێبەجێکردنی ئێمە: ChangeNotifierProviderـێک لە سەرووی Viewـەوەیە، View لەخۆ دەگرێت و لە دەژی. جێبەجێکردن بەبێ Provider: و ڕاستەوخۆ قسە لەگەڵ لە دەکەن. سلایدەکە وەڵام نادات. وەڵامەکە بەڵێیە و ئەوە ئەو ڕێگایەیە کە بەڵگەنامەکانی خودی Flutter ئەمڕۆ پێ فێر دەکەن.

Two built-in classes are enough: the view model is a ChangeNotifier, and the view rebuilds with a ListenableBuilder. The view receives its view model through its constructor (from the router, main, or the service locator), so no package is involved at all:

دوو ناوخۆیی بەسن: ChangeNotifierـە و بە ListenableBuilder دووبارە دروست دەبێتەوە. لە ڕێگەی وەردەگرێت (لە router، main یان )، بۆیە هیچ بەکار نایەت:

The to-do view with ListenableBuilder (no provider package)دیمەنی ئەرکەکان بە ListenableBuilder (بەبێ کتێبخانەی provider)screens/todolist/todolist_view.dartDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
import 'package:flutter/material.dart';

import 'todolist_viewmodel.dart';

// The same view without the provider package:
// it receives its view model and listens with ListenableBuilder.
class TodoListView extends StatelessWidget {
  const TodoListView({super.key, required this.viewModel});

  final TodoListViewModel viewModel;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(viewModel.user?.name ?? '')),
      body: ListenableBuilder(
        listenable: viewModel, // rebuilds when the view model notifies
        builder: (context, child) => ListView(
          children: [
            for (final todo in viewModel.todos)
              ListTile(
                title: Text(
                  todo.title,
                  style: TextStyle(
                    decoration:
                        todo.completed ? TextDecoration.lineThrough : null,
                  ),
                ),
                onTap: () => viewModel.toggle(todo),
                onLongPress: () => viewModel.delete(todo),
              ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add a to-do',
        onPressed: () => viewModel.add('New task'),
        child: const Icon(Icons.add),
      ),
    );
  }
}

// Somewhere above it (a route, or main) hands the view model in:
//   TodoListView(viewModel: dependency<TodoListViewModel>())
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 8

The view model comes in through the constructor.

لە ڕێگەی دێتە ژوورەوە.

Lineدێڕی 17

ListenableBuilder plays the role of the Consumer.

ListenableBuilder ڕۆڵی Consumer دەگێڕێت.

Lineدێڕی 29

Events go straight to the view model.

ڕاستەوخۆ دەچن بۆ .

PartبەشWith provider (the lecture)بە provider (وانەکە)Without provider (Flutter docs)بەبێ provider (بەڵگەنامەکانی Flutter)
View modelChangeNotifierChangeNotifier
How the view gets it چۆن وەری دەگرێتChangeNotifierProvider.value + Consumer (or context.watch)ChangeNotifierProvider.value + Consumer (یان context.watch)A constructor parameter
What rebuilds the viewچی دووبارە دروست دەکاتەوەConsumerListenableBuilder
Package needed پێویستprovidernone (built into Flutter)هیچ (لەناو خودی Flutterـدایە)
Quick checkپشکنینی خێراWithout the provider package, which widget rebuilds part of a view when its view model notifies?بەبێ provider، کام بەشێک لە دووبارە دروست دەکاتەوە کاتێک ئاگادار دەکاتەوە؟

Flutter's official architecture guideڕێنماییە فەرمییەکەی تەلارسازیی نەرمەواڵەی Flutter

The lecture's slides were made before Flutter published its own architecture guide. The guide recommends MVVM too, with a few more names. Here is how its parts line up with this lesson:

سلایدەکانی وانەکە پێش ئەوە دروست کران کە Flutter ڕێنمایی خۆی بڵاو بکاتەوە. ڕێنماییەکە پێشنیار دەکات، بە چەند ناوێکی زیاترەوە. ئەمە ئەوەیە کە بەشەکانی چۆن لەگەڵ ئەم وانەیەدا یەک دەگرنەوە:

GuideڕێنماییIts jobکارەکەیIn this lessonلەم وانەیەدا
View (UI layer) ( ڕووکار)A composition of widgets for one feature; no business logic, only simple display logic (show/hide, animation, layout, simple routing).کۆمەڵێک بۆ یەک تایبەتمەندی؛ بەبێ لۆژیکی بازرگانی، تەنها لۆژیکی سادەی پیشاندان (پیشاندان/شاردنەوە، ، ڕیزبەندی، سادە).LoginView, TodoListViewLoginView، TodoListView
View model (UI layer) ( ڕووکار)Turns app data into UI state, keeps that state, and exposes commands to the view.دراوەی دەگۆڕێت بۆ ڕووکار، ئەو هەڵدەگرێت و دەدات بە .LoginViewModel, TodoListViewModelLoginViewModel، TodoListViewModel
Repository (data layer) ( دراوە)The source of truth for one type of data: caching, errors, retries, turning raw data into models.سەرچاوەی ڕاستی بۆ یەک جۆر لە دراوە: ، ، هەوڵدانەوە، گۆڕینی دراوەی خاو بۆ .Not in the lecture; the view models talk to services directlyلە وانەکەدا نییە؛ ڕاستەوخۆ قسە لەگەڵ دەکەن
Service (data layer) ( دراوە)Wraps one data source (a REST API, local files, a platform API); holds no state.یەک دەپێچێتەوە (⁦REST API⁩، فایلە ناوخۆییەکان، APIـی )؛ هیچ هەڵناگرێت.UserService, TodoServiceUserService، TodoService
Quick checkپشکنینی خێراIn Flutter's guide, which part is the source of truth for one type of data and handles caching and errors?لە ڕێنماییەکەی Flutterـدا، کام بەش سەرچاوەی ڕاستییە بۆ یەک جۆر لە دراوە و و بەڕێوە دەبات؟

Flutter's official architecture guideڕێنماییە فەرمییەکەی تەلارسازیی نەرمەواڵەی Flutter

One view, one view model, and commandsیەک دیمەن، یەک مۆدێلی دیمەن و فەرمانەکان

  • One view, one view model per feature (usually a screen). A view model may use several repositories, and repositories may be shared by several view models.
  • A command is a method of the view model that the view calls from a gesture, without knowing how it works. The guide's example wraps each action in a small Command object that also says whether it is running, finished with an error, or completed, so the view can show a spinner or an error without extra flags.
  • The guide's case study shares services and repositories with the provider package (Provider, ChangeNotifierProvider, context.read) and builds each screen's view model when the route is created; views listen with ListenableBuilder. Provider and the built-in classes work together.
  • یەک ، یەک بۆ هەر تایبەتمەندییەک (زۆربەی جار شاشەیەک). دەتوانێت چەند بەکار بهێنێت و چەند دەتوانن بەش بکەن.
  • کە لە بانگی دەکات، بەبێ ئەوەی بزانێت چۆن کار دەکات. نموونەکەی ڕێنماییەکە هەر کارێک لەناو بچووکی Commandـدا دەپێچێت کە دەشڵێت ئایا جێبەجێ دەبێت، بە کۆتایی هات یان تەواو بوو، بۆیە دەتوانێت بەبێ ئاڵای زیادە بازنەی چاوەڕوانی یان پیشان بدات.
  • نموونەی لێکۆڵینەوەی ڕێنماییەکە و بە provider بەش دەکات (Provider، ChangeNotifierProvider، context.read) و هەر شاشەیەک لە کاتی دروستکردنی دروست دەکات؛ بە ListenableBuilder گوێ دەگرن. Provider و ناوخۆییەکان پێکەوە کار دەکەن.
Quick checkپشکنینی خێراIn Flutter's architecture guide, what is a command?لە ڕێنمایی Flutterـدا، چییە؟

Flutter's official architecture guideڕێنماییە فەرمییەکەی تەلارسازیی نەرمەواڵەی Flutter

Testing a view model with fakesتاقیکردنەوەی مۆدێلێکی دیمەن بە ئۆبجێکتە ساختەکان

Because the view models receive their services in the constructor, a test can hand them fake services and check the logic with no widget, no server and no service locator — a unit test, the first of the four testing levels. This is the guide's advice, and this test really passed:

چونکە لە وەردەگرن، تاقیکردنەوەیەک دەتوانێت ساختەیان بداتێ و لۆژیکەکە بپشکنێت بەبێ هیچ ، یان — ئەمە ـە، یەکەمی چوار ئاستەکەی تاقیکردنەوە. ئەمە ئامۆژگاریی ڕێنماییەکەیە و ئەم تاقیکردنەوەیە بە ڕاستی سەرکەوتوو بوو:

A unit test of the to-do view modelتاقیکردنەوەیەکی یەکە بۆ مۆدێلی دیمەنی ئەرکەکانtest/todolist_viewmodel_test.dartDART
import 'package:architecture_mvvm/screens/login/login_viewmodel.dart';
import 'package:architecture_mvvm/screens/todolist/todolist_viewmodel.dart';
import 'package:architecture_mvvm/services/todo/todo_service_mock.dart';
import 'package:architecture_mvvm/services/user/user_service_mock.dart';
import 'package:flutter_test/flutter_test.dart';

Future<void> answers() => Future<void>.delayed(Duration.zero);

void main() {
  test('TodoListViewModel follows the logged-in user', () async {
    // Fake services: no server, no widgets, no get_it.
    final login = LoginViewModel(UserServiceMock());
    final todos = TodoListViewModel(TodoServiceMock(), login);
    await answers();
    expect(login.users.map((u) => u.name), ['Aram', 'Shilan', 'Karwan']);

    login.logInAs(login.users[1]); // Shilan
    await answers();
    expect(todos.todos.length, 2);

    await todos.add('Revise chapter 9');
    expect(todos.todos.last.id, 9);
  });
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 13

Fake services in, real view models under test.

ساختە دەچنە ژوورەوە، ڕاستەقینە تاقی دەکرێنەوە.

Quick checkپشکنینی خێراWhy can this test check TodoListViewModel without get_it, widgets or a server?بۆچی ئەم تاقیکردنەوەیە دەتوانێت TodoListViewModel بەبێ ⁦get_it⁩، یان بپشکنێت؟

Common mistakesهەڵە باوەکان

MistakeهەڵەWhat goes wrongچی هەڵە دەبێتFixچارەسەر
A view model that imports material.dart and holds a BuildContext or widgets کە material.dart import دەکات و BuildContext یان هەڵدەگرێتIt now knows the view; it cannot be unit tested or reusedئێستا دەناسێت؛ ناتوانرێت بکرێت یان دووبارە بەکار بهێنرێتەوەImport only foundation.dart; expose state, let the view decide how it looksتەنها foundation.dart import بکە؛ بدە و با بڕیار بدات چۆن دەردەکەوێت
ChangeNotifierProvider(create: (_) => dependency<LoginViewModel>())ChangeNotifierProvider(create: (_) => dependency<LoginViewModel>())The provider disposes the locator's view model when the view leaves; the next screen gets a dead objectproviderـەکە کاتێک دەڕوات dispose دەکات؛ شاشەی داهاتوو مردوو وەردەگرێتUse ChangeNotifierProvider.value(value: …) for objects the locator ownsبۆ ئەو هی ChangeNotifierProvider.value(value: …) بەکار بهێنە
Business logic in the view (filtering, formatting, calling services)لۆژیکی بازرگانی لە (پاڵاوتن، شێوەپێدان، بانگکردنی )Hard to test; the same rule gets copied into several screensتاقیکردنەوەی قورسە؛ هەمان یاسا لە چەند شاشەیەکدا کۆپی دەکرێتMove it into the view model; the view only shows state and sends eventsبیگوازەرەوە بۆ ؛ تەنها پیشان دەدات و دەنێرێت
Public service fields in the view modelخانەی گشتیی لە A view calls the service directly and the view model's state goes out of date ڕاستەوخۆ بانگی دەکات و کۆن دەبێتKeep injected services private (_todoService) تێخراوەکان تایبەت ڕابگرە (_todoService)
addListener on another notifier without removeListener in disposeaddListener لەسەر ChangeNotifierـێکی تر بەبێ removeListener لە disposeـداThe other notifier keeps calling a disposed objectChangeNotifierـەکەی تر بەردەوام بانگی disposeکراو دەکاتAlways pair themهەمیشە جووتیان بکە
Quick checkپشکنینی خێراA teammate writes Color get titleColor => Colors.red; in a view model. What is the better design?هاوتیمێک لە Color get titleColor => Colors.red; دەنووسێت. دیزاینی باشتر چییە؟

Exercisesڕاهێنانەکان

Exerciseڕاهێنان1. Name the part١. ناوی بەشەکە بڵێ

For each piece of the to-do app, say whether it belongs in a view, a view model, a model or a service: (a) cross out completed to-dos; (b) remember which user is logged in; (c) GET /todos?userId=1; (d) the Todo class with id, title and completed; (e) open the to-do screen after a tap; (f) ignore a late answer for a user who logged out.

بۆ هەر پارچەیەکی ئەرکەکان، بڵێ هی ، ، یان : (أ) هێڵ بەسەر ئەرکە تەواوکراوەکاندا بهێنە؛ (ب) لەبیرت بێت کام چووەتە ژوورەوە؛ (ج) GET /todos?userId=1؛ (د) Todo بە id، title و completed؛ (هـ) دوای دەستلێدانێک شاشەی ئەرکەکان بکەرەوە؛ (و) وەڵامێکی دواکەوتوو بۆ کە چووەتە دەرەوە پشتگوێ بخە.

Show answerوەڵامەکە پیشان بدە
  • (a) view (how it looks)
  • (b) view model (state)
  • (c) service (talks to the data source)
  • (d) model
  • (e) view (simple routing)
  • (f) view model (a rule about the state)
  • (أ) (چۆن دەردەکەوێت)
  • (ب) ()
  • (ج) (قسە لەگەڵ دەکات)
  • (د)
  • (هـ) ( سادە)
  • (و) (یاسایەک دەربارەی )
Exerciseڕاهێنان2. Refactor a screen٢. شاشەیەک ڕێک بخەرەوە

A ProfileScreen reads context.watch<ProfileNotifier>() from a ChangeNotifierProvider above MaterialApp. Write the two refactoring steps of this lesson for it: what changes in the service locator, and what does the screen's build return in the end?

ProfileScreenـێک context.watch<ProfileNotifier>() لە ChangeNotifierProviderـێکی سەرووی MaterialAppـەوە دەخوێنێتەوە. دوو هەنگاوەکەی ئەم وانەیە بۆی بنووسە: چی لە دەگۆڕێت و لە کۆتاییدا buildـی شاشەکە چی دەگەڕێنێتەوە؟

Show answerوەڵامەکە پیشان بدە
  1. Step 1: rename it ProfileViewModel, register it with dependency.registerLazySingleton(() => ProfileViewModel(dependency<ProfileService>())), and change the provider above MaterialApp to ChangeNotifierProvider.value(value: dependency<ProfileViewModel>()).

    هەنگاوی ١: ناوی بگۆڕە بۆ ProfileViewModel، بە dependency.registerLazySingleton(() => ProfileViewModel(dependency<ProfileService>())) تۆماری بکە و providerـەکەی سەرووی MaterialApp بگۆڕە بۆ ChangeNotifierProvider.value(value: dependency<ProfileViewModel>()).

  2. Step 2: remove that provider from above MaterialApp. ProfileScreen.build returns ChangeNotifierProvider<ProfileViewModel>.value(value: dependency<ProfileViewModel>(), child: Consumer<ProfileViewModel>(builder: (context, vm, child) => Scaffold(...))).

    هەنگاوی ٢: ئەو providerـە لە سەرووی MaterialApp لابە. ProfileScreen.build ئەمە دەگەڕێنێتەوە: ChangeNotifierProvider<ProfileViewModel>.value(value: dependency<ProfileViewModel>(), child: Consumer<ProfileViewModel>(builder: (context, vm, child) => Scaffold(...))).

Exerciseڕاهێنان3. Without provider٣. بەبێ provider

Rewrite the login view of this lesson without the provider package. What replaces ChangeNotifierProvider.value and Consumer, and how does the view get its view model?

ئەم وانەیە بەبێ provider بنووسەرەوە. چی جێگەی ChangeNotifierProvider.value و Consumer دەگرێتەوە و چۆن وەردەگرێت؟

Show answerوەڵامەکە پیشان بدە
DART
class LoginView extends StatelessWidget {
  const LoginView({super.key, required this.viewModel});

  final LoginViewModel viewModel;

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: viewModel,
      builder: (context, child) => Scaffold(
        appBar: AppBar(title: const Text('Choose a user to log in as')),
        body: ListView(
          children: [
            for (final user in viewModel.users)
              ListTile(
                title: Text(user.name),
                onTap: () => viewModel.logInAs(user),
              ),
          ],
        ),
      ),
    );
  }
}

// in the routes: LoginView(viewModel: dependency<LoginViewModel>())

ListenableBuilder replaces the Consumer, and the view model arrives as a constructor parameter instead of through a provider.

ListenableBuilder جێگەی Consumer دەگرێتەوە و وەک دێت لەبری ئەوەی لە ڕێگەی providerـێکەوە بێت.

Exerciseڕاهێنان4. A test٤. تاقیکردنەوەیەک

Write a unit test (no widgets) that logs in as Aram with the mock services, toggles “Replace light bulb” and checks that it is now completed.

بنووسە (بەبێ ) کە بە ساختەکان وەک ئارام دەچێتە ژوورەوە، «⁦Replace light bulb⁩» دەگۆڕێت و دەپشکنێت کە ئێستا تەواوکراوە.

Show answerوەڵامەکە پیشان بدە
DART
test('toggle marks a to-do as completed', () async {
  final login = LoginViewModel(UserServiceMock());
  final todos = TodoListViewModel(TodoServiceMock(), login);
  await Future<void>.delayed(Duration.zero);

  login.logInAs(login.users.first); // Aram
  await Future<void>.delayed(Duration.zero);

  final bulb = todos.todos.firstWhere((t) => t.title == 'Replace light bulb');
  await todos.toggle(bulb);
  expect(todos.todos.firstWhere((t) => t.id == bulb.id).completed, isTrue);
});

Summaryپوختە

  • In Flutter, views are widgets and view models are ChangeNotifiers that hold the models; services are plain classes behind the view models.
  • The lecture refactors in two steps: (1) move the view models into the service locator (providers use .value); (2) remove the MultiProvider and give each view, or part of a view, its own ChangeNotifierProvider and Consumer.
  • Views send events and show state; view models keep the rules; injected services stay private.
  • A view model works without provider too: a ChangeNotifier plus ListenableBuilder, the way Flutter's docs teach it.
  • Flutter's guide adds repositories (source of truth) between view models and services, and commands for actions; view models are tested with fake services.
  • لە Flutterـدا، و ChangeNotifierن کە هەڵدەگرن؛ ئاسایین لە پشت .
  • وانەکە بە دوو هەنگاو ڕێک دەخاتەوە: ⁦(1)⁩ بگوازەرەوە بۆ ناو (providerـەکان .value بەکار دەهێنن)؛ ⁦(2) MultiProvider⁩ لابە و بە هەر ، یان بەشێکی ، ChangeNotifierProvider و Consumerـی خۆی بدە.
  • دەنێرن و پیشان دەدەن؛ یاساکان دەپارێزن؛ تێخراوەکان تایبەت دەمێننەوە.
  • بەبێ providerـیش کار دەکات: ChangeNotifier لەگەڵ ListenableBuilder، بەو شێوەیەی بەڵگەنامەکانی Flutter فێری دەکەن.
  • ڕێنماییەکەی Flutter (سەرچاوەی ڕاستی) لە نێوان و زیاد دەکات و بۆ کارەکان؛ بە ساختە تاقی دەکرێنەوە.

Lesson complete!وانەکە تەواو بوو!

Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.

Key termsزاراوە سەرەکییەکان

Next lessonوانەی داهاتوو

Organising an MVVM Project: Folders, Services and get_itڕێکخستنی پڕۆژەیەکی MVVM: فۆڵدەرەکان، خزمەتگوزارییەکان و ⁦get_it⁩

References and further readingسەرچاوەکان و خوێندنەوەی زیاتر

This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.