FlutterFlutter · Chapterبەش 11 · Lectureوانە 11.7

Project: A To-Do App with Providersپڕۆژە: ئەپێکی ئەرکەکان بە providerـەکان

The lecture's demo as a guided project: a login-as screen and a to-do screen (log out, toggle, delete, add), sample data as Dart lists with neutral users and no passwords, and a provider tree with a FutureProvider of users, a ValueNotifier for the current user and a ChangeNotifierProxyProvider for the list, checked with a real widget test.

نموونەکەی وانەکە وەک پڕۆژەیەکی ڕێنمایی‌کراو: شاشەیەکی وەک و شاشەیەکی ئەرکەکان (، گۆڕین، سڕینەوە، زیادکردن)، دراوە نموونەییەکان وەک لیستی Dart بە بێلایەن و بەبێ و دارێکی provider بە FutureProviderـێکی ، ValueNotifierـێک بۆ ئێستا و ChangeNotifierProxyProviderـێک بۆ لیستەکە، کە بە تاقیکردنەوەیەکی ڕاستەقینەی پشکنراوە.

17 min read16 خولەک خوێندنەوە · 14 stepsهەنگاو

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

  • Plan an app as a provider tree: choose the right provider for each kind of data.
  • Write immutable model classes and sample data without passwords.
  • Build a ChangeNotifier that follows the current user through a ChangeNotifierProxyProvider.
  • Connect taps, long presses and buttons to notifier methods with context.read, and show data with context.watch.
  • وەک دارێکی provider پلان بکەیت: providerـی گونجاو بۆ هەر جۆرە دراوەیەک هەڵبژێریت.
  • و دراوەی نموونەیی بەبێ بنووسیت.
  • ChangeNotifierـێک دروست بکەیت کە بە ChangeNotifierProxyProvider شوێنی ئێستا بکەوێت.
  • دەستلێدان، پەنجە ڕاگرتن و بە context.read ببەستیتەوە بە ChangeNotifier و دراوە بە context.watch پیشان بدەیت.

What we are going to buildئەوەی دروستی دەکەین

This lesson is a project. You build the lecture's demo app from start to finish with the providers of this chapter: FutureProvider, ChangeNotifierProvider with a ValueNotifier and ChangeNotifierProxyProvider. The app has two screens:

ئەم وانەیە پڕۆژەیەکە. نموونەی وانەکە لە سەرەتاوە تا کۆتایی بە providerـەکانی ئەم بەشە دروست دەکەیت: FutureProvider، ChangeNotifierProvider لەگەڵ ValueNotifier و ChangeNotifierProxyProvider. دوو شاشەی هەیە:

  • LoginAsScreen: “Choose a user to log in as”, a list of users. Tapping a user logs in as that user. (A demo: there are no passwords.)
  • TodoListScreen: the active user's to-do list, with the user's avatar and name in the app bar and a Log out button. Tap a to-do to toggle it between done and not done, long-press it to delete it, and press + to add a new one.
  • LoginAsScreen: «⁦Choose a user to log in as⁩»، لیستێکی . دەستلێدان لە وەک ئەو دەچێتە ژوورەوە. (نموونەیە: هیچ نییە.)
  • TodoListScreen: لیستی ئەرکەکانی چالاک، لەگەڵ وێنۆچکە و ناوی لە و ⁦Log out⁩. دەست لە ئەرکێک بدە بۆ گۆڕینی لە نێوان تەواوکراو و تەواونەکراودا، پەنجەی لەسەر ڕابگرە بۆ سڕینەوەی و + دابگرە بۆ زیادکردنی ئەرکێکی نوێ.
Two phone wireframes. Left, LoginAsScreen: an app bar 'Choose a user to log in as' and three users, Aram (user id 1), Shilan (2) and Karwan (3), each with a round avatar letter; Aram is highlighted and an arrow 'tap a user' leads to the right phone. Right, TodoListScreen: an app bar with avatar A, the name Aram and a log-out icon; five to-dos with their ids, the fifth crossed out; a round + button. Notes on the right point to the log-out icon ('Log out'), the list ('The active user's to-do list'), a row ('Tap: toggle done / not done', 'Long-press: delete the to-do') and the + button ('Add a new to-do').دوو وێنەی سادەی مۆبایل. لای چەپ، LoginAsScreen: شریتێکی ئەپ بە 'Choose a user to log in as' و سێ بەکارهێنەر، ئارام (ژمارەی ناسنامە ١)، شیلان (٢) و کاروان (٣)، هەریەکەیان بە پیتێک لە بازنەیەکدا؛ ئارام دیار کراوە و تیرێک بە ناوی 'دەست بە بەکارهێنەرێکدا بنێ' دەچێت بۆ مۆبایلی ڕاست. لای ڕاست، TodoListScreen: شریتێکی ئەپ بە پیتی A، ناوی ئارام و ئایکۆنێکی چوونەدەرەوە؛ پێنج ئەرک بە ژمارەی ناسنامەکانیانەوە، پێنجەمیان هێڵی بەسەردا هاتووە؛ دوگمەیەکی خڕی +. تێبینییەکان لای ڕاست ئاماژە دەکەن بۆ ئایکۆنی چوونەدەرەوە ('چوونەدەرەوە')، لیستەکە ('لیستی ئەرکەکانی بەکارهێنەری چالاک')، دێڕێک ('دەستلێدان: تەواو / تەواونەکراو'، 'پەنجە ڕاگرتن: سڕینەوەی ئەرکەکە') و دوگمەی + ('زیادکردنی ئەرکێکی نوێ').
The two screens of the to-do app (redrawn from the lecture's slide with neutral users and no photos).دوو شاشەکەی ئەرکەکان (لە سلایدەکەی وانەکەوە دووبارە کێشراوە، بە بێلایەن و بەبێ وێنەی ڕاستەقینە).CêIT
Quick checkپشکنینی خێراOn the to-do screen, what does a long press on a to-do do?لە شاشەی ئەرکەکاندا، پەنجە ڕاگرتن لەسەر ئەرکێک چی دەکات؟

The data: users and to-dosدراوەکە: بەکارهێنەرەکان و ئەرکەکان

The lecture keeps the data in two collections, todos and users, written as JSON. Each to-do has an id, the userId of its owner, a title and completed (true or false); each user has an id, a name and an e-mail. In Dart we describe each kind of data with a small model class. Both classes are immutable: to toggle a to-do we make a changed copy with copyWith:

وانەکە دراوەکە لە دوو کۆمەڵەدا هەڵدەگرێت، todos و users، کە بە نووسراون. هەر ئەرکێک id، userIdـی خاوەنەکەی، title و completed (true یان false)ـی هەیە؛ هەر id، name و هەیە. لە Dartـدا هەر جۆرە دراوەیەک بە بچووک وەسف دەکەین. هەردوو : بۆ گۆڕینی ئەرکێک کۆپییەکی گۆڕاو بە copyWith دروست دەکەین:

The models (models.dart)مۆدێلەکان ⁦(models.dart)⁩models.dartDART
class User {
  const User({required this.id, required this.name, required this.email});

  final int id;
  final String name;
  final String email;
}

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 is immutable: toggling makes a changed copy.
  Todo copyWith({bool? completed}) => Todo(
        id: id,
        userId: userId,
        title: title,
        completed: completed ?? this.completed,
      );
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 23

A copy with one field changed; the original stays as it was.

کۆپییەک کە یەک خانەی گۆڕاوە؛ ئەسڵەکە وەک خۆی دەمێنێتەوە.

Quick checkپشکنینی خێراTodo is immutable. How does the app mark a to-do as done?Todo . چۆن ئەرکێک وەک تەواوکراو نیشان دەکات؟

The data: users and to-dosدراوەکە: بەکارهێنەرەکان و ئەرکەکان

The sample data, without passwordsدراوە نموونەییەکان، بەبێ تێپەڕەوشە

Here are the two collections as Dart lists. The to-dos are the lecture's own sample data. The users are not: the slide's users have made-up English names, sample e-mail addresses, plain-text passwords and photos of real faces from a web service. We keep the structure and replace the rest:

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

The sample data (sample_data.dart)دراوە نموونەییەکان ⁦(sample_data.dart)⁩sample_data.dartDART
import 'models.dart';

// The lecture's two collections as Dart lists.
// Neutral names, example.com addresses, no passwords, no photos.
const sampleUsers = [
  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'),
];

const sampleTodos = [
  Todo(id: 1, userId: 1, title: 'Prepare proposal for the new project'),
  Todo(id: 2, userId: 1, title: 'Replace light bulb'),
  Todo(id: 3, userId: 1, title: 'Buy Flutter eBook'),
  Todo(id: 4, userId: 1, title: 'Subscribe to fibre-optic internet'),
  Todo(id: 5, userId: 1, title: 'Set up online meeting room', completed: true),
  Todo(id: 6, userId: 2, title: 'New task'),
  Todo(id: 8, userId: 2, title: 'New task', completed: true),
];
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 17

Shilan's two to-dos were added in the app, so they are called “New task”. There is no id 7: it was deleted. Ids need not be consecutive.

دوو ئەرکەکەی شیلان لەناو زیاد کراون، بۆیە ناویان «⁦New task⁩»ـە. ژمارەی ناسنامەی 7 نییە: سڕاوەتەوە. پێویست ناکات ژمارەکان بە دوای یەکدا بن.

Quick checkپشکنینی خێراWhich to-dos belong to Shilan (user id 2)?کام ئەرکانە هی شیلانن ( ژمارە ٢)؟

The data: users and to-dosدراوەکە: بەکارهێنەرەکان و ئەرکەکان

Two services that pretend to be a serverدوو خزمەتگوزاری کە خۆیان وەک ڕاژەکار پیشان دەدەن

In the lecture the data comes from a server through two services, UserDataService and TodoDataService. Ours keep the sample lists in memory and wait half a second before answering, so the app behaves like a real network app: it must show a spinner while it waits. TodoDataService keeps a changeable copy, so adding, changing and deleting really work:

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

The services (services.dart)خزمەتگوزارییەکان ⁦(services.dart)⁩services.dartDART
import 'models.dart';
import 'sample_data.dart';

// Pretend servers: they answer after a short wait.
class UserDataService {
  Future<List<User>> getUserList() async {
    await Future<void>.delayed(const Duration(milliseconds: 500));
    return sampleUsers;
  }
}

class TodoDataService {
  final List<Todo> _todos = [...sampleTodos]; // a changeable copy

  Future<List<Todo>> getTodoList(int userId) async {
    await Future<void>.delayed(const Duration(milliseconds: 500));
    return _todos.where((t) => t.userId == userId).toList();
  }

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

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

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

A copy of the const list, because a const list cannot change.

کۆپییەکی لیستە constـەکە، چونکە لیستی const ناتوانێت بگۆڕێت.

Lineدێڕی 21

The next id is the biggest id plus one: after 8 comes 9 (like a database).

ژمارەی ناسنامەی داهاتوو گەورەترین ژمارەیە زائیدەی یەک: دوای 8، 9 دێت (وەک ).

Quick checkپشکنینی خێراWhy does TodoDataService copy sampleTodos into its own list?بۆچی TodoDataService sampleTodos کۆپی دەکات بۆ لیستێکی خۆی؟

The plan: three providers above MaterialAppپلانەکە: سێ provider لە سەرووی MaterialAppـەوە

The lecture plans the app as a tree before writing code. Three providers sit in one MultiProvider above MaterialApp, so both screens can reach them. Each one holds a different kind of data, and each kind gets the provider that fits it:

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

A services box on the left with UserDataService and TodoDataService. A MultiProvider box holds three numbered providers: 1 FutureProvider (List<User>), fed by getUserList(); 2 ChangeNotifierProvider (ValueNotifier<User?>); 3 ChangeNotifierProxyProvider (TodoListNotifier), fed by getTodoList(userId) and by an arrow 'setUser(user)' from provider 2. Below: MaterialApp with LoginAsScreen and TodoListScreen. Under LoginAsScreen: watch<List<User>>() 'show the users' and read<ValueNotifier<User?>>().value = user 'log in as that user'. Under TodoListScreen: watch<ValueNotifier<User?>>() 'the app bar', watch<TodoListNotifier>() 'the list', read<TodoListNotifier>().toggle(…) 'tap, long-press, +'.سندووقێکی خزمەتگوزارییەکان لای چەپ لەگەڵ UserDataService و TodoDataService. سندووقێکی MultiProvider سێ providerـی ژمارەدار لەخۆ دەگرێت: ١ FutureProvider (List<User>)، کە getUserList() پڕی دەکاتەوە؛ ٢ ChangeNotifierProvider (ValueNotifier<User?>)؛ ٣ ChangeNotifierProxyProvider (TodoListNotifier)، کە getTodoList(userId) و تیرێکی 'setUser(user)' لە providerـی ٢ـەوە پڕی دەکەنەوە. لە خوارەوە: MaterialApp لەگەڵ LoginAsScreen و TodoListScreen. لە ژێر LoginAsScreen: watch<List<User>>() 'بەکارهێنەرەکان پیشان دەدات' و read<ValueNotifier<User?>>().value = user 'وەک ئەو بەکارهێنەرە دەچێتە ژوورەوە'. لە ژێر TodoListScreen: watch<ValueNotifier<User?>>() 'شریتی ئەپ'، watch<TodoListNotifier>() 'لیستەکە'، read<TodoListNotifier>().toggle(…) 'دەستلێدان، پەنجە ڕاگرتن، +'.
The provider tree of the to-do app, redrawn from the lecture's three slides.داری providerـەکانی ئەرکەکان، لە سێ سلایدەکەی وانەکەوە دووبارە کێشراوە.CêIT
Providerجۆری providerHoldsهەڵدەگرێتWhy this providerبۆچی ئەم providerـەWho uses itکێ بەکاری دەهێنێت
1 FutureProvider<List<User>>All usersهەموو Loaded once from UserDataService, never changed by the appیەک جار لە UserDataServiceـەوە بار دەکرێت، هەرگیز نایگۆڕێتLoginAsScreen: to build the ListViewLoginAsScreen: بۆ دروستکردنی ListView
2 ChangeNotifierProvider<ValueNotifier<User?>>The current user (null = nobody) ئێستا (null = هیچ کەس)One value that the app changes; ValueNotifier notifies by itselfیەک نرخ کە دەیگۆڕێت؛ ValueNotifier خۆی ئاگادار دەکاتەوەLoginAsScreen changes it; TodoListScreen shows it in the app barLoginAsScreen دەیگۆڕێت؛ TodoListScreen لە پیشانی دەدات
3 ChangeNotifierProxyProvider<…, TodoListNotifier>The current user's to-dosئەرکەکانی ئێستاA notifier with its own state (the user edits the list) that must follow provider 2ChangeNotifierـێک بە خۆیەوە ( دەستکاریی لیستەکە دەکات) کە دەبێت شوێنی providerـی ٢ بکەوێتTodoListScreen: to build the ListView, toggle, delete, addTodoListScreen: بۆ دروستکردنی ListView، گۆڕین، سڕینەوە، زیادکردن
Quick checkپشکنینی خێراWhy is the to-do list in a ChangeNotifierProxyProvider and not in a plain ChangeNotifierProvider?بۆچی لیستی ئەرکەکان لە ChangeNotifierProxyProviderـدایە نەک لە ChangeNotifierProviderـێکی ئاسایی؟

The TodoListNotifierکلاسی TodoListNotifier

The heart of the app is the notifier that holds the current user's to-dos. It follows the right pattern: it is created once without a user, and the proxy provider passes each new user in through setUser. Every change ends with notifyListeners():

دڵی ئەو ChangeNotifierـەیە کە ئەرکەکانی ئێستا هەڵدەگرێت. شێوازە دروستەکە پەیڕەو دەکات: یەک جار بەبێ دروست دەکرێت و providerـی proxy هەر نوێ بە setUser دەداتێ. هەموو گۆڕانێک بە notifyListeners() کۆتایی دێت:

todo_list_notifier.dartفایلی ⁦todo_list_notifier.dart⁩todo_list_notifier.dartDART
import 'package:flutter/foundation.dart';

import 'models.dart';
import 'services.dart';

class TodoListNotifier extends ChangeNotifier {
  TodoListNotifier(this._service);

  final TodoDataService _service;
  User? _user;
  List<Todo> _todos = const [];
  bool _loading = false;

  List<Todo> get todos => _todos;
  bool get loading => _loading;

  // ChangeNotifierProxyProvider calls this whenever the current user changes.
  void setUser(User? user) {
    if (user?.id == _user?.id) return; // the same user: keep the list
    _user = user;
    _todos = const [];
    _loading = user != null;
    if (user != null) _load(user.id);
    notifyListeners();
  }

  Future<void> _load(int userId) async {
    final list = await _service.getTodoList(userId);
    if (_user?.id != userId) return; // another user logged in meanwhile
    _todos = list;
    _loading = false;
    notifyListeners();
  }

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

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

  Future<void> add(String title) async {
    final user = _user;
    if (user == null) return;
    final todo = await _service.addTodo(user.id, title);
    _todos = [..._todos, todo];
    notifyListeners();
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 18

Called by the proxy provider every time the current user changes.

providerـی proxy هەر جارێک ئێستا بگۆڕێت بانگی دەکات.

Lineدێڕی 19

The same user again: keep the list and its edits.

دیسان هەمان : لیستەکە و دەستکارییەکانی بهێڵەوە.

Lineدێڕی 29

A slow answer for a user who already logged out is ignored.

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

Lineدێڕی 36

Toggle: a changed copy replaces the old to-do in a new list.

گۆڕین: کۆپییەکی گۆڕاو جێگەی ئەرکە کۆنەکە دەگرێتەوە لە لیستێکی نوێدا.

Notice that the screens never touch _todos directly. They read the todos getter and call toggle, delete and add. All the rules about the list live in this one class, which you could test without any widget.

سەرنج بدە کە شاشەکان هەرگیز ڕاستەوخۆ دەست لە _todos نادەن. todos دەخوێننەوە و toggle، delete و add بانگ دەکەن. هەموو یاساکانی لیستەکە لەم یەک دەژین کە دەتوانیت بەبێ هیچ تاقی بکەیتەوە.

Quick checkپشکنینی خێراAram logs in, and while his list is still loading, he logs out and Shilan logs in. Aram's list arrives late. What does the notifier do with it?ئارام دەچێتە ژوورەوە و لە کاتێکدا لیستەکەی هێشتا بار دەکرێت، دەچێتە دەرەوە و شیلان دەچێتە ژوورەوە. لیستەکەی ئارام دواکەوتوو دەگات. ChangeNotifierـەکە چی لێ دەکات؟

Wiring the providers in main.dartبەستنەوەی providerـەکان لە ⁦main.dart⁩ـدا

Now the plan becomes code. The MultiProvider lists the two services first (as plain Providers, because they never change), then the three providers of the plan. The order matters: a provider can only read providers above it, and in a MultiProvider the first item is the outermost:

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

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

import 'login_as_screen.dart';
import 'models.dart';
import 'services.dart';
import 'todo_list_notifier.dart';

void main() => runApp(const TodoApp());

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

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        // The services (the lecture's "service locator")
        Provider<UserDataService>(create: (_) => UserDataService()),
        Provider<TodoDataService>(create: (_) => TodoDataService()),
        // 1. All users, loaded once
        FutureProvider<List<User>>(
          create: (context) => context.read<UserDataService>().getUserList(),
          initialData: const [],
        ),
        // 2. The current user (null = nobody logged in)
        ChangeNotifierProvider<ValueNotifier<User?>>(
          create: (_) => ValueNotifier<User?>(null),
        ),
        // 3. The current user's to-dos, kept in sync with 2
        ChangeNotifierProxyProvider<ValueNotifier<User?>, TodoListNotifier>(
          create: (context) => TodoListNotifier(context.read<TodoDataService>()),
          update: (context, currentUser, list) =>
              list!..setUser(currentUser.value),
        ),
      ],
      child: MaterialApp(
        title: 'To-do',
        theme: ThemeData(colorSchemeSeed: Colors.green),
        home: const LoginAsScreen(),
      ),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 19

The services: the lecture's “service locator” role, given by dependency injection.

: ڕۆڵی «»ـی وانەکە، بە دەدرێن.

Lineدێڕی 23

Provider 1 reads a service with context.read.

providerـی ١ بە context.read دەخوێنێتەوە.

Lineدێڕی 28

Provider 2: nobody is logged in yet.

providerـی ٢: هێشتا کەس نەچووەتە ژوورەوە.

Lineدێڕی 32

Provider 3 is created once, with its service…

providerـی ٣ یەک جار دروست دەکرێت، لەگەڵ …

Lineدێڕی 34

…and told about every new current user.

…و لە هەموو ئێستای نوێ ئاگادار دەکرێتەوە.

Quick checkپشکنینی خێراWhat would break if the ChangeNotifierProxyProvider were listed before the ChangeNotifierProvider<ValueNotifier<User?>>?چی تێک دەچێت ئەگەر ChangeNotifierProxyProvider پێش ChangeNotifierProvider<ValueNotifier<User?>> بنووسرێت؟

The login-as screenشاشەی چوونەژوورەوە وەک بەکارهێنەرێک

The first screen watches the user list from the FutureProvider and shows a spinner until it arrives. Tapping a user writes the current user (with read, because a tap only changes something) and opens the to-do screen:

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

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

import 'models.dart';
import 'todo_list_screen.dart';

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

  @override
  Widget build(BuildContext context) {
    final users = context.watch<List<User>>(); // from the FutureProvider
    return Scaffold(
      appBar: AppBar(title: const Text('Choose a user to log in as')),
      body: users.isEmpty
          ? const Center(child: CircularProgressIndicator())
          : ListView(
              children: [
                for (final user in users)
                  ListTile(
                    leading: CircleAvatar(child: Text(user.name[0])),
                    title: Text(user.name),
                    subtitle: Text('user id: ${user.id}'),
                    onTap: () => _logInAs(context, user),
                  ),
              ],
            ),
    );
  }

  void _logInAs(BuildContext context, User user) {
    context.read<ValueNotifier<User?>>().value = user; // change the current user
    Navigator.of(context).push(
      MaterialPageRoute<void>(builder: (_) => const TodoListScreen()),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 12

Get the user list, to build the ListView ([] while loading).

لیستی وەردەگرێت بۆ دروستکردنی ListView ([] لە کاتی بارکردندا).

Lineدێڕی 32

Change the current user: setting .value notifies, and the proxy provider loads this user's to-dos.

ئێستا دەگۆڕێت: دانانی .value ئاگادار دەکاتەوە و providerـی proxy ئەرکەکانی ئەم بار دەکات.

Quick checkپشکنینی خێراWhy does the tap use context.read and not context.watch?بۆچی دەستلێدانەکە context.read بەکار دەهێنێت نەک context.watch؟

The to-do screenشاشەی ئەرکەکان

The second screen uses two providers: the current user for the app bar and the TodoListNotifier for the list. Taps, long presses and the + button only call methods on the notifier; the notifier changes the data and notifies, and the list redraws itself:

شاشەی دووەم دوو provider بەکار دەهێنێت: ئێستا بۆ و TodoListNotifier بۆ لیستەکە. دەستلێدان، پەنجە ڕاگرتن و + تەنها لەسەر ChangeNotifierـەکە بانگ دەکەن؛ ChangeNotifierـەکە دراوەکە دەگۆڕێت و ئاگادار دەکاتەوە و لیستەکە خۆی دووبارە دەکێشێتەوە:

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

import 'models.dart';
import 'todo_list_notifier.dart';

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

  @override
  Widget build(BuildContext context) {
    final user = context.watch<ValueNotifier<User?>>().value; // for the app bar
    final list = context.watch<TodoListNotifier>(); // for the list
    return Scaffold(
      appBar: AppBar(
        title: Row(
          children: [
            CircleAvatar(child: Text(user?.name[0] ?? '?')),
            const SizedBox(width: 12),
            Text(user?.name ?? ''),
          ],
        ),
        actions: [
          IconButton(
            tooltip: 'Log out',
            icon: const Icon(Icons.logout),
            onPressed: () {
              context.read<ValueNotifier<User?>>().value = null;
              Navigator.of(context).pop();
            },
          ),
        ],
      ),
      body: list.loading
          ? const Center(child: CircularProgressIndicator())
          : ListView.builder(
              itemCount: list.todos.length,
              itemBuilder: (context, i) {
                final todo = list.todos[i];
                return ListTile(
                  title: Text(
                    todo.title,
                    style: TextStyle(
                      decoration:
                          todo.completed ? TextDecoration.lineThrough : null,
                    ),
                  ),
                  subtitle: Text('id: ${todo.id}'),
                  onTap: () => context.read<TodoListNotifier>().toggle(todo),
                  onLongPress: () =>
                      context.read<TodoListNotifier>().delete(todo),
                );
              },
            ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add a to-do',
        onPressed: () => context.read<TodoListNotifier>().add('New task'),
        child: const Icon(Icons.add),
      ),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 12

Get the current user, to build the AppBar.

ئێستا وەردەگرێت بۆ دروستکردنی AppBar.

Lineدێڕی 13

Get the to-do list, to build the ListView.

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

Lineدێڕی 28

Log out: no current user, then back to the login screen.

: هیچ ئێستا نییە، پاشان گەڕانەوە بۆ شاشەی .

Lineدێڕی 45

Completed to-dos are crossed out, as on the slide.

ئەرکە تەواوکراوەکان هێڵیان بەسەردا دێت، وەک لە سلایدەکەدا.

Lineدێڕی 49

onTap: toggle status.

onTap: گۆڕینی دۆخ.

Lineدێڕی 51

onLongPress: delete the to-do.

onLongPress: سڕینەوەی ئەرکەکە.

Lineدێڕی 57

+: add a new to-do.

+: زیادکردنی ئەرکێکی نوێ.

Quick checkپشکنینی خێراWhich widget of TodoListScreen rebuilds when the user toggles a to-do?کاتێک ئەرکێک دەگۆڕێت، کام TodoListScreen دووبارە دروست دەکرێتەوە؟

Running the appکارپێکردنی ئەپەکە

Here is the whole app in five steps: log in as a user, see the list, tap, long-press and add:

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

Animation in five frames. Left: a phone; right: three boxes showing what happens in the code. 1: the login screen with Aram highlighted; onTap, currentUser.value = Aram, Navigator.push(TodoListScreen). 2: Aram's five to-dos appear; currentUser.value = Aram, list.setUser(Aram), getTodoList(1). 3: 'Replace light bulb' is crossed out; onTap, list.toggle(todo), notifyListeners(). 4: 'Buy Flutter eBook' is gone, four to-dos remain; onLongPress, list.delete(todo), notifyListeners(). 5: a new 'New task' row (id 9) is highlighted; onPressed (+), list.add('New task'), notifyListeners().
Animation in five frames. Left: a phone; right: three boxes showing what happens in the code. 1: the login screen with Aram highlighted; onTap, currentUser.value = Aram, Navigator.push(TodoListScreen). 2: Aram's five to-dos appear; currentUser.value = Aram, list.setUser(Aram), getTodoList(1). 3: 'Replace light bulb' is crossed out; onTap, list.toggle(todo), notifyListeners(). 4: 'Buy Flutter eBook' is gone, four to-dos remain; onLongPress, list.delete(todo), notifyListeners(). 5: a new 'New task' row (id 9) is highlighted; onPressed (+), list.add('New task'), notifyListeners().
Every gesture becomes a method call on the notifier; the screen only redraws what the notifier now holds.هەموو دەبێت بە بانگکردنی لەسەر ChangeNotifierـەکە؛ شاشەکە تەنها ئەوە دووبارە دەکێشێتەوە کە ChangeNotifierـەکە ئێستا هەیەتی.CêIT

A widget test drove the real app the same way and printed what the screen showed after each step:

تاقیکردنەوەیەکی بە هەمان شێوە ڕاستەقینەکەی بەڕێوە برد و دوای هەر هەنگاوێک ئەوەی شاشەکە پیشانی دەدا چاپی کرد:

Real output of a Flutter widget test (flutter test, October 2026)دەرخستەی ڕاستەقینەی تاقیکردنەوەیەکی ویجێتی ⁦Flutter (flutter test⁩، ئۆکتۆبەری ⁦2026⁩)TEXT
1 start:        spinner=true
2 users loaded: Aram | Shilan | Karwan
3 tap Aram:     app bar shows Aram=true
4 his to-dos:   Prepare proposal for the new project | Replace light bulb | Buy Flutter eBook | Subscribe to fibre-optic internet | Set up online meeting room (done)
5 tap:          Prepare proposal for the new project | Replace light bulb (done) | Buy Flutter eBook | Subscribe to fibre-optic internet | Set up online meeting room (done)
6 long-press:   Prepare proposal for the new project | Replace light bulb (done) | Subscribe to fibre-optic internet | Set up online meeting room (done)
7 tap +:        Prepare proposal for the new project | Replace light bulb (done) | Subscribe to fibre-optic internet | Set up online meeting room (done) | New task (new id 9)
8 log out:      back on "Choose a user to log in as"
9 tap Shilan:   New task | New task (done)

Step 9 shows the proxy provider at work: after logging out and choosing Shilan, setUser loaded her two to-dos, without any code in the screens that says “reload”.

هەنگاوی ٩ providerـی proxy لە کاتی کارکردندا پیشان دەدات: دوای و هەڵبژاردنی شیلان، setUser دوو ئەرکەکەی ئەوی بار کرد، بەبێ ئەوەی هیچ کۆدێک لە شاشەکاندا بڵێت «دووبارە بار بکەرەوە».

Quick checkپشکنینی خێراAfter step 7, Aram logs out and logs in again. What does his list show?دوای هەنگاوی ٧، ئارام دەچێتە دەرەوە و دیسان دەچێتەوە ژوورەوە. لیستەکەی چی پیشان دەدات؟

Next: the same app in MVVMداهاتوو: هەمان ئەپ بە MVVM

The next chapter rebuilds this very app with the MVVM architecture. The lecture's plan for it already looks different: the widget tree has only MaterialApp with LoginScreen and TodolistScreen, and a service locator holds a Rest Service (which talks to the database on the server) and two view models, UserViewmodel and TodoViewmodel. The providers above MaterialApp disappear; each screen gets its own. The details are in MVVM in Flutter with Provider.

بەشی داهاتوو هەر ئەم بە دووبارە دروست دەکاتەوە. پلانی وانەکە بۆی پێشتر جیاوازە: تەنها MaterialAppـی تێدایە لەگەڵ LoginScreen و TodolistScreen و ⁦Rest Service⁩ـێک هەڵدەگرێت (کە قسە لەگەڵ لەسەر دەکات) لەگەڵ دوو ، UserViewmodel و TodoViewmodel. providerـەکانی سەرووی MaterialApp نامێنن؛ هەر شاشەیەک providerـی خۆی وەردەگرێت. وردەکارییەکان لە MVVM لە Flutterـدا بە Providerـدان.

Quick checkپشکنینی خێراIn the MVVM version, where do the user list and the to-do list live?لە وەشانی ، لیستی و لیستی ئەرکەکان لە کوێ دەژین؟

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

MistakeهەڵەWhat happensچی ڕوو دەداتFixچارەسەر
ChangeNotifierProvider<User> with a plain User classChangeNotifierProvider<User> لەگەڵ Userـی ئاساییDoes not compile: User is not a ChangeNotifiercompile نابێت: User ChangeNotifier نییەProvide a ValueNotifier<User?> (or a notifier class that holds the user)ValueNotifier<User?> دابین بکە (یان ChangeNotifier کە هەڵدەگرێت)
Changing a to-do in place (todo.completed = true)گۆڕینی ئەرکێک لە شوێنی خۆیدا (todo.completed = true)With immutable models it does not compile; with mutable ones nobody is notifiedلەگەڵ compile نابێت؛ لەگەڵ گۆڕاوەکاندا کەس ئاگادار ناکرێتەوەReplace it with copyWith(...) inside the notifier, then notifyListeners()لەناو ChangeNotifierـەکەدا بە copyWith(...) بیگۆڕەوە، پاشان notifyListeners()
Creating TodoListNotifier(...) inside updateدروستکردنی TodoListNotifier(...) لەناو updateـداEvery login builds a new list object; edits and listeners are lostهەموو لیستی نوێ دروست دەکات؛ دەستکاری و لەدەست دەچنCreate once in create; call setUser in updateیەک جار لە createـدا دروستی بکە؛ لە updateـدا setUser بانگ بکە
context.watch inside onTap / onPressedcontext.watch لەناو onTap / onPressedـداProvider throws an error in debug modeprovider لە دۆخی debugـدا دروست دەکاتUse context.read in callbacksلە context.read بەکار بهێنە
Passwords in the sample data لە دراوە نموونەییەکانداAnyone with the app or the JSON can read themهەر کەسێک یان هەبێت دەتوانێت بیانخوێنێتەوەNever store passwords in app data; use an authentication serviceهەرگیز لە دراوەی هەڵمەگرە؛ بەکار بهێنە
Quick checkپشکنینی خێراA student wrote todo.completed = !todo.completed; in a mutable Todo class and the list did not change on screen. Why?قوتابییەک لە Todoـی گۆڕاودا todo.completed = !todo.completed;ـی نووسی و لیستەکە لەسەر شاشە نەگۆڕا. بۆچی؟

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

Exerciseڕاهێنان1. A count badge١. نیشانەی ژمارە

Add the badge from the ProxyProvider lesson to the to-do screen: it must show how many to-dos are not completed for the current user. Which provider do you add, and where in the list?

نیشانەکەی وانەی ProxyProvider زیاد بکە بۆ شاشەی ئەرکەکان: دەبێت پیشان بدات چەند ئەرک تەواو نەکراون بۆ ئێستا. کام provider زیاد دەکەیت و لە کوێی لیستەکەدا؟

Show answerوەڵامەکە پیشان بدە

A ProxyProvider that reads the TodoListNotifier, listed after it:

ProxyProviderـێک کە TodoListNotifier دەخوێنێتەوە و دوای ئەو دەنووسرێت:

DART
ProxyProvider<TodoListNotifier, int>(
  update: (context, list, previous) =>
      list.todos.where((t) => !t.completed).length,
),

In the app bar: Badge.count(count: context.watch<int>(), child: const Icon(Icons.checklist)). For Aram the badge shows 4 at first.

لە : Badge.count(count: context.watch<int>(), child: const Icon(Icons.checklist)). بۆ ئارام نیشانەکە سەرەتا ٤ پیشان دەدات.

Exerciseڕاهێنان2. Your own title٢. ناونیشانی خۆت

The + button always adds “New task”. Change it so that it opens a dialog with a TextField and adds the typed title (ignore empty titles).

+ هەمیشە «⁦New task⁩» زیاد دەکات. بیگۆڕە بە شێوەیەک کە دیالۆگێک لەگەڵ TextFieldـێک بکاتەوە و ئەو ناونیشانەی نووسراوە زیاد بکات (ناونیشانی بەتاڵ پشتگوێ بخە).

Show answerوەڵامەکە پیشان بدە
DART
Future<void> _addTodo(BuildContext context) async {
  final controller = TextEditingController();
  final title = await showDialog<String>(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('New to-do'),
      content: TextField(controller: controller, autofocus: true),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('Cancel'),
        ),
        FilledButton(
          onPressed: () => Navigator.pop(context, controller.text.trim()),
          child: const Text('Add'),
        ),
      ],
    ),
  );
  controller.dispose();
  if (title == null || title.isEmpty || !context.mounted) return;
  await context.read<TodoListNotifier>().add(title);
}

The notifier does not change at all: it already takes a title. Only the view asks the user for it. (Text fields are taught in Text fields and forms.)

ChangeNotifierـەکە هیچ ناگۆڕێت: پێشتر ناونیشانێک وەردەگرێت. تەنها شاشەکە داوای لە دەکات. ( لە خانەکانی دەق و فۆرمەکانـدا فێر دەکرێن.)

Exerciseڕاهێنان3. Undo a delete٣. گەڕاندنەوەی سڕینەوە

A long press deletes at once, which is easy to do by accident. Describe how to show a SnackBar “Deleted … UNDO” and put the to-do back if the user taps UNDO. Which class gets a new method?

پەنجە ڕاگرتن یەکسەر دەسڕێتەوە و ئەمە بە هەڵە ئاسان ڕوو دەدات. ڕوونی بکەرەوە چۆن SnackBarـێکی «Deleted … UNDO» پیشان بدەیت و ئەگەر دەستی لە UNDO دا ئەرکەکە بگەڕێنیتەوە. کام نوێ وەردەگرێت؟

Show answerوەڵامەکە پیشان بدە
  • TodoListNotifier gets restore(Todo todo). It cannot use add, because addTodo gives a new id; so the service also gets restoreTodo(Todo todo), which puts the same to-do (same id and title) back. restore calls it, adds the to-do to _todos again and notifies.
  • The screen keeps the deleted to-do in a local variable, calls delete, then shows ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Deleted ${todo.title}'), action: SnackBarAction(label: 'UNDO', onPressed: () => list.restore(todo)))), where list is context.read<TodoListNotifier>(), read before the SnackBar is shown.
  • The rule (“a deleted to-do can come back”) lives in the notifier; the view only shows the message.
  • TodoListNotifier restore(Todo todo) وەردەگرێت. ناتوانێت add بەکار بهێنێت، چونکە addTodo ژمارەی ناسنامەیەکی نوێ دەدات؛ بۆیە restoreTodo(Todo todo) وەردەگرێت کە هەمان ئەرک (هەمان ژمارە و ناونیشان) دەگەڕێنێتەوە. restore بانگی دەکات، ئەرکەکە دیسان دەخاتەوە ناو _todos و ئاگادار دەکاتەوە.
  • شاشەکە ئەرکە سڕاوەکە لە ناوخۆییدا هەڵدەگرێت، delete بانگ دەکات، پاشان ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Deleted ${todo.title}'), action: SnackBarAction(label: 'UNDO', onPressed: () => list.restore(todo)))) پیشان دەدات، کە تێیدا list هەمان context.read<TodoListNotifier>()ـە و پێش پیشاندانی SnackBarـەکە خوێندراوەتەوە.
  • یاساکە («ئەرکێکی سڕاو دەتوانێت بگەڕێتەوە») لە ChangeNotifierـەکەدا دەژی؛ شاشەکە تەنها پەیامەکە پیشان دەدات.
Exerciseڕاهێنان4. Trace the providers٤. شوێنی providerـەکان بکەوە

Karwan has no to-dos. Write, in order, what happens in the providers and screens when the app starts and the user taps Karwan, then taps +.

کاروان هیچ ئەرکێکی نییە. بە ڕیز بنووسە چی لە providerـەکان و شاشەکاندا ڕوو دەدات کاتێک دەست پێ دەکات و دەست لە کاروان دەدات، پاشان دەست لە +.

Show answerوەڵامەکە پیشان بدە
  1. The FutureProvider starts getUserList(); LoginAsScreen watches [] and shows a spinner.

    FutureProvider getUserList() دەست پێ دەکات؛ LoginAsScreen [] watch دەکات و بازنەی چاوەڕوانی پیشان دەدات.

  2. After 0.5 s the three users arrive; the screen rebuilds with three ListTiles.

    دوای ٠٫٥ چرکە سێ دەگەن؛ شاشەکە بە سێ ListTile دووبارە دروست دەکرێتەوە.

  3. Tap Karwan: ValueNotifier<User?>.value = Karwan notifies; TodoListScreen is pushed.

    دەستلێدان لە کاروان: ValueNotifier<User?>.value = Karwan ئاگادار دەکاتەوە؛ TodoListScreen دەکرێتەوە.

  4. The proxy's update calls setUser(Karwan): loading = true, getTodoList(3) starts; the screen shows a spinner.

    updateـی proxyـەکە setUser(Karwan) بانگ دەکات: loading = true، getTodoList(3) دەست پێ دەکات؛ شاشەکە بازنەی چاوەڕوانی پیشان دەدات.

  5. After 0.5 s an empty list arrives; loading = false; the list is empty.

    دوای ٠٫٥ چرکە لیستێکی بەتاڵ دەگات؛ loading = false؛ لیستەکە بەتاڵە.

  6. Tap +: add('New task') gets id 9 from the service, notifies, and one row appears.

    دەستلێدان لە +: add('New task') ژمارەی 9 لە وەردەگرێت، ئاگادار دەکاتەوە و یەک دێڕ دەردەکەوێت.

Summaryپوختە

  • The lecture's demo has a login-as screen and a to-do screen (log out, tap to toggle, long-press to delete, + to add).
  • The data is two collections (users, todos) described by small immutable model classes; samples use neutral users and never passwords.
  • Three providers above MaterialApp: FutureProvider<List<User>> (loaded once), ChangeNotifierProvider<ValueNotifier<User?>> (the current user), ChangeNotifierProxyProvider for the TodoListNotifier (follows the current user).
  • Screens watch to show and read to act; all rules about the list live in the notifier, which ends every change with notifyListeners().
  • Next, the same app is rebuilt in MVVM with view models in a service locator.
  • نموونەکەی وانەکە شاشەیەکی وەک و شاشەیەکی ئەرکەکانی هەیە (، دەستلێدان بۆ گۆڕین، پەنجە ڕاگرتن بۆ سڕینەوە، + بۆ زیادکردن).
  • دراوەکە دوو کۆمەڵەیە (، ئەرکەکان) کە بچووک و وەسفیان دەکەن؛ نموونەکان بێلایەن بەکار دەهێنن و هەرگیز نا.
  • سێ provider لە سەرووی MaterialAppـەوە: FutureProvider<List<User>> (یەک جار بار دەکرێت)، ChangeNotifierProvider<ValueNotifier<User?>> ( ئێستا)، ChangeNotifierProxyProvider بۆ TodoListNotifier (شوێنی ئێستا دەکەوێت).
  • شاشەکان بۆ پیشاندان watch دەکەن و بۆ کارکردن read دەکەن؛ هەموو یاساکانی لیستەکە لە ChangeNotifierـەکەدا دەژین کە هەموو گۆڕانێک بە notifyListeners() کۆتایی پێ دەهێنێت.
  • دواتر، هەمان بە دووبارە دروست دەکرێتەوە لەگەڵ لە .

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

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

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

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

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

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

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