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

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

The lecture's MVVM project structure: lib/app, models, screens (view, view model and widgets per screen), shared widgets and services (an abstract TodoService with REST, mock and Firebase implementations); Dart's file-name style; registering services and view models with get_it (registerLazySingleton) and calling init() in main; swapping the real service for a mock in one line.

پێکهاتەی پڕۆژەی وانەکە: ⁦lib/app⁩، models، screens (، و بۆ هەر شاشەیەک)، هاوبەشەکان و ⁦services (TodoService⁩ـێکی ڕووت بە جێبەجێکردنەکانی REST، ساختە و Firebase)؛ شێوازی ناوی Dart؛ تۆمارکردنی و بە ⁦get_it⁩ (registerLazySingleton) و بانگکردنی init() لە mainـدا؛ گۆڕینەوەی ڕاستەقینەکە بە ساختەیەک لە یەک دێڕدا.

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

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

  • Lay out an MVVM Flutter project in app, models, screens, widgets and services folders.
  • Put a screen's view, view model and private widgets together, and shared widgets apart.
  • Write an abstract service with several implementations and name the files in Dart style.
  • Register services and view models with get_it and explain singleton, lazy singleton and factory.
  • Start the locator in main() and swap a real service for a mock.
  • پڕۆژەیەکی Flutterـی لە app، models، screens، widgets و services ڕێک بخەیت.
  • ، و تایبەتەکانی شاشەیەک پێکەوە دابنێیت و هاوبەشەکان جیا.
  • ڕووت بە چەند جێبەجێکردنێکەوە بنووسیت و ناوی بە شێوازی Dart دابنێیت.
  • و بە ⁦get_it⁩ تۆمار بکەیت و ، دواخراو و factory ڕوون بکەیتەوە.
  • لە main()ـدا دەست پێ بکەیت و ڕاستەقینە بە ساختەیەک بگۆڕیتەوە.

Where does each file go?هەر فایلێک دەچێتە کوێ؟

In MVVM in Flutter with Provider the to-do app became views, view models, models and services. A real app has dozens of them. If they all sit in one folder, nobody finds anything, and two developers keep changing the same file. The lecture's last part answers three practical questions: which folders an MVVM project has, how services are organised so they can be swapped, and how the service locator (the get_it package) is set up and started.

لە MVVM لە Flutterـدا بە Provider ئەرکەکان بوو بە ، ، و . ڕاستەقینە دەیان دانەی لەمانە هەیە. ئەگەر هەموویان لە یەک بن، کەس هیچ شتێک نادۆزێتەوە و دوو بەردەوام هەمان دەگۆڕن. دوایین بەشی وانەکە وەڵامی سێ پرسیاری پراکتیکی دەداتەوە: پڕۆژەیەکی کام هەیە، چۆن ڕێک دەخرێن تا بگۆڕدرێنەوە و ( ⁦get_it⁩) چۆن ئامادە دەکرێت و دەست پێ دەکات.

A pharmacy shelfڕەفەی دەرمانخانە

In a good pharmacy, every medicine has a shelf: painkillers here, syrups there, each brand in its own box. The pharmacist finds anything in seconds, and a new assistant learns the system in a day. A project's folders are the shelves: a fixed place for each kind of file makes the code easy to find, even for a new team member.

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

Where the analogy stops workingسنووری ئەم نموونەیە

Shelves do not choose the medicine; folders do not make bad code good. They only make it easy to find what is there.

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

The top folders of lib/فۆڵدەرەکانی سەرەوەی lib/

All Dart code of a Flutter app lives in lib/. The lecture's MVVM project (architecture_mvvm) puts main.dart there and five folders next to it:

هەموو کۆدی Dartـی Flutter لە lib/ـدا دەژی. پڕۆژەی وانەکە (architecture_mvvm) main.dart و پێنج لە تەنیشتیەوە دادەنێت:

The lecture's top folders (as a tree)فۆڵدەرەکانی سەرەوەی وانەکە (وەک دار)TEXT
architecture_mvvm/
└── lib/
    ├── main.dart
    ├── app/
    │   ├── dependencies.dart          (service registry)
    │   ├── router.dart
    │   └── other_app_wide_stuff.dart
    ├── models/
    │   ├── todo.dart
    │   └── user.dart
    ├── screens/
    ├── widgets/
    └── services/
FolderWhat goes in itچی تێدا دادەنرێت
app/Things for the whole app: dependencies.dart, the service registry (the get_it setup); router.dart, the routes; and any other app-wide code such as the theme.شتی هەموو : dependencies.dart، تۆماری (ئامادەکردنی ⁦get_it⁩)؛ router.dart، ؛ و هەر کۆدێکی تری هەموو وەک .
models/Plain data classes, one file each: todo.dart, user.dart. دراوەی سادە، هەریەکەیان : todo.dart، user.dart.
screens/One folder per screen, with its view and view model. بۆ هەر شاشەیەک، لەگەڵ و .
widgets/Custom widgets shared by several screens. تایبەت کە چەند شاشەیەک بەشیان دەکەن.
services/Code that talks to data sources: REST, Firebase, mocks.ئەو کۆدەی قسە لەگەڵ دەکات: REST، Firebase، ساختە.
A file tree in three columns. Left: lib/ with main.dart ('calls di.init()'), app/ ('app-wide') with dependencies.dart ('get_it'), router.dart and other_app_wide_stuff.dart, models/ ('plain data') with todo.dart and user.dart, and widgets/ ('shared') with user_avatar.dart. Middle: screens/ ('in lib/, one folder per screen') with view.dart and viewmodel.dart ('generic'), login/ (login_view.dart, login_viewmodel.dart, widgets/) and todolist/ (todolist_view.dart, todolist_viewmodel.dart, widgets/ with appbar_view.dart and todo_view.dart). Right: services/ with rest.dart and firebase.dart ('shared'), todo/ ('one folder per service') with todo_service.dart and its rest, mock and firebase versions, and user/ with the same four files. Colours: views blue, view models purple, abstract services orange, implementations green. Footer: file names are lowercase_with_underscores; class names inside them are UpperCamelCase.دارێکی فایل لە سێ ستووندا. چەپ: lib/ لەگەڵ main.dart ('di.init() بانگ دەکات')، app/ ('بۆ هەموو ئەپەکە') لەگەڵ dependencies.dart ('get_it')، router.dart و other_app_wide_stuff.dart، models/ ('دراوەی سادە') لەگەڵ todo.dart و user.dart و widgets/ ('هاوبەش') لەگەڵ user_avatar.dart. ناوەڕاست: screens/ ('لەناو lib/، فۆڵدەرێک بۆ هەر شاشەیەک') لەگەڵ view.dart و viewmodel.dart ('گشتی')، login/ (login_view.dart، login_viewmodel.dart، widgets/) و todolist/ (todolist_view.dart، todolist_viewmodel.dart، widgets/ لەگەڵ appbar_view.dart و todo_view.dart). ڕاست: services/ لەگەڵ rest.dart و firebase.dart ('هاوبەش')، todo/ ('فۆڵدەرێک بۆ هەر خزمەتگوزارییەک') لەگەڵ todo_service.dart و وەشانەکانی rest، mock و firebase و user/ لەگەڵ هەمان چوار فایل. ڕەنگەکان: دیمەنەکان شین، مۆدێلەکانی دیمەن مۆر، خزمەتگوزارییەکان ڕووتەکان پرتەقاڵی، جێبەجێکردنەکان سەوز. خوارەوە: ناوی فایلەکان بە پیتی بچووک و هێڵی ژێرەوەن؛ ناوی کلاسەکان لەناویاندا UpperCamelCaseـن.
The whole MVVM project of the to-do app (all these files exist and pass flutter analyze).هەموو پڕۆژەی ئەرکەکان (هەموو ئەم هەن و ⁦flutter analyze⁩ تێپەڕ دەکەن).CêIT
Quick checkپشکنینی خێراWhere does the get_it setup (init()) of the to-do app live?ئامادەکردنی ⁦get_it⁩ (init())ـی ئەرکەکان لە کوێ دەژی؟

You Need THIS for Big Flutter Projects | Flutter Project StructureLambda Dev · Englishئینگلیزی · 51K viewsبینین · 1.5K likesلایک

Two ways to organise the lib folder: by layer or by feature, plus a shared app/core folder, and when each one fits a project.

دوو شێواز بۆ دابەشکردنی lib: بەپێی یان بەپێی feature، لەگەڵ هاوبەشی app/core و کاتی گونجاوی هەریەکەیان بۆ پرۆژەیەک.

The folder names differ from the lesson's (data, domain, presentation; controller, repository). Watch for the two ideas: by layer and by feature. The lesson's project mixes them: screens by feature, models and services by type.

ناوی لەگەڵ وانەکە جیاوازن (data، domain، presentation؛ controller، repository). سەرنج بدە دوو بیرۆکەکە: بەپێی و بەپێی feature. پرۆژەی وانەکە هەردووکیان تێکەڵ دەکات: شاشەکان بەپێی feature، و بەپێی جۆر.

The screens folder: a folder per screenفۆڵدەری screens: فۆڵدەرێک بۆ هەر شاشەیەک

The lecture's screens/ and widgets/ foldersفۆڵدەرەکانی screens/ و widgets/ـی وانەکەTEXT
screens/
├── view.dart                    (generic view class)
├── viewmodel.dart               (generic view model class)
├── login/
│   ├── login_view.dart
│   ├── login_viewmodel.dart
│   └── widgets/
├── todolist/
│   ├── todolist_view.dart
│   ├── todolist_viewmodel.dart
│   └── widgets/
│       ├── appbar_view.dart
│       └── todo_view.dart
└── other_screen/
    ├── other_view.dart
    ├── other_viewmodel.dart
    └── widgets/
widgets/                         (shared widgets)
  • Each screen has its own folder (login/, todolist/).
  • A screen's view and view model sit in the same folder, so you open one folder to work on one screen.
  • The widgets/ folder inside a screen folder is for widgets refactored out of that screen, when a widget's code is too large to stay in the screen's file (our appbar_view.dart and todo_view.dart).
  • view.dart and viewmodel.dart hold generic classes used to build every screen.
  • The widgets/ folder outside (in lib/) holds custom widgets shared between screens, such as our UserAvatar.
  • هەر شاشەیەک خۆی هەیە (login/، todolist/).
  • و شاشەیەک لە هەمان ، بۆیە بۆ کارکردن لەسەر یەک شاشە یەک دەکەیتەوە.
  • widgets/ لەناو شاشەیەکدا بۆ ئەو کە لە شاشەکە دەرهێنراون، کاتێک کۆدی زۆر گەورەیە بۆ مانەوە لە شاشەکەدا (appbar_view.dart و todo_view.dartـی ئێمە).
  • view.dart و viewmodel.dart گشتی هەڵدەگرن کە بۆ دروستکردنی هەموو شاشەیەک بەکار دێن.
  • widgets/ـی دەرەوە (لە lib/ـدا) تایبەت هەڵدەگرێت کە لە نێوان شاشەکاندا هاوبەشن، وەک UserAvatarـی ئێمە.

Two small examples. TodoView, one row of the to-do list, was moved out of the to-do screen into that screen's own widgets/ folder. UserAvatar is used by both screens, so it lives in the shared lib/widgets/ folder:

دوو نموونەی بچووک. TodoView، یەک دێڕی لیستی ئەرکەکان، لە شاشەی ئەرکەکانەوە گوازرایەوە بۆ widgets/ـی خودی ئەو شاشەیە. UserAvatar هەردوو شاشەکە بەکاری دەهێنن، بۆیە لە هاوبەشی lib/widgets/ـدا دەژی:

A widget refactored out of the to-do screenویجێتێک کە لە شاشەی ئەرکەکان دەرهێنراوەscreens/todolist/widgets/todo_view.dartDART
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../../../models/todo.dart';
import '../todolist_viewmodel.dart';

// One to-do row. Taps become events for the view model.
class TodoView extends StatelessWidget {
  const TodoView(this.todo, {super.key});

  final Todo todo;

  @override
  Widget build(BuildContext context) {
    final viewModel = context.read<TodoListViewModel>();
    return ListTile(
      title: Text(
        todo.title,
        style: TextStyle(
          decoration: todo.completed ? TextDecoration.lineThrough : null,
        ),
      ),
      subtitle: Text('id: ${todo.id}'),
      onTap: () => viewModel.toggle(todo),
      onLongPress: () => viewModel.delete(todo),
    );
  }
}
A shared widgetویجێتێکی هاوبەشwidgets/user_avatar.dartDART
import 'package:flutter/material.dart';

import '../models/user.dart';

// Shared by the login screen and the to-do screen.
class UserAvatar extends StatelessWidget {
  const UserAvatar(this.user, {super.key});

  final User user;

  @override
  Widget build(BuildContext context) {
    return CircleAvatar(child: Text(user.name[0]));
  }
}
Quick checkپشکنینی خێراA PriceTag widget is used on the product screen and the cart screen. Where does its file go? PriceTag لە شاشەی کاڵا و شاشەی سەبەتەدا بەکار دێت. دەچێتە کوێ؟

The screens folder: a folder per screenفۆڵدەری screens: فۆڵدەرێک بۆ هەر شاشەیەک

The generic classes: ViewModel and BaseViewکلاسە گشتییەکان: ViewModel و BaseView

Here are the two generic classes. ViewModel is the base of every view model: a ChangeNotifier with a busy flag. BaseView gives any view its own ChangeNotifierProvider and Consumer in one line, which is exactly the pattern of step 2 written once:

ئەمە دوو گشتییەکەن. ViewModel بنەمای هەموو : ChangeNotifierـێک بە ئاڵای busy. BaseView بە یەک دێڕ ChangeNotifierProvider و Consumerـی خۆی دەدات بە هەر ، کە ڕێک شێوازی هەنگاوی ٢ـیە کە یەک جار نووسراوە:

The generic view modelمۆدێلی دیمەنی گشتیscreens/viewmodel.dartDART
import 'package:flutter/foundation.dart';

// The generic view model: every screen's view model extends it.
abstract class ViewModel extends ChangeNotifier {
  bool _busy = false;

  bool get busy => _busy; // true while data is loading

  @protected
  void setBusy(bool value) {
    _busy = value;
    notifyListeners();
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 9

@protected: only subclasses (the real view models) may set it.

@protected: تەنها ( ڕاستەقینەکان) دەتوانن دایبنێن.

The generic viewدیمەنی گشتیscreens/view.dartDART
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

import 'viewmodel.dart';

// The generic view: gives one view its own provider and consumer.
// (Not named View: Flutter already has a widget called View.)
class BaseView<T extends ViewModel> extends StatelessWidget {
  const BaseView({super.key, required this.viewModel, required this.builder});

  final T viewModel; // taken from the service locator
  final Widget Function(BuildContext context, T viewModel, Widget? child)
      builder;

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<T>.value(
      value: viewModel, // .value: the locator owns it, not this widget
      child: Consumer<T>(builder: builder),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 8

Generic: T is the view model type of the screen that uses it.

گشتی: T جۆری ئەو شاشەیەیە کە بەکاری دەهێنێت.

Lineدێڕی 18

.value, because the service locator owns the view model.

.value، چونکە خاوەنی .

With BaseView, a screen's view is short. It asks the service locator for its view model and builds itself from it:

بە BaseView، شاشەیەک کورتە. داوای لە دەکات و لێیەوە خۆی دروست دەکات:

The login view, built on BaseViewدیمەنی چوونەژوورەوە، لەسەر BaseViewscreens/login/login_view.dartDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
import 'package:flutter/material.dart';

import '../../app/dependencies.dart';
import '../../app/router.dart' as router;
import '../../widgets/user_avatar.dart';
import '../view.dart';
import 'login_viewmodel.dart';

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

  @override
  Widget build(BuildContext context) {
    return BaseView<LoginViewModel>(
      viewModel: dependency<LoginViewModel>(),
      builder: (context, viewModel, _) => 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: UserAvatar(user),
                      title: Text(user.name),
                      subtitle: Text('user id: ${user.id}'),
                      onTap: () {
                        viewModel.logInAs(user); // an event for the view model
                        Navigator.of(context).pushNamed(router.todoList);
                      },
                    ),
                ],
              ),
      ),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 15

The view model comes from the service locator.

لە دێت.

Lineدێڕی 24

A shared widget from lib/widgets/.

هاوبەش لە lib/widgets/ـەوە.

Lineدێڕی 29

Routes come from app/router.dart.

لە app/router.dartـەوە دێن.

Quick checkپشکنینی خێراWhy is the lecture's generic view class called BaseView in this course?بۆچی گشتیی وانەکە لەم کۆرسەدا ناوی BaseViewـە؟

The services folder: one abstract class, several implementationsفۆڵدەری services: کلاسێکی ڕووت، چەند جێبەجێکردنێک

The lecture's services/ folder (file names in Dart style)فۆڵدەری services/ـی وانەکە (ناوی فایلەکان بە شێوازی Dart)TEXT
services/
├── rest.dart
├── firebase.dart
├── todo/
│   ├── todo_service.dart            (abstract class)
│   ├── todo_service_rest.dart       (implementation class)
│   ├── todo_service_mock.dart       (implementation class)
│   └── todo_service_firebase.dart   (implementation class)
└── user/
    ├── user_service.dart            (abstract class)
    ├── user_service_rest.dart       (implementation class)
    ├── user_service_mock.dart       (implementation class)
    └── user_service_firebase.dart   (implementation class)
  • Shared services sit at the root of services/: rest.dart (talking to the REST server) and firebase.dart (talking to Firebase) are used by every service.
  • A service may have different implementations, such as the actual one and a mock one.
  • Thus, each service gets its own folder (todo/, user/), holding the abstract class and all its implementations.
  • Services are registered in the service locator, in app/dependencies.dart.
  • هاوبەشەکان لە ڕەگی services/ـدان: rest.dart (قسەکردن لەگەڵ REST) و firebase.dart (قسەکردن لەگەڵ Firebase) هەموو بەکاریان دەهێنێت.
  • دەکرێت چەند جێبەجێکردنێکی جیاوازی هەبێت، وەک ڕاستەقینەکە و ساختەیەک.
  • بۆیە هەر خۆی وەردەگرێت (todo/، user/)، کە و هەموو جێبەجێکردنەکانی هەڵدەگرێت.
  • لە تۆمار دەکرێن، لە app/dependencies.dartـدا.

The abstract class is a promise: it lists what every to-do service can do, without any code. The view models depend only on it (abstract classes):

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

The abstract TodoServiceTodoServiceـی ڕووتservices/todo/todo_service.dartDART
import '../../models/todo.dart';

// What every to-do service can do. No code here: only the promises.
abstract class TodoService {
  Future<List<Todo>> getTodoList(int userId);
  Future<Todo> addTodo(int userId, String title);
  Future<void> updateTodo(Todo todo);
  Future<void> deleteTodo(int id);
}
An orange box 'abstract class TodoService' with four methods: getTodoList(userId), addTodo(userId, title), updateTodo(todo), deleteTodo(id). Three boxes below are joined to it by dashed lines with a hollow triangle (implements): TodoServiceRest ('talks to a REST server', highlighted as the one plugged in), TodoServiceMock ('talks to a list in memory') and TodoServiceFirebase ('talks to Cloud Firestore'). On the left, TodoListViewModel 'knows only the abstract class' points to TodoService. On the right, app/dependencies.dart with registerLazySingleton<TodoService>(() => TodoServiceRest(…)) 'chooses which one is plugged in', with a dashed arrow to TodoServiceRest.سندووقێکی پرتەقاڵی 'abstract class TodoService' لەگەڵ چوار میتۆد: getTodoList(userId)، addTodo(userId, title)، updateTodo(todo)، deleteTodo(id). سێ سندووق لە خوارەوە بە هێڵی پچڕپچڕ و سێگۆشەیەکی بەتاڵ (implements) پێیەوە بەستراون: TodoServiceRest ('قسە لەگەڵ ڕاژەکارێکی REST دەکات'، وەک ئەوەی دانراوە دیار کراوە)، TodoServiceMock ('قسە لەگەڵ لیستێک لە بیرگەدا دەکات') و TodoServiceFirebase ('قسە لەگەڵ Cloud Firestore دەکات'). لای چەپ، TodoListViewModel 'تەنها ئەم کلاسی ڕووتە دەناسێت' ئاماژە بە TodoService دەکات. لای ڕاست، app/dependencies.dart لەگەڵ registerLazySingleton<TodoService>(() => TodoServiceRest(…)) 'دیاری دەکات کامیان دابنرێت'، بە تیرێکی پچڕپچڕ بۆ TodoServiceRest.
One abstract service, three implementations; get_it decides which one the app uses. ڕووت، سێ جێبەجێکردن؛ ⁦get_it⁩ بڕیار دەدات کامیان بەکار بهێنێت.CêIT
Quick checkپشکنینی خێراTodoListViewModel's constructor takes a TodoService. Why not a TodoServiceRest? TodoListViewModel TodoServiceـێک وەردەگرێت. بۆچی TodoServiceRest نا؟

The services folder: one abstract class, several implementationsفۆڵدەری services: کلاسێکی ڕووت، چەند جێبەجێکردنێک

Three implementations of one promiseسێ جێبەجێکردنی یەک بەڵێن

Each implementation keeps the same promises in its own way. The REST one asks the server through the shared RestService:

هەر جێبەجێکردنێک هەمان بەڵێنەکان بە ڕێگای خۆی دەپارێزێت. ئەوەی REST لە ڕێگەی RestServiceـی هاوبەشەوە داوا لە دەکات:

The real one: TodoServiceRestڕاستەقینەکە: TodoServiceRestservices/todo/todo_service_rest.dartDART
import '../../models/todo.dart';
import '../rest.dart';
import 'todo_service.dart';

// The real one: talks to the REST server.
class TodoServiceRest implements TodoService {
  TodoServiceRest(this._rest);

  final RestService _rest;

  @override
  Future<List<Todo>> getTodoList(int userId) async {
    final json = await _rest.get('todos?userId=$userId') as List<dynamic>;
    return [for (final item in json) Todo.fromJson(item as Map<String, dynamic>)];
  }

  @override
  Future<Todo> addTodo(int userId, String title) async {
    final json = await _rest.post(
      'todos',
      {'userId': userId, 'title': title, 'completed': false},
    );
    return Todo.fromJson(json as Map<String, dynamic>);
  }

  @override
  Future<void> updateTodo(Todo todo) =>
      _rest.patch('todos/${todo.id}', todo.toJson());

  @override
  Future<void> deleteTodo(int id) => _rest.delete('todos/$id');
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 6

implements: it must provide every method of TodoService.

implements: دەبێت هەموو TodoService دابین بکات.

Lineدێڕی 13

The server answers with JSON; Todo.fromJson turns it into models.

بە وەڵام دەداتەوە؛ Todo.fromJson دەیگۆڕێت بۆ .

The fake one: TodoServiceMockساختەکە: TodoServiceMockservices/todo/todo_service_mock.dartDART
import '../../models/todo.dart';
import 'todo_service.dart';

// A fake one: keeps the lecture's sample to-dos in memory.
class TodoServiceMock implements TodoService {
  final List<Todo> _todos = [
    const Todo(id: 1, userId: 1, title: 'Prepare proposal for the new project'),
    const Todo(id: 2, userId: 1, title: 'Replace light bulb'),
    const Todo(id: 3, userId: 1, title: 'Buy Flutter eBook'),
    const Todo(id: 4, userId: 1, title: 'Subscribe to fibre-optic internet'),
    const Todo(id: 5, userId: 1, title: 'Set up online meeting room', completed: true),
    const Todo(id: 6, userId: 2, title: 'New task'),
    const Todo(id: 8, userId: 2, title: 'New task', completed: true),
  ];

  @override
  Future<List<Todo>> getTodoList(int userId) async =>
      _todos.where((t) => t.userId == userId).toList();

  @override
  Future<Todo> addTodo(int userId, String title) async {
    final id = _todos.fold(0, (max, t) => t.id > max ? t.id : max) + 1;
    final todo = Todo(id: id, userId: userId, title: title);
    _todos.add(todo);
    return todo;
  }

  @override
  Future<void> updateTodo(Todo todo) async {
    final i = _todos.indexWhere((t) => t.id == todo.id);
    if (i != -1) _todos[i] = todo;
  }

  @override
  Future<void> deleteTodo(int id) async => _todos.removeWhere((t) => t.id == id);
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 5

Same promises, no network: perfect for tests and for working without a server.

هەمان بەڵێنەکان، بەبێ : باشترینە بۆ تاقیکردنەوە و بۆ کارکردن بەبێ .

The Firebase one keeps the same promises with Cloud Firestore (set up as in Firebase and Cloud Firestore):

ئەوەی Firebase هەمان بەڵێنەکان بە ⁦Cloud Firestore⁩ دەپارێزێت (ئامادەکراو وەک لە Firebase و ⁦Cloud Firestore⁩):

Another real one: TodoServiceFirebaseڕاستەقینەیەکی تر: TodoServiceFirebaseservices/todo/todo_service_firebase.dartDART
import '../../models/todo.dart';
import '../firebase.dart';
import 'todo_service.dart';

// Another real one: the same promises, kept with Cloud Firestore.
class TodoServiceFirebase implements TodoService {
  TodoServiceFirebase(this._firebase);

  final FirebaseService _firebase;

  @override
  Future<List<Todo>> getTodoList(int userId) async {
    final snapshot = await _firebase.db
        .collection('todos')
        .where('userId', isEqualTo: userId)
        .get();
    return [for (final doc in snapshot.docs) Todo.fromJson(doc.data())];
  }

  @override
  Future<Todo> addTodo(int userId, String title) async {
    final id = DateTime.now().millisecondsSinceEpoch;
    final todo = Todo(id: id, userId: userId, title: title);
    await _firebase.db.collection('todos').doc('$id').set(todo.toJson());
    return todo;
  }

  @override
  Future<void> updateTodo(Todo todo) =>
      _firebase.db.collection('todos').doc('${todo.id}').update(todo.toJson());

  @override
  Future<void> deleteTodo(int id) =>
      _firebase.db.collection('todos').doc('$id').delete();
}
Quick checkپشکنینی خێراTodoServiceMock.getTodoList uses no network. Why does it still return a Future?TodoServiceMock.getTodoList هیچ بەکار ناهێنێت. بۆچی هێشتا Futureـێک دەگەڕێنێتەوە؟

The services folder: one abstract class, several implementationsفۆڵدەری services: کلاسێکی ڕووت، چەند جێبەجێکردنێک

Shared helpers and the model's JSONیاریدەدەرە هاوبەشەکان و JSONـی مۆدێلەکە

The shared rest.dart and firebase.dart at the root of services/ are short helper classes used by every service: RestService wraps the http package and checks status codes; FirebaseService gives access to Firestore. The REST and Firebase services also need the model to turn JSON into a Todo and back, so models/todo.dart gets fromJson and toJson:

rest.dart و firebase.dartـی هاوبەش لە ڕەگی services/ـدا یاریدەدەری کورتن کە هەموو بەکاریان دەهێنێت: RestService http دەپێچێتەوە و دەپشکنێت؛ FirebaseService دەستگەیشتن بە Firestore دەدات. REST و Firebase پێویستیان بە هەیە بۆ گۆڕینی بۆ Todo و بە پێچەوانەوە، بۆیە models/todo.dart fromJson و toJson وەردەگرێت:

The shared RestServiceRestServiceـی هاوبەشservices/rest.dartDART
import 'dart:convert';

import 'package:http/http.dart' as http;

// Shared by every REST service: one place for the server address.
class RestService {
  RestService({this.baseUrl = 'https://todo-api.example.com'});

  final String baseUrl;
  final http.Client _client = http.Client();

  Future<dynamic> get(String path) async {
    final response = await _client.get(Uri.parse('$baseUrl/$path'));
    return _decode(response, 'GET $path');
  }

  Future<dynamic> post(String path, Map<String, dynamic> body) async {
    final response = await _client.post(
      Uri.parse('$baseUrl/$path'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(body),
    );
    return _decode(response, 'POST $path');
  }

  Future<dynamic> patch(String path, Map<String, dynamic> body) async {
    final response = await _client.patch(
      Uri.parse('$baseUrl/$path'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(body),
    );
    return _decode(response, 'PATCH $path');
  }

  Future<void> delete(String path) async {
    final response = await _client.delete(Uri.parse('$baseUrl/$path'));
    _decode(response, 'DELETE $path');
  }

  dynamic _decode(http.Response response, String what) {
    if (response.statusCode < 200 || response.statusCode >= 300) {
      throw Exception('$what failed: HTTP ${response.statusCode}');
    }
    return response.body.isEmpty ? null : jsonDecode(response.body);
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 7

One place for the server address (example.com is a placeholder).

یەک شوێن بۆ ناونیشانی (⁦example.com⁩ شوێنگرە).

Lineدێڕی 41

Any status outside 2xx becomes an exception.

هەر لە دەرەوەی ⁦2xx⁩ دەبێت بە .

The shared FirebaseServiceFirebaseServiceـی هاوبەشservices/firebase.dartDART
import 'package:cloud_firestore/cloud_firestore.dart';

// Shared by every Firebase service. Firebase.initializeApp() must have run
// in main() before this is used.
class FirebaseService {
  FirebaseFirestore get db => FirebaseFirestore.instance;
}
The Todo model with its JSON methodsمۆدێلی Todo لەگەڵ میتۆدەکانی JSONmodels/todo.dartDART
class Todo {
  const Todo({
    required this.id,
    required this.userId,
    required this.title,
    this.completed = false,
  });

  final int id;
  final int userId;
  final String title;
  final bool completed;

  Todo copyWith({bool? completed}) => Todo(
        id: id,
        userId: userId,
        title: title,
        completed: completed ?? this.completed,
      );

  factory Todo.fromJson(Map<String, dynamic> json) => Todo(
        id: json['id'] as int,
        userId: json['userId'] as int,
        title: json['title'] as String,
        completed: json['completed'] as bool,
      );

  Map<String, dynamic> toJson() =>
      {'id': id, 'userId': userId, 'title': title, 'completed': completed};
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 21-26

JSON from the server → a Todo (explained in HTTP and JSON).

دەبێت بە Todo (لە HTTP و JSONـدا ڕوون کراوەتەوە).

Lineدێڕی 28-29

A Todo → JSON, for POST, PATCH and Firestore.

Todo دەبێت بە ، بۆ POST، PATCH و Firestore.

Quick checkپشکنینی خێراThe server answers GET todos?userId=1 with status 404. What does RestService.get do? بە ⁦404⁩ وەڵامی GET todos?userId=1 دەداتەوە. RestService.get چی دەکات؟

The services folder: one abstract class, several implementationsفۆڵدەری services: کلاسێکی ڕووت، چەند جێبەجێکردنێک

File names and class namesناوی فایلەکان و ناوی کلاسەکان

The lecture's services tree writes file names like the classes inside them: TodoService.dart, TodoServiceRest.dart. Dart's official style guide, Effective Dart, has a different rule for each kind of name, and the analyzer's lints follow it:

وانەکە ناوی وەک ئەو ناویان دەنووسێت: TodoService.dart، TodoServiceRest.dart. ڕێنمایی شێوازی فەرمیی Dart، ⁦Effective Dart⁩، یاسایەکی جیاوازی بۆ هەر جۆرە ناوێک هەیە و lintـەکانی analyzer پەیڕەوی دەکەن:

Kind of nameجۆری ناوStyleشێوازGoodباشBadخراپ
Files, folders, packages، ، پاکێجەکان: lowercase with underscores: پیتی بچووک لەگەڵ هێڵی ژێرەوەtodo_service_rest.dartTodoServiceRest.dart, todo-service.dart
Classes, enums, typedefs، enumـەکان، typedefـەکانUpperCamelCaseTodoServiceResttodoServiceRest
Variables, functions, parameters، ، (lowerCamelCase) ⁦(lowerCamelCase)⁩dependency, getTodoListDependency, get_todo_list
Import prefixesپێشگرەکانی importlowercase with underscoresپیتی بچووک لەگەڵ هێڵی ژێرەوەas di, as routeras DI, as Router
Quick checkپشکنینی خێراWhich file name follows Effective Dart?کام ناوی ⁦Effective Dart⁩ پەیڕەو دەکات؟

get_it: the service locator⁦get_it⁩: دۆزەرەوەی خزمەتگوزارییەکە

A service locator is one central place where the app registers its services (and, in MVVM, its view models) and where any code can find them by type. get_it is the package the lecture uses for it. Add it with flutter pub add get_it. Unlike provider, it does not live in the widget tree and needs no BuildContext: you can ask it from anywhere, even from a view model.

یەک شوێنی ناوەندییە کە تێدا تۆمار دەکات (و لە ) و هەر کۆدێک دەتوانێت بەپێی جۆر بیاندۆزێتەوە. ⁦get_it⁩ ئەو کە وانەکە بۆی بەکار دەهێنێت. بە flutter pub add get_it زیادی بکە. بە پێچەوانەی provider، لە ناژی و پێویستی بە BuildContext نییە: دەتوانیت لە هەر شوێنێکەوە داوای لێ بکەیت، تەنانەت لە .

Here is the to-do app's app/dependencies.dart, the lecture's “register dependencies to the service locator” slide written out in full:

ئەمە app/dependencies.dartـی ئەرکەکانە، سلایدی « لە تۆمار بکە»ـی وانەکە بە تەواوی نووسراوە:

Setting up the service locatorئامادەکردنی دۆزەرەوەی خزمەتگوزارییەکەapp/dependencies.dartDART
import 'package:get_it/get_it.dart';

import '../screens/login/login_viewmodel.dart';
import '../screens/todolist/todolist_viewmodel.dart';
import '../services/rest.dart';
import '../services/todo/todo_service.dart';
import '../services/todo/todo_service_rest.dart';
import '../services/user/user_service.dart';
import '../services/user/user_service_rest.dart';

final dependency = GetIt.instance; // the service locator

void init() {
  // Services
  dependency.registerLazySingleton(() => RestService());
  dependency.registerLazySingleton<TodoService>(
    () => TodoServiceRest(dependency<RestService>()),
  );
  dependency.registerLazySingleton<UserService>(
    () => UserServiceRest(dependency<RestService>()),
  );

  // View models
  dependency.registerLazySingleton(
    () => LoginViewModel(dependency<UserService>()),
  );
  dependency.registerLazySingleton(
    () => TodoListViewModel(
      dependency<TodoService>(),
      dependency<LoginViewModel>(),
    ),
  );
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 11

The one global GetIt object (the slide writes GetIt dependency = …; final keeps anyone from replacing it).

تاکە گشتیی GetIt (سلایدەکە GetIt dependency = … دەنووسێت؛ final ڕێگە نادات کەس بیگۆڕێتەوە).

Lineدێڕی 15

A lazy singleton: created on first use, then shared.

دواخراو: لە یەکەم بەکارهێناندا دروست دەکرێت، پاشان بەش دەکرێت.

Lineدێڕی 16

Registered under the abstract type: code asks for TodoService…

بە جۆرە ڕووتەکە تۆمار کراوە: کۆد داوای TodoService دەکات…

Lineدێڕی 17

…and gets this implementation, built with the shared RestService.

…و ئەم جێبەجێکردنە وەردەگرێت، کە بە RestServiceـی هاوبەش دروست کراوە.

Lineدێڕی 25

View models get their services from the same locator.

لە هەمان وەردەگرن.

  • registerLazySingleton<TodoService>(() => TodoServiceRest(…)): the type in angle brackets is what code asks for; the function says what it gets.
  • dependency<TodoService>() (or dependency.get<TodoService>()) returns the registered object. Asking for a type that was never registered throws an error.
  • Registering the same type twice is an error too (see below), which protects you from two different services fighting.
  • registerLazySingleton<TodoService>(() => TodoServiceRest(…)): جۆرەکەی ناو کەوانە گۆشەییەکان ئەوەیە کە کۆد داوای دەکات؛ دەڵێت چی وەردەگرێت.
  • dependency<TodoService>() (یان dependency.get<TodoService>()) تۆمارکراوەکە دەگەڕێنێتەوە. داواکردنی جۆرێک کە هەرگیز تۆمار نەکراوە دروست دەکات.
  • تۆمارکردنی هەمان جۆر دوو جار (لە خوارەوە بڕوانە)، کە دەتپارێزێت لە ململانێی دوو جیاواز.
Quick checkپشکنینی خێراAfter registerLazySingleton<UserService>(() => UserServiceRest(…)), what does dependency<UserService>() return?دوای registerLazySingleton<UserService>(() => UserServiceRest(…))، dependency<UserService>() چی دەگەڕێنێتەوە؟

get_it (Package of the Week)Flutter · Englishئینگلیزی · 144K viewsبینین · 3.1K likesلایک

Flutter's official 80-second tour of get_it: register an object once, get it from anywhere, create it only when needed, and swap objects for tests.

ناساندنی فەرمیی get_it لە ⁦80⁩ چرکەدا: یەک جار تێیدا دابنێ، لە هەر شوێنێکەوە وەریبگرە، تەنها کاتی پێویست دروستی بکە و بۆ بگۆڕەوە.

A very short overview; the lesson explains why a service locator helps. The video's 'lazy initialization' uses registerFactory (a new object on every get); the lesson's table compares it with registerLazySingleton.

پوختەیەکی زۆر کورت؛ وانەکە ڕوون دەکاتەوە بۆچی سوودی هەیە. بەشی lazy initialization لە ڤیدیۆکەدا registerFactory بەکار دەهێنێت (لە هەر getـێکدا نوێ)؛ وانەکە لە بەراوردێکدا لەگەڵ registerLazySingleton دایدەنێت.

get_it: the service locator⁦get_it⁩: دۆزەرەوەی خزمەتگوزارییەکە

Singleton, lazy singleton or factory?تاکنموونە، تاکنموونەی دواخراو یان factory؟

get_it can create an object at three different moments. The lecture uses registerLazySingleton for everything. This small Dart program shows all three, each class printing a line when it is created:

⁦get_it⁩ دەتوانێت لە سێ کاتی جیاوازدا دروست بکات. وانەکە بۆ هەموو شتێک registerLazySingleton بەکار دەهێنێت. ئەم بچووکی Dart هەر سێکیان پیشان دەدات، هەر کاتێک دروست دەکرێت دێڕێک چاپ دەکات:

Three ways to registerسێ ڕێگای تۆمارکردنbin/lazy.dartDARTType in a terminalلە تێرمیناڵدا بینووسە
import 'package:get_it/get_it.dart';

final dependency = GetIt.instance;

class RestService {
  RestService() {
    print('  RestService created');
  }
}

class Clock {
  Clock() {
    print('  Clock created');
  }
}

class Order {
  Order() {
    print('  Order created');
  }
}

void main() {
  print('1. register');
  dependency.registerSingleton(Clock()); // made now
  dependency.registerLazySingleton(() => RestService()); // made on first use
  dependency.registerFactory(() => Order()); // made on every use

  print('2. first use');
  final a = dependency<RestService>();
  dependency<Clock>();
  final o1 = dependency<Order>();

  print('3. second use');
  final b = dependency<RestService>();
  final o2 = dependency<Order>();

  print('same RestService? ${identical(a, b)}');
  print('same Order? ${identical(o1, o2)}');
}
RegistrationتۆمارکردنCreated when?کەی دروست دەکرێت؟How many?چەند دانە؟Good forباشە بۆ
registerSingleton(Clock())At once, during registrationیەکسەر، لە کاتی تۆمارکردنداOne, sharedیەک، هاوبەشSomething needed from the very startشتێک کە لە سەرەتاوە پێویستە
registerLazySingleton(() => RestService())On first useلە یەکەم بەکارهێنانداOne, sharedیەک، هاوبەشServices and view models (the lecture's choice) و (هەڵبژاردنی وانەکە)
registerFactory(() => Order())On every useلە هەموو بەکارهێنانێکداA new one each timeهەر جارێک یەکێکی نوێShort-lived objects, such as one form's data کورتخایەنەکان، وەک دراوەی یەک فۆرم
Quick checkپشکنینی خێراTwo views call dependency<LoginViewModel>(), registered with registerLazySingleton. What do they get?دوو dependency<LoginViewModel>() بانگ دەکەن کە بە registerLazySingleton تۆمار کراوە. چی وەردەگرن؟

Calling init() in main()بانگکردنی ⁦init()⁩ لە ⁦main()⁩ـدا

The locator must be filled before the first widget asks it for anything, so main() calls init() before runApp. The lecture imports app/dependencies.dart with the prefix di (dependency injection) and the router with the prefix router. Here is our final main.dart, with every screen getting its own provider:

دەبێت پێش ئەوەی یەکەم داوای هەر شتێکی لێ بکات پڕ بکرێتەوە، بۆیە main() پێش runApp init() بانگ دەکات. وانەکە app/dependencies.dart بە پێشگری di () و router بە پێشگری router import دەکات. ئەمە main.dartـی کۆتایی ئێمەیە، کە هەموو شاشەیەک providerـی خۆی وەردەگرێت:

main.dartفایلی ⁦main.dart⁩main.dartDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
import 'package:flutter/material.dart';

import 'app/dependencies.dart' as di;
import 'app/router.dart' as router;

void main() {
  di.init(); // fill the service locator before the first widget is built

  runApp(const TodoApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MVVM to-do',
      theme: ThemeData(colorSchemeSeed: Colors.green),
      initialRoute: router.login,
      routes: router.routes,
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 3

as di: di.init() and di.dependency say clearly where they come from.

as di: di.init() و di.dependency بە ڕوونی دەڵێن لە کوێوە دێن.

Lineدێڕی 7

First fill the locator…

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

Lineدێڕی 9

…then start the app. No providers above MaterialApp any more.

…پاشان دەست پێ بکە. چیتر هیچ providerـێک لە سەرووی MaterialAppـەوە نییە.

app/router.dartفایلی ⁦app/router.dart⁩app/router.dartDART
import 'package:flutter/widgets.dart';

import '../screens/login/login_view.dart';
import '../screens/todolist/todolist_view.dart';

const login = '/';
const todoList = '/todos';

final Map<String, WidgetBuilder> routes = {
  login: (_) => const LoginView(),
  todoList: (_) => const TodoListView(),
};

The lecture's slide shows an earlier step, cut off after a few lines: services already come from get_it, but the state still sits in providers above MaterialApp, and the DI calls changed from Provider.of() to the locator. Written out and corrected, it looks like this:

سلایدەکەی وانەکە هەنگاوێکی پێشتر پیشان دەدات، کە دوای چەند دێڕێک بڕاوە: پێشتر لە ⁦get_it⁩ـەوە دێن، بەڵام هێشتا لە providerـەکانی سەرووی MaterialAppـدایە و بانگکردنەکانی لە Provider.of()ـەوە گۆڕاون بۆ . بە تەواوی نووسراو و چاککراو، بەم شێوەیە دەبێت:

The lecture's first step, completedهەنگاوی یەکەمی وانەکە، تەواوکراو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 'models/user.dart';
import 'services/user/user_service.dart';

// The lecture's first step: services come from get_it,
// app state still lives in providers above MaterialApp.
void main() {
  di.init();

  runApp(
    MultiProvider(
      providers: [
        FutureProvider<List<User>>(
          create: (_) => di.dependency<UserService>().getUserList(),
          initialData: const [], // required since provider 5
        ),
        ChangeNotifierProvider(create: (_) => ValueNotifier<User?>(null)),
      ],
      child: const MaterialApp(home: UserListScreen()),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    final users = context.watch<List<User>>();
    return Scaffold(
      appBar: AppBar(title: const Text('Choose a user to log in as')),
      body: ListView(
        children: [for (final u in users) ListTile(title: Text(u.name))],
      ),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 17

The FutureProvider gets its service from the locator, not from another provider.

FutureProvider لە وەردەگرێت، نەک لە providerـێکی ترەوە.

Lineدێڕی 18

Missing on the slide: required since provider 5.

لەسەر سلایدەکە نییە: لە ⁦provider 5⁩ـەوە پێویستە.

Quick checkپشکنینی خێراWhat happens if runApp runs before di.init()?چی ڕوو دەدات ئەگەر runApp پێش di.init() جێبەجێ بێت؟

Swapping the real service for a fake oneگۆڕینەوەی خزمەتگوزارییە ڕاستەقینەکە بە ساختەیەک

Now the payoff of the whole structure. The app talks to the server only through TodoService, and only init() says which implementation that is. Change one line in init(), restart the app, and it runs on fake data, with no server at all:

ئێستا بەرهەمی هەموو پێکهاتەکە. تەنها لە ڕێگەی TodoServiceـەوە قسە لەگەڵ دەکات و تەنها init() دەڵێت ئەوە کام جێبەجێکردنە. یەک دێڕ لە init()ـدا بگۆڕە، دووبارە دەست پێ بکەرەوە و لەسەر دراوەی ساختە کار دەکات، بەبێ هیچ :

Animation in three frames. Left: app/dependencies.dart with init(). Right: a phone showing Aram's to-dos. Frame 1: the line '() => TodoServiceRest(dependency<RestService>()));' is highlighted; the phone's footer says 'data from: the REST server'. Frame 2: the line is changed to '() => TodoServiceMock());'; the phone says 'restart the app (hot restart: main runs again)'; 'Only this one line changes'. Frame 3: the phone shows the same to-dos with the footer 'data from: TodoServiceMock (memory)'; 'Views and view models: not one line changed'.
Animation in three frames. Left: app/dependencies.dart with init(). Right: a phone showing Aram's to-dos. Frame 1: the line '() => TodoServiceRest(dependency<RestService>()));' is highlighted; the phone's footer says 'data from: the REST server'. Frame 2: the line is changed to '() => TodoServiceMock());'; the phone says 'restart the app (hot restart: main runs again)'; 'Only this one line changes'. Frame 3: the phone shows the same to-dos with the footer 'data from: TodoServiceMock (memory)'; 'Views and view models: not one line changed'.
Swap TodoServiceRest for TodoServiceMock in init(): the app keeps working with fake data.TodoServiceRest بە TodoServiceMock لە initـدا بگۆڕەوە: هەر کار دەکات بە دراوەی ساختە.CêIT

Widget tests show why this matters. Inside flutter test the real network is blocked (every HTTP request gets status 400), so the REST service cannot work there; the same app with mock services runs normally:

تاقیکردنەوەکانی پیشان دەدەن بۆچی ئەمە گرنگە. لەناو flutter testـدا ڕاستەقینە داخراوە (هەموو HTTP ⁦400⁩ وەردەگرێت)، بۆیە REST لەوێ کار ناکات؛ هەمان بە ساختەکان بە ئاسایی کار دەکات:

Real output of a Flutter widget test (flutter test, October 2026)دەرخستەی ڕاستەقینەی تاقیکردنەوەیەکی ویجێتی ⁦Flutter (flutter test⁩، ئۆکتۆبەری ⁦2026⁩)TEXT
TodoService -> TodoServiceRest
UserService -> UserServiceRest
TodoServiceRest: Exception: GET todos?userId=1 failed: HTTP 400
login screen: Aram | Shilan | Karwan
Karwan: 0 to-dos
after +: New task

In tests you usually replace registrations instead of editing init(). get_it refuses to register the same type twice (unless you set dependency.allowReassignment = true), and reset() clears everything so a test can register mocks. This Dart program shows both:

لە تاقیکردنەوەکاندا زۆربەی جار تۆمارکردنەکان دەگۆڕیتەوە لەبری دەستکاریکردنی init(). ⁦get_it⁩ ڕەت دەکاتەوە هەمان جۆر دوو جار تۆمار بکات (مەگەر dependency.allowReassignment = true دابنێیت) و reset() هەموو شتێک پاک دەکاتەوە تا تاقیکردنەوەیەک بتوانێت ساختەکان تۆمار بکات. Dartـی خوارەوە هەردووکیان پیشان دەدات:

Registering twice, then reset()دوو جار تۆمارکردن، پاشان ⁦reset()⁩bin/swap.dartDARTType in a terminalلە تێرمیناڵدا بینووسە
import 'package:get_it/get_it.dart';

final dependency = GetIt.instance;

abstract class TodoService {
  Future<List<String>> getTodoList(int userId);
}

class TodoServiceRest implements TodoService {
  @override
  Future<List<String>> getTodoList(int userId) async =>
      throw Exception('no server in this demo');
}

class TodoServiceMock implements TodoService {
  @override
  Future<List<String>> getTodoList(int userId) async =>
      ['Replace light bulb', 'Buy Flutter eBook'];
}

Future<void> main() async {
  dependency.registerLazySingleton<TodoService>(() => TodoServiceRest());
  print('registered: ${dependency<TodoService>().runtimeType}');

  try {
    dependency.registerLazySingleton<TodoService>(() => TodoServiceMock());
  } catch (e) {
    print('second registration: ${e.toString().split('\n').first}');
  }

  await dependency.reset(); // clears every registration (handy in tests)
  dependency.registerLazySingleton<TodoService>(() => TodoServiceMock());
  final service = dependency<TodoService>();
  print('registered: ${service.runtimeType}');
  print(await service.getTodoList(1));
}
Quick checkپشکنینی خێراYou want the whole app to run on fake data for a demo without internet. What do you change?دەتەوێت هەموو بۆ نمایشێک بەبێ لەسەر دراوەی ساختە کار بکات. چی دەگۆڕیت؟

get_it and provider: different jobs⁦get_it⁩ و provider: کاری جیاواز

The finished app uses both packages, and they do not compete. get_it finds objects; provider (or ListenableBuilder) rebuilds widgets when those objects notify:

تەواوەکە هەردوو بەکار دەهێنێت و ململانێ ناکەن. ⁦get_it⁩ دەدۆزێتەوە؛ provider (یان ListenableBuilder) دووبارە دروست دەکاتەوە کاتێک ئەو ئاگادار دەکەنەوە:

Pointخاڵget_itprovider
What it isچییەA service locator: a global registry of objects by type: تۆمارێکی گشتیی بەپێی جۆرDependency injection through the widget tree لە ڕێگەی
Needs a BuildContext?پێویستی بە BuildContext هەیە؟No: usable anywhere, also in view models and testsنەخێر: لە هەر شوێنێک بەکار دێت، لە و تاقیکردنەوەکانیشداYes: only widgets below the provider can read itبەڵێ: تەنها ئەو لە خوارەوەی providerـەکەن دەتوانن بیخوێننەوە
Rebuilds widgets? دووبارە دروست دەکاتەوە؟NoنەخێرYes, with watch, Consumer, Selectorبەڵێ، بە watch، Consumer، Selector
LifetimeتەمەنAs long as the app runs (until reset or unregister)تا کار دەکات (تا reset یان unregister)As long as the provider widget is in the treeتا providerـەکە لە دارەکەدایە
In the to-do appلە ئەرکەکانداHolds services and view models و هەڵدەگرێتEach view's ChangeNotifierProvider.value + Consumer⁦ChangeNotifierProvider.value + Consumer⁩ـی هەر
Quick checkپشکنینی خێراWhich job can get_it not do on its own?کام کار ⁦get_it⁩ بە تەنها ناتوانێت بیکات؟

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

MistakeهەڵەWhat happensچی ڕوو دەداتFixچارەسەر
Naming files like classes (TodoService.dart)ناونانی وەک (TodoService.dart)The file_names lint warns; imports become inconsistentlintـی file_names ئاگادار دەکاتەوە؛ importـەکان ناڕێک دەبنtodo_service.dart; class TodoServicetodo_service.dart؛ TodoService
A generic class called View گشتی بە ناوی Viewambiguous_import with Flutter's own View widgetambiguous_import لەگەڵ Viewـی خودی FlutterBaseViewBaseView
Registering the concrete type: registerLazySingleton(() => TodoServiceRest(…))تۆمارکردنی جۆرە دیاریکراوەکە: registerLazySingleton(() => TodoServiceRest(…))dependency<TodoService>() fails: nothing is registered as TodoServicedependency<TodoService>() شکست دەهێنێت: هیچ شتێک وەک TodoService تۆمار نەکراوەregisterLazySingleton<TodoService>(() => TodoServiceRest(…))registerLazySingleton<TodoService>(() => TodoServiceRest(…))
runApp before init()runApp پێش init()The first view gets an error from get_itیەکەم لە ⁦get_it⁩ـەوە وەردەگرێتCall di.init() first in mainیەکەم di.init() لە mainـدا بانگ بکە
Calling init() twice (for example in a test)بانگکردنی init() دوو جار (بۆ نموونە لە تاقیکردنەوەیەکدا)“Type … is already registered inside GetIt” «Type … ⁦is already registered inside GetIt⁩»await dependency.reset() between testsawait dependency.reset() لە نێوان تاقیکردنەوەکاندا
Expecting hot reload to pick up a changed init()چاوەڕوانی ئەوەی ⁦hot reload⁩ init()ـی گۆڕاو وەربگرێتHot reload does not run main again; the old registration stays⁦hot reload⁩ main دووبارە جێبەجێ ناکاتەوە؛ تۆمارکردنە کۆنەکە دەمێنێتەوەUse hot restart (or stop and run)⁦hot restart⁩ بەکار بهێنە (یان بیوەستێنە و دووبارە کاری پێ بکەرەوە)
Quick checkپشکنینی خێراA test calls init() in setUp, and the second test fails with “already registered”. What is missing?تاقیکردنەوەیەک لە setUpـدا init() بانگ دەکات و دووەم تاقیکردنەوە بە «⁦already registered⁩» شکست دەهێنێت. چی کەمە؟

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

Exerciseڕاهێنان1. Draw the tree١. دارەکە بکێشە

Your app gets a third screen, settings, with a SettingsViewModel, and a SettingsService that saves settings on the phone (with a real and a mock version). Write the new files and folders, with correct names.

شاشەیەکی سێیەم وەردەگرێت، settings، لەگەڵ SettingsViewModel و SettingsServiceـێک کە ڕێکخستنەکان لەسەر مۆبایلەکە پاشەکەوت دەکات (بە وەشانێکی ڕاستەقینە و ساختە). و نوێیەکان بە ناوی دروست بنووسە.

Show answerوەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوTEXT
lib/
├── screens/
│   └── settings/
│       ├── settings_view.dart
│       ├── settings_viewmodel.dart
│       └── widgets/
└── services/
    └── settings/
        ├── settings_service.dart              (abstract class)
        ├── settings_service_prefs.dart        (shared_preferences)
        └── settings_service_mock.dart

And two new lines in app/dependencies.dart: registerLazySingleton<SettingsService>(() => SettingsServicePrefs()) and registerLazySingleton(() => SettingsViewModel(dependency<SettingsService>())).

و دوو دێڕی نوێ لە app/dependencies.dartـدا: registerLazySingleton<SettingsService>(() => SettingsServicePrefs()) و registerLazySingleton(() => SettingsViewModel(dependency<SettingsService>())).

Exerciseڕاهێنان2. Write the abstract service٢. خزمەتگوزاریی ڕووت بنووسە

Write UserService (abstract) with getUserList(), and a UserServiceMock that returns Aram, Shilan and Karwan. Then register the mock in init().

UserService (ڕووت) بە getUserList() بنووسە و UserServiceMockـێک کە ئارام، شیلان و کاروان دەگەڕێنێتەوە. پاشان ساختەکە لە init()ـدا تۆمار بکە.

Show answerوەڵامەکە پیشان بدە
DART
import '../../models/user.dart';

abstract class UserService {
  Future<List<User>> getUserList();
}

class UserServiceMock implements UserService {
  @override
  Future<List<User>> getUserList() async => const [
        User(id: 1, name: 'Aram', email: 'aram@example.com'),
        User(id: 2, name: 'Shilan', email: 'shilan@example.com'),
        User(id: 3, name: 'Karwan', email: 'karwan@example.com'),
      ];
}

// in init():
dependency.registerLazySingleton<UserService>(() => UserServiceMock());
Exerciseڕاهێنان3. Predict the output٣. دەرخستەکە پێشبینی بکە

With registerLazySingleton(() => A()) and registerFactory(() => B()), where A() and B() print “A made” and “B made”, the program calls dependency<B>(), dependency<A>(), dependency<B>(), dependency<A>(). What is printed?

لەگەڵ registerLazySingleton(() => A()) و registerFactory(() => B())، کە A() و B() «⁦A made⁩» و «⁦B made⁩» چاپ دەکەن، dependency<B>()، dependency<A>()، dependency<B>()، dependency<A>() بانگ دەکات. چی چاپ دەکرێت؟

Show answerوەڵامەکە پیشان بدە
AnswerوەڵامTEXT
B made
A made
B made

The factory makes a new B on every call; the lazy singleton makes A once, on the first call, and reuses it.

factory لە هەموو بانگکردنێکدا Bـێکی نوێ دروست دەکات؛ دواخراو A یەک جار، لە یەکەم بانگکردندا، دروست دەکات و دووبارە بەکاری دەهێنێتەوە.

Exerciseڕاهێنان4. Find the bugs٤. هەڵەکان بدۆزەرەوە

Find three problems in this code and fix them.

سێ کێشە لەم کۆدەدا بدۆزەرەوە و چاکیان بکە.

DART
// lib/services/todo/TodoServiceRest.dart
class TodoServiceRest implements TodoService { /* … */ }

// lib/app/dependencies.dart
void init() {
  dependency.registerLazySingleton(() => TodoServiceRest());
}

// lib/main.dart
void main() {
  runApp(const TodoApp());
  di.init();
}
Show answerوەڵامەکە پیشان بدە
  • The file name TodoServiceRest.dart should be todo_service_rest.dart.
  • runApp runs before init(): call di.init() first.
  • registerLazySingleton(() => TodoServiceRest()) registers the concrete type, so dependency<TodoService>() finds nothing: write registerLazySingleton<TodoService>(…).
  • ناوی TodoServiceRest.dart دەبێت todo_service_rest.dart بێت.
  • runApp پێش init() جێبەجێ دەبێت: یەکەم di.init() بانگ بکە.
  • registerLazySingleton(() => TodoServiceRest()) جۆرە دیاریکراوەکە تۆمار دەکات، بۆیە dependency<TodoService>() هیچ نادۆزێتەوە: registerLazySingleton<TodoService>(…) بنووسە.

Summaryپوختە

  • lib/ holds main.dart and five folders: app/ (dependencies.dart, router.dart), models/, screens/, widgets/ (shared) and services/.
  • Each screen has a folder with its view, view model and a widgets/ folder for parts refactored out of it; generic ViewModel and BaseView classes build every screen.
  • Each service has a folder with an abstract class and its implementations (REST, mock, Firebase); shared helpers sit at the root of services/.
  • File names are lowercase_with_underscores; classes are UpperCamelCase.
  • get_it is the service locator: registerLazySingleton<Abstract>(() => Implementation()) in init(), dependency<T>() to get it, and di.init() before runApp.
  • Swapping one registration (Rest → Mock) changes the whole app's data source without touching views or view models.
  • lib/ main.dart و پێنج هەڵدەگرێت: app/ (⁦dependencies.dart⁩، ⁦router.dart⁩)، models/، screens/، widgets/ (هاوبەش) و services/.
  • هەر شاشەیەک هەیە لەگەڵ ، و widgets/ بۆ ئەو بەشانەی لێی دەرهێنراون؛ گشتیی ViewModel و BaseView هەموو شاشەیەک دروست دەکەن.
  • هەر هەیە لەگەڵ و جێبەجێکردنەکانی (REST، ساختە، Firebase)؛ یاریدەدەرە هاوبەشەکان لە ڕەگی services/ـدان.
  • ناوی پیتی بچووک و هێڵی ژێرەوەن؛ UpperCamelCaseـن.
  • ⁦get_it⁩ : registerLazySingleton<Abstract>(() => Implementation()) لە init()ـدا، dependency<T>() بۆ وەرگرتنی و di.init() پێش runApp.
  • گۆڕینەوەی یەک تۆمارکردن (Rest → Mock) هەموو دەگۆڕێت بەبێ دەستلێدان لە یان .

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

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

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

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

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