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

The Selector Widget: Fewer Rebuildsویجێتی Selector: دووبارە دروستکردنەوەی کەمتر

What a callback is, how Selector<T1, T2> uses a selector and a builder callback, how it compares the old and new selected values to skip rebuilds, the lecture's list-length, user-id and name-versus-age examples with real rebuild counts, context.select, and what may be selected.

چییە، Selector<T1, T2> چۆن selector و builder بەکار دەهێنێت، چۆن نرخی هەڵبژێردراوی کۆن و نوێ بەراورد دەکات بۆ ئەوەی دووبارە دروستکردنەوە بوەستێنێت، نموونەکانی وانەکە (درێژیی لیست، idـی ، ناو بەرامبەر تەمەن) بە ژمارەی ڕاستەقینەی دووبارە دروستکردنەوە، context.select و چی دەتوانرێت هەڵبژێردرێت.

17 min read17 خولەک خوێندنەوە · 13 stepsهەنگاو

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

  • Explain what a callback is and name the two callbacks of a Selector.
  • Set up Selector<T1, T2> with the right provider type and selected type.
  • Describe how Selector compares the previous and new results to decide on a rebuild.
  • Choose between Consumer, Selector and context.select for a given widget.
  • Select immutable values, and several values with a record.
  • ڕوون بکەیتەوە چییە و ناوی هەردوو Selector ببەیت.
  • Selector<T1, T2> بە جۆری دروستی provider و جۆری هەڵبژێردراو دابمەزرێنیت.
  • وەسفی ئەوە بکەیت کە Selector چۆن ئەنجامی پێشوو و نوێ بەراورد دەکات بۆ بڕیاردان لەسەر دووبارە دروستکردنەوە.
  • بۆ دیاریکراو لە نێوان Consumer، Selector و context.selectـدا هەڵبژێریت.
  • نرخی هەڵبژێریت و بۆ چەند نرخێک record بەکار بهێنیت.

When a Consumer rebuilds too oftenکاتێک Consumer زۆر جار دووبارە دروست دەبێتەوە

A Consumer rebuilds its builder every time its provider notifies, whatever changed. That is fine for a small object. But imagine a big Profile with a name, an age, a photo and settings, and a title that shows only the name. A birthday changes the age, the provider notifies, and the title is rebuilt for nothing.

Consumer هەر جارێک providerـەکەی ئاگادار بکاتەوە builderـەکەی دووبارە دروست دەکاتەوە، هەرچی گۆڕابێت. بۆ بچووک ئەمە کێشە نییە. بەڵام Profileـێکی گەورە بهێنە پێش چاوت کە ناو، تەمەن، وێنە و ڕێکخستنی هەیە و ناونیشانێک کە تەنها ناوەکە پیشان دەدات. ڕۆژی لەدایکبوون تەمەن دەگۆڕێت، providerـەکە ئاگادار دەکاتەوە و ناونیشانەکە بێهودە دووبارە دروست دەکرێتەوە.

The lecture's answer is the Selector widget: an equivalent of Consumer that can prevent widgets from being rebuilt when the part they use did not change. You tell it which part you care about, and it rebuilds only when that part is different.

وەڵامی وانەکە Selectorـە: هاوتای Consumerـە، بەڵام دەتوانێت ڕێگە بگرێت لە دووبارە دروستکردنەوەی کاتێک ئەو بەشەی بەکاری دەهێنن نەگۆڕاوە. تۆ پێی دەڵێیت گرنگیت بە کام بەشە و تەنها کاتێک ئەو بەشە جیاواز بێت دووبارە دروست دەبێتەوە.

The football results boardتەختەی ئەنجامی یارییەکان

At the tea house, one fan follows only Erbil's matches. The TV shows every result of the evening, but he looks up only when Erbil's score changes. A Selector is that fan: the provider may announce many changes, but the Selector reacts only to the value it picked.

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

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

The fan still hears every announcement; he just ignores most of them. A Selector is the same: its selector callback runs on every notification, and only the builder is skipped.

هاندەرەکە هێشتا هەموو ڕاگەیاندنێک دەبیستێت؛ تەنها زۆربەیان پشتگوێ دەخات. Selector هەروایە: selector لە هەموو ئاگادارکردنەوەیەکدا جێبەجێ دەبێت و تەنها builderـەکە دەوەستێنرێت.

Quick checkپشکنینی خێراWhat does a Selector add to what a Consumer does?Selector چی زیاد دەکات بۆ ئەوەی Consumer دەیکات؟

Two callbacks: selector and builderدوو کرداری بانگکردنەوە: selector و builder

The lecture stops to explain one word first. A callback is a function you pass as an argument to other code, which that code calls later, when it needs it. You met the idea in functions as values: onPressed is a callback that Flutter calls when the button is tapped.

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

A Selector takes two callbacks, and the Selector itself decides when to call each one:

Selector دوو وەردەگرێت و خودی Selector بڕیار دەدات کەی هەر یەکێکیان بانگ بکات:

  • selector picks the part of the provided data that should be watched for changes. It runs every time the provider notifies.
  • builder builds the widgets from that part, like a Consumer's builder. It runs only when the selected part is new.
  • selector ئەو بەشەی دراوە دابینکراوەکە هەڵدەبژێرێت کە دەبێت چاودێریی گۆڕانەکانی بکرێت. هەر جارێک providerـەکە ئاگادار بکاتەوە جێبەجێ دەبێت.
  • builder لەو بەشەوە دروست دەکات، وەک builderـی Consumer. تەنها کاتێک بەشە هەڵبژێردراوەکە نوێ بێت جێبەجێ دەبێت.
Quick checkپشکنینی خێراWhich of these is a callback?کامیان ؟

Setting up a Selector<T1, T2>دامەزراندنی Selector<T1, T2>

A Selector has two types in its angle brackets: Selector<T1, T2>.

Selector دوو جۆری لەناو کەوانە تیژەکانیدا هەیە: Selector<T1, T2>.

TypeجۆرMeaningواتاIn the badge belowلە نیشانەکەی خوارەوەدا
T1The type of the provider. The Selector uses it to climb up the tree and find the provider.جۆری providerـەکە. Selector بەکاری دەهێنێت بۆ ئەوەی بە سەر بکەوێت و providerـەکە بدۆزێتەوە.TodoListModel
T2The type of the part you watch: what selector returns and builder receives.جۆری ئەو بەشەی چاودێری دەکەیت: ئەوەی selector دەیگەڕێنێتەوە و builder وەری دەگرێت.int

The lecture's example rebuilds a list only when its length changes. Here is the same idea as a to-do counter badge for an app bar. Ticking a to-do as done changes the model but not the number of to-dos, so the badge stays as it is (the model, TodoListModel, is shown in What to select):

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

A badge that rebuilds only when the length changesنیشانەیەک کە تەنها کاتێک درێژی دەگۆڕێت دووبارە دروست دەبێتەوەDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
// Rebuilds only when the NUMBER of to-dos changes.
class TodoCountBadge extends StatelessWidget {
  const TodoCountBadge({super.key});

  @override
  Widget build(BuildContext context) {
    return Selector<TodoListModel, int>(
      selector: (_, list) => list.items.length, // T1 in, T2 out
      builder: (context, count, child) => Badge(
        label: Text('$count'),
        child: const Icon(Icons.checklist),
      ),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 7

T1 = TodoListModel (the provider), T2 = int (the part we watch).

T1 = TodoListModel (providerـەکە)، T2 = int (ئەو بەشەی چاودێری دەکەین).

Lineدێڕی 8

From the whole model, keep only the number of items.

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

Lineدێڕی 9

count is the int the selector returned.

count ئەو intـەیە کە selector گەڕاندییەوە.

Badge is a Material 3 widget that puts a small label on an icon. The test shows the badge was built once at the start, not again after a to-do was ticked, and once more after a new to-do was added.

Badge ⁦Material 3⁩ـە کە نیشانەیەکی بچووک دەخاتە سەر . تاقیکردنەوەکە پیشان دەدات کە نیشانەکە لە سەرەتادا یەک جار دروست کرا، دوای نیشانکردنی ئەرکێک دووبارە دروست نەکرایەوە و دوای زیادکردنی ئەرکێکی نوێ جارێکی تر دروست کرایەوە.

Quick checkپشکنینی خێراIn Selector<TodoListModel, int>, what is int?لە Selector<TodoListModel, int>ـدا، int چییە؟

How Selector decides: the old result against the new oneSelector چۆن بڕیار دەدات: ئەنجامی کۆن بەرامبەر ئەنجامی نوێ

  1. The provider notifiesproviderـەکە ئاگادار دەکاتەوە

    Something in the model changed and notifyListeners() ran.

    شتێک لە گۆڕا و notifyListeners() جێبەجێ بوو.

  2. Selector reads the dataSelector دراوەکە دەخوێنێتەوە

    Like a Consumer, it gets the whole value with Provider.of, and passes it to the selector callback.

    وەک Consumer، هەموو نرخەکە بە Provider.of وەردەگرێت و دەیدات بە selector.

  3. The selector makes new dataselector دراوەی نوێ دروست دەکات

    The selector creates new data from it, usually one small part, for example user.id.

    selector لەوەوە دراوەیەکی نوێ دروست دەکات، زۆرجار بەشێکی بچووک، بۆ نموونە user.id.

  4. Compareبەراوردکردن

    The Selector compares this result with the previous result of the selector.

    Selector ئەم ئەنجامە لەگەڵ ئەنجامی پێشووی selector بەراورد دەکات.

  5. Rebuild or keepدووبارە دروستکردنەوە یان هێشتنەوە

    If they are different, it calls the builder with the new result. If they are equal, it keeps the widget it built last time; the builder is not called.

    ئەگەر جیاواز بن، builder بە ئەنجامە نوێیەکە بانگ دەکات. ئەگەر یەکسان بن، ئەو جاری پێشوو دروستی کردبوو دەیهێڵێتەوە؛ builder بانگ ناکرێت.

A User object with id 7, name 'Aram', city 'Erbil' and an email goes into a funnel labelled selector: (_, user) => user.id. Only the number 7 comes out ('only the id'). A box 'Compare with last time' shows previous: 7 and new: 7. Two outcomes: 'Equal: keep the old widget, builder not called' and 'Different: call the builder with the new value, builder(ctx, 8, child)'. Side note: a name or city change gives 7 again, so there is no rebuild; only a new user (id 8) passes through to the builder. Bottom: Selector<User, int>: User goes in (T1), an int comes out (T2).ئۆبجێکتێکی User بە id 7، ناوی 'Aram'، شاری 'Erbil' و ئیمەیڵێک دەچێتە ناو قیفێکەوە بە نووسراوی selector: (_, user) => user.id. تەنها ژمارە 7 دێتە دەرەوە («تەنها id»). سندووقێکی «بەراورد لەگەڵ جاری پێشوو» previous: 7 و new: 7 پیشان دەدات. دوو ئەنجام: «یەکسان: ویجێتە کۆنەکە بهێڵەرەوە، builder بانگ ناکرێت» و «جیاواز: builder بە نرخە نوێیەکە بانگ بکە، builder(ctx, 8, child)». تێبینی: گۆڕینی ناو یان شار دیسان 7 دەدات، بۆیە دووبارە دروست ناکرێتەوە؛ تەنها بەکارهێنەرێکی نوێ (id 8) دەگاتە builderـەکە. خوارەوە: Selector<User, int>: User دەچێتە ژوورەوە (T1)، int دێتە دەرەوە (T2).
The selector callback is a filter: the whole object goes in, one small value comes out and is compared. selector پاڵاوتەیەکە: هەموو دەچێتە ژوورەوە، نرخێکی بچووک دێتە دەرەوە و بەراورد دەکرێت.CêIT
In selector: (context, data) { return newData; }لە selector: (context, data) { return newData; }ـداMeaningواتا
contextA BuildContext object BuildContext
dataThe whole value obtained from the provider (type T1)هەموو ئەو نرخەی لە providerـەکەوە وەرگیراوە (جۆری T1)
newDataThe result of the selector (type T2): passed to the builder and used for the comparisonئەنجامی selector (جۆری T2): دەدرێت بە builder و بۆ بەراوردکردن بەکار دێت
Quick checkپشکنینی خێراA provider notifies, but the selector returns the same value as last time. What happens?providerـێک ئاگادار دەکاتەوە، بەڵام selector هەمان نرخی جاری پێشوو دەگەڕێنێتەوە. چی ڕوو دەدات؟

The lecture's example: rebuild only for a new userنموونەکەی وانەکە: تەنها بۆ بەکارهێنەرێکی نوێ دووبارە دروست بکەرەوە

The lecture's second example rebuilds the whole Scaffold only if the user id has changed, that is, only when a different person signs in. Note that the builder reads the user with listen: false: the Selector already decides when the builder runs.

نموونەی دووەمی وانەکە هەموو Scaffoldـەکە تەنها ئەگەر idـی گۆڕابێت دووبارە دروست دەکاتەوە، واتە تەنها کاتێک کەسێکی تر دێتە ژوورەوە. ئاگاداری ئەوە بە کە builderـەکە بە listen: false دەخوێنێتەوە: خودی Selector بڕیار دەدات builder کەی جێبەجێ ببێت.

Rebuild the Scaffold only when the user id changesScaffold تەنها کاتێک idـی بەکارهێنەر دەگۆڕێت دووبارە دروست بکەرەوەDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
// The lecture's example: rebuild the Scaffold only if the user id changes.
Widget buildMainScreen(BuildContext context) {
  return Selector<UserModel, int>(
    selector: (_, UserModel user) => user.id,
    builder: (context, uid, child) {
      // read without listening: the builder already runs when the id changes
      final user = Provider.of<UserModel>(context, listen: false);
      return Scaffold(
        appBar: AppBar(
          leading: CircleAvatar(child: Text(user.name[0])),
          title: Text(user.name),
        ),
        body: Center(child: Text('Signed in as user #$uid')),
      );
    },
  );
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 4

The selector callback: from the whole UserModel, return only id (an int).

selector: لە هەموو UserModel، تەنها id بگەڕێنەرەوە (intـێک).

Lineدێڕی 7

Read the rest of the user without listening; the Selector already listens.

ئەوانی تری بەبێ گوێگرتن بخوێنەرەوە؛ Selector خۆی گوێ دەگرێت.

Look at the second line of the output. rename changed the name, the provider notified, but the id was still 1, so the builder did not run and the title still shows the old name. Only switchUser (a new id) rebuilt the screen. That is exactly what the code asks for: the widget rebuilds only if the user id changes, even when other properties such as the name or avatar change.

سەیری دێڕی دووەمی دەرخستەکە بکە. rename ناوەکەی گۆڕی، providerـەکە ئاگاداری کردەوە، بەڵام id هێشتا 1 بوو، بۆیە builder جێبەجێ نەبوو و ناونیشانەکە هێشتا ناوە کۆنەکە پیشان دەدات. تەنها switchUser (idـێکی نوێ) شاشەکەی دووبارە دروست کردەوە. ئەمە ڕێک ئەوەیە کە کۆدەکە داوای دەکات: تەنها ئەگەر idـی بگۆڕێت دووبارە دروست دەبێتەوە، تەنانەت ئەگەر تر وەک ناو یان وێنە بگۆڕێن.

Quick checkپشکنینی خێراWith this Selector, the user's name changes from Aram to Aram K. but the id stays 1. What does the app bar show?بەم Selectorـە، ناوی لە Aramـەوە دەبێت بە ⁦Aram K⁩. بەڵام id هەر 1 دەمێنێتەوە. چی پیشان دەدات؟

Consumer or Selector: the name and the ageConsumer یان Selector: ناو و تەمەن

The lecture compares the two widgets with a model that has a name and an age. Here is that model, and a title written both ways:

وانەکە ئەم دوو بە بەراورد دەکات کە name و ageـی هەیە. ئەمە ئەو و ناونیشانێک کە بە هەردوو شێوە نووسراوە:

DARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
class Profile extends ChangeNotifier {
  String _name = 'Dilan';
  int _age = 20;

  String get name => _name;
  int get age => _age;

  void rename(String newName) {
    _name = newName;
    notifyListeners();
  }

  void birthday() {
    _age++;
    notifyListeners();
  }
}
With a Consumer: rebuilt after any changeبە Consumer: دوای هەر گۆڕانێک دووبارە دروست دەبێتەوەDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
// Consumer: the Text is rebuilt after ANY change, even a birthday.
class NameWithConsumer extends StatelessWidget {
  const NameWithConsumer({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<Profile>(
      builder: (context, profile, child) => Text(profile.name),
    );
  }
}
With a Selector: rebuilt only when the name changesبە Selector: تەنها کاتێک ناو دەگۆڕێت دووبارە دروست دەبێتەوەDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
// Selector: the Text is rebuilt only when the name itself changes.
class NameWithSelector extends StatelessWidget {
  const NameWithSelector({super.key});

  @override
  Widget build(BuildContext context) {
    return Selector<Profile, String>(
      selector: (context, profile) => profile.name,
      builder: (context, name, child) => Text(name),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 8

The selector takes only name out of the model.

selector تەنها name لە دەردەهێنێت.

Animation in three frames. Left: a Profile object with name 'Dilan' and age 20. Middle: a funnel 'selector: p.name' that outputs 'Dilan'. Right: a phone whose app bar shows Dilan. Frame 1: the first build. Frame 2: birthday() makes the age 21; the funnel still outputs 'Dilan' = 'Dilan', 'same: no rebuild', and nothing on the phone changes. Frame 3: rename('Lana'); the funnel outputs 'Lana' ≠ 'Dilan', 'different: builder runs', and only the title Text on the phone is outlined in orange and shows Lana.
Animation in three frames. Left: a Profile object with name 'Dilan' and age 20. Middle: a funnel 'selector: p.name' that outputs 'Dilan'. Right: a phone whose app bar shows Dilan. Frame 1: the first build. Frame 2: birthday() makes the age 21; the funnel still outputs 'Dilan' = 'Dilan', 'same: no rebuild', and nothing on the phone changes. Frame 3: rename('Lana'); the funnel outputs 'Lana' ≠ 'Dilan', 'different: builder runs', and only the title Text on the phone is outlined in orange and shows Lana.
A birthday does not touch a Selector on the name; a new name does.ڕۆژی لەدایکبوون دەست لە Selectorـێک لەسەر ناو نادات؛ ناوێکی نوێ دەستی لێ دەدات.CêIT

A widget test put both titles on one screen, then called birthday() and rename('Lana'):

تاقیکردنەوەیەکی هەردوو ناونیشانەکەی لە یەک شاشەدا دانا، پاشان بانگی birthday() و rename('Lana')ـی کرد:

Real output of a Flutter widget test (flutter test, October 2026)دەرخستەی ڕاستەقینەی تاقیکردنەوەیەکی ویجێتی ⁦Flutter (flutter test⁩، ئۆکتۆبەری ⁦2026⁩)TEXT
start:          {Consumer: 1, Selector: 1}
after birthday: {Consumer: 2, Selector: 1}
after rename:   {Consumer: 3, Selector: 2}

The birthday rebuilt the Consumer's Text, although it shows only the name; the Selector's Text stayed. The rename rebuilt both, because the name really changed. With one Text the saving is tiny. With a large widget, or a long list where every row would rebuild, it matters for performance: less work per change means smoother scrolling and less battery.

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

Quick checkپشکنینی خێراA Selector<Profile, String> selects profile.name. Which call makes its builder run again?Selector<Profile, String>ـێک profile.name هەڵدەبژێرێت. کام بانگکردن builderـەکەی دووبارە جێبەجێ دەکات؟

context.select: a Selector without the widgetcontext.select: Selector بەبێ ویجێت

You met context.select in State Management with Provider. It does the same filtering as a Selector, but inside a build method instead of as a widget of its own:

context.selectـت لە بەڕێوەبردنی دۆخ بە Provider بینی. هەمان پاڵاوتنی Selector دەکات، بەڵام لەناو buildـدا، نەک وەک سەربەخۆ:

The name title again, with context.selectناونیشانی ناو دووبارە، بە context.selectDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
// The same filter without a Selector widget: context.select in build.
class NameText extends StatelessWidget {
  const NameText({super.key});

  @override
  Widget build(BuildContext context) {
    final name = context.select((Profile p) => p.name);
    return Text(name);
  }
}
Selector widget Selectorcontext.select
Selector<Profile, String>(selector: (_, p) => p.name, builder: …)final name = context.select((Profile p) => p.name);
A widget: wraps one part of a build method: بە دەوری بەشێکی buildـدا دەپێچێتA call: the whole widget that calls it is rebuiltبانگکردنێکە: هەموو ئەو بانگی دەکات دووبارە دروست دەبێتەوە
Can keep a static childدەتوانێت childـێکی نەگۆڕ بهێڵێتەوەNo child; keep the widget small insteadchild نییە؛ لە جیاتی ئەوە بچووک بهێڵەرەوە

One rule: context.select works only inside build, and not straight inside a list's itemBuilder (there it would rebuild the whole list). Give each row its own small widget or a Builder:

یەک یاسا: context.select تەنها لەناو buildـدا کار دەکات و ڕاستەوخۆ لەناو itemBuilderـی کار ناکات (لەوێ هەموو دووبارە دروست دەکاتەوە). هەر ڕیزێک بچووکی خۆی یان Builderـێک بدەرێ:

Each row listens only to its own nameهەر ڕیزێک تەنها گوێ لە ناوی خۆی دەگرێتDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
// In a list, select inside a small widget per row, not in itemBuilder.
class NamesList extends StatelessWidget {
  const NamesList({super.key});

  @override
  Widget build(BuildContext context) {
    final count = context.select((ClassModel c) => c.names.length);
    return ListView.builder(
      itemCount: count,
      itemBuilder: (context, index) => Builder(
        builder: (context) {
          // each row listens only to its own name
          final name = context.select((ClassModel c) => c.names[index]);
          return ListTile(title: Text(name));
        },
      ),
    );
  }
}
Quick checkپشکنینی خێراWhere may you call context.select?لە کوێ دەتوانیت بانگی context.select بکەیت؟

What to select: small values that cannot change in placeچی هەڵبژێرین: نرخی بچووک کە لە شوێنی خۆیدا ناگۆڕێن

The comparison only works if the old result is kept as it was. If your selector returns an object that the model later changes in place, the old and new results are the very same object, so they always look equal. The package's documentation says the selected value must be immutable.

بەراوردکردنەکە تەنها کاتێک کار دەکات کە ئەنجامە کۆنەکە وەک خۆی بمێنێتەوە. ئەگەر selectorـەکەت بگەڕێنێتەوە کە دواتر لە شوێنی خۆیدا دەیگۆڕێت، ئەنجامی کۆن و نوێ ڕێک هەمان ، بۆیە هەمیشە یەکسان دەردەکەون. دەڵێن نرخی هەڵبژێردراو دەبێت بێت.

Here is the model behind the badge and this lesson's tests. items returns a new unmodifiable list on every call, but the Todo objects inside are always the same ones, and toggle changes done inside them:

ئەمە ئەو کە نیشانەکە و تاقیکردنەوەکانی ئەم وانەیە بەکاری دەهێنن. items لە هەر بانگکردنێکدا نوێ و نەگۆڕ دەگەڕێنێتەوە، بەڵام Todoـی ناوی هەمیشە هەمان ئەوانەن و toggle ئەو doneـەی لەناو خودی ئەواندایە دەگۆڕێت:

The to-do model (Todo and TodoListModel)مۆدێلی ئەرکەکان (Todo و TodoListModel)DARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
class Todo {
  Todo(this.title);

  final String title;
  bool done = false; // can change in place
}

class TodoListModel extends ChangeNotifier {
  final List<Todo> _items = [Todo('Buy bread'), Todo('Call Shilan')];

  // A new unmodifiable list on every call, holding the SAME Todo objects.
  List<Todo> get items => List.unmodifiable(_items);

  void add(String title) {
    _items.add(Todo(title));
    notifyListeners(); // the length changes
  }

  void toggle(int index) {
    _items[index].done = !_items[index].done; // changed in place
    notifyListeners(); // the length stays the same
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 5

A mutable field: toggle changes it inside the existing object.

گۆڕاو: toggle لەناو هەمان دەیگۆڕێت.

Lineدێڕی 12

A new list each time, but with the same Todo objects in it.

هەر جارێک نوێ، بەڵام هەمان Todoـی تێدایە.

A widget test selected list.items itself and then ticked a to-do. The new list held the very same Todo objects, now with done = true, so the deep comparison found no difference and the screen stayed wrong. Adding a to-do was seen, because the length changed:

تاقیکردنەوەیەکی خودی list.itemsـی هەڵبژارد و پاشان ئەرکێکی نیشان کرد. نوێیەکە ڕێک هەمان Todoـی تێدا بوو، کە ئێستا done = true بوون، بۆیە بەراوردە قووڵەکە هیچ جیاوازییەکی نەدۆزییەوە و شاشەکە هەڵە مایەوە. زیادکردنی ئەرکێک بینرا، چونکە درێژییەکە گۆڕا:

Real output of a Flutter widget test (flutter test, October 2026)دەرخستەی ڕاستەقینەی تاقیکردنەوەیەکی ویجێتی ⁦Flutter (flutter test⁩، ئۆکتۆبەری ⁦2026⁩)TEXT
after toggle: {list: 1}, model says done=true
after add:    {list: 2}
Quick checkپشکنینی خێراYour selector returns model.items, a list whose items are changed in place. The builder never runs after a change. Why?selectorـەکەت model.items دەگەڕێنێتەوە، کە دانەکانی لە شوێنی خۆیاندا دەگۆڕدرێن. builder دوای گۆڕان هەرگیز جێبەجێ نابێت. بۆچی؟

What to select: small values that cannot change in placeچی هەڵبژێرین: نرخی بچووک کە لە شوێنی خۆیدا ناگۆڕێن

Small values, and records for severalنرخی بچووک و record بۆ چەند نرخێک

  • Select small, simple values: an int, a String, a bool, a length, a total.
  • To watch several values, return a Dart 3 record: values in brackets that are compared field by field.
  • If you select your own class, make it immutable (final fields, a new object for every change) and give it an ==.
  • نرخی بچووک و سادە هەڵبژێرە: int، String، bool، درێژییەک، کۆیەک.
  • بۆ چاودێریکردنی چەند نرخێک، recordـێکی ⁦Dart 3⁩ بگەڕێنەرەوە: چەند نرخێک لەناو کەوانەدا کە خانە بە خانە بەراورد دەکرێن.
  • ئەگەر خۆت هەڵدەبژێریت، بیکە بە ( final، نوێ بۆ هەر گۆڕانێک) و ==ـی بۆ بنووسە.
Two values at once with a recordدوو نرخ پێکەوە بە recordDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
// Two values at once: a Dart 3 record compares field by field.
class NameAndAge extends StatelessWidget {
  const NameAndAge({super.key});

  @override
  Widget build(BuildContext context) {
    return Selector<Profile, ({String name, int age})>(
      selector: (_, p) => (name: p.name, age: p.age),
      builder: (context, data, child) => Text('${data.name}, ${data.age}'),
    );
  }
}
Quick checkپشکنینی خێراA title shows both the name and the age of a Profile. What should its selector return so that it rebuilds when either one changes?ناونیشانێک هەم ناو و هەم تەمەنی Profileـێک پیشان دەدات. selectorـەکەی دەبێت چی بگەڕێنێتەوە بۆ ئەوەی کاتێک هەر یەکێکیان دەگۆڕێت دووبارە دروست ببێتەوە؟

Consumer or Selector?Consumer یان Selector؟

PointخاڵConsumer<T>Selector<T1, T2>
Rebuilds whenکەی دووبارە دروست دەبێتەوەThe provider notifies, whatever changedproviderـەکە ئاگادار دەکاتەوە، هەرچی گۆڕابێتThe selected part is different from last timeبەشە هەڵبژێردراوەکە لە جاری پێشوو جیاواز بێت
Callbacksbuilderselector + builder
Builder receivesbuilder وەردەگرێتThe whole valueهەموو نرخەکەOnly the selected partتەنها بەشە هەڵبژێردراوەکە
Best forباشترین بۆWidgets that use most of a small objectئەو زۆربەی بچووک بەکار دەهێننOne part of a big object, in many places or in listsیەک بەشی گەورە، لە چەند شوێنێکدا یان لە لیستەکاندا
Short form in buildشێوەی کورت لە buildـداcontext.watch<T>()context.select((T t) => t.part)

Start with a Consumer (or context.watch): it is simpler. Switch to a Selector when a widget uses only a small part of a large object that changes often. That is how the lecture sums it up: if you have a large state object but only need a small part of it in a widget, a Selector prevents unnecessary rebuilds.

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

Quick checkپشکنینی خێراA Settings object has 15 fields and changes often. A small icon shows only settings.wifiOn. What fits best? Settings ⁦15⁩ هەیە و زۆرجار دەگۆڕێت. بچووک تەنها settings.wifiOn پیشان دەدات. چی لە هەمووی گونجاوترە؟

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

MistakeهەڵەWhat happensچی ڕوو دەداتFixچاکردنەوە
Selecting a mutable object or listهەڵبژاردنی یان گۆڕاوChanges in place are never seen; the screen shows old dataگۆڕانی لە شوێنی خۆیدا هەرگیز نابینرێت؛ شاشەکە دراوەی کۆن پیشان دەداتSelect simple values or a record; keep model objects immutableنرخی سادە یان record هەڵبژێرە؛ بهێڵەرەوە
Reading other fields in the builder with listen: false, then expecting them to updateخوێندنەوەی تر لە builderـدا بە listen: false و چاوەڕوانکردنی نوێبوونەوەیانThey update only when the selected part changesتەنها کاتێک بەشە هەڵبژێردراوەکە دەگۆڕێت نوێ دەبنەوەSelect everything the builder shows (a record)هەموو ئەو شتانەی builder پیشانیان دەدات هەڵبژێرە (recordـێک)
Mixing up T1 and T2تێکەڵکردنی T1 و T2Compile error or ProviderNotFoundException compile یان ProviderNotFoundExceptionT1 = the provider's type, T2 = what selector returnsT1 = جۆری providerـەکە، T2 = ئەوەی selector دەیگەڕێنێتەوە
context.select straight in itemBuildercontext.select ڕاستەوخۆ لەناو itemBuilderـداAssertion error in debug mode assertion لە دۆخی debugـداWrap each row in a Builder or its own widgetهەر ڕیزێک لە Builderـێک یان تایبەت بە خۆیدا بپێچە
A Selector for every tiny widgetSelector بۆ هەموو بچووکMore code, almost no gainکۆدی زیاتر، نزیکەی هیچ قازانجێکUse it where a big object changes oftenلەو شوێنانە بەکاری بهێنە کە گەورە زۆرجار دەگۆڕێت
Quick checkپشکنینی خێراA builder shows name and age, but the Selector selects only name. What goes wrong?builderـێک name و age پیشان دەدات، بەڵام Selector تەنها name هەڵدەبژێرێت. چی هەڵە دەبێت؟

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

Exerciseڕاهێنان1. A total that ignores gift wrap١. کۆیەک کە پشتگوێی پێچانی دیاری دەخات

A CartModel has totalPrice (an int) and a giftWrap switch that does not change the price. Write a TotalText widget that shows Total: … IQD and is not rebuilt when only giftWrap changes.

CartModelـێک totalPriceـی (intـێک) و سویچێکی giftWrapـی هەیە کە نرخ ناگۆڕێت. TotalText بنووسە کە Total: … IQD پیشان بدات و کاتێک تەنها giftWrap دەگۆڕێت دووبارە دروست نەبێتەوە.

Hintئاماژە

Which part does the Text use? Select exactly that.

Textـەکە کام بەش بەکار دەهێنێت؟ ڕێک ئەوە هەڵبژێرە.

Show answerوەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
class TotalText extends StatelessWidget {
  const TotalText({super.key});

  @override
  Widget build(BuildContext context) {
    return Selector<CartModel, int>(
      selector: (_, cart) => cart.totalPrice,
      builder: (context, total, child) => Text('Total: $total IQD'),
    );
  }
}

T1 = CartModel, T2 = int. Toggling gift wrap leaves the total equal, so the builder is skipped. context.select((CartModel c) => c.totalPrice) in a small widget works just as well.

T1 = CartModel، T2 = int. گۆڕینی پێچانی دیاری کۆکە یەکسان دەهێڵێتەوە، بۆیە builder دەوەستێنرێت. context.select((CartModel c) => c.totalPrice) لە بچووکدا هەمان کار دەکات.

Exerciseڕاهێنان2. Count the builds٢. buildـەکان بژمێرە

Selector<Profile, int>(selector: (_, p) => p.age, builder: …) is on screen. Then the app calls rename('Hana'), birthday(), rename('Soran'), birthday(). How many times has the builder run in total?

Selector<Profile, int>(selector: (_, p) => p.age, builder: …) لەسەر شاشەیە. پاشان بانگی rename('Hana')، birthday()، rename('Soran')، birthday() دەکات. builder بە گشتی چەند جار جێبەجێ بووە؟

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

3 times: once for the first build and once after each birthday(). The two renames make the provider notify, but the age stays equal, so the builder is skipped.

3 جار: یەک جار بۆ یەکەم build و یەک جار دوای هەر birthday()ـێک. دوو گۆڕینی ناوەکە وا دەکەن providerـەکە ئاگادار بکاتەوە، بەڵام تەمەن یەکسان دەمێنێتەوە، بۆیە builder دەوەستێنرێت.

Exerciseڕاهێنان3. Why does the list not update?٣. بۆچی لیستەکە نوێ نابێتەوە؟

A classmate writes Selector<TodoListModel, List<Todo>>(selector: (_, m) => m.items, …). Adding a to-do updates the screen, but ticking one as done does not. Explain and fix.

هاوپۆلێک دەنووسێت Selector<TodoListModel, List<Todo>>(selector: (_, m) => m.items, …). زیادکردنی ئەرکێک شاشەکە نوێ دەکاتەوە، بەڵام نیشانکردنی ئەرکێک وەک تەواوبوو نوێی ناکاتەوە. ڕوونی بکەرەوە و چاکی بکە.

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

Adding changes the length, so the deep comparison sees a difference. Ticking changes done inside the same Todo object, so old and new lists hold identical objects and compare equal. (Adding is seen only because items returns a new list each time; if it returned _items itself, the Selector would compare the list with itself and miss even an addition.) Fix: make Todo immutable (final fields and a constructor Todo(this.title, {this.done = false})) and replace the object when it changes, _items[i] = Todo(_items[i].title, done: !_items[i].done); or simply use a Consumer for this list.

زیادکردن درێژی دەگۆڕێت، بۆیە بەراوردە قووڵەکە جیاوازییەک دەبینێت. نیشانکردن done لەناو هەمان Todoـدا دەگۆڕێت، بۆیە لیستی کۆن و نوێ وەک یەکیان تێدایە و یەکسان دەردەچن. (زیادکردن تەنها لەبەر ئەوە دەبینرێت کە items هەر جارێک نوێ دەگەڕێنێتەوە؛ ئەگەر خودی _itemsـی بگەڕاندایەتەوە، Selector لەگەڵ خۆیدا بەراورد دەکرد و تەنانەت زیادکردنیش نەدەبینرا.) چاکردنەوە: Todo بکە بە ( final و Todo(this.title, {this.done = false})) و کاتێک دەگۆڕێت نوێی لە جێ دابنێ، _items[i] = Todo(_items[i].title, done: !_items[i].done)؛ یان بە سادەیی Consumer بۆ ئەم بەکار بهێنە.

Exerciseڕاهێنان4. The lecture's list-length example٤. نموونەی درێژیی لیستەکەی وانەکە

Write the slide's idea in full: a provider of type List<Todo> sits above the screen. Build a ListView that is rebuilt only when the number of to-dos changes.

بیرۆکەکەی سلایدەکە بە تەواوی بنووسە: providerـێکی جۆری List<Todo> لە سەرووی شاشەکەوەیە. ListViewـێک دروست بکە کە تەنها کاتێک ژمارەی ئەرکەکان دەگۆڕێت دووبارە دروست ببێتەوە.

Show answerوەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
// The lecture's idea: rebuild the ListView only when the length changes.
// Here the List<Todo> comes from a provider of type List<Todo>.
class TodoListView extends StatelessWidget {
  const TodoListView({super.key});

  @override
  Widget build(BuildContext context) {
    return Selector<List<Todo>, int>(
      selector: (_, list) => list.length,
      builder: (context, length, child) {
        final todos = Provider.of<List<Todo>>(context, listen: false);
        return ListView.builder(
          itemCount: length,
          itemBuilder: (context, i) => ListTile(title: Text(todos[i].title)),
        );
      },
    );
  }
}

Like the user-id example, the builder reads the list with listen: false. So if a new list arrives with the same length but different titles, the screen keeps the old titles. That is the price of selecting only the length.

وەک نموونەی idـی ، builder بە listen: false دەخوێنێتەوە. بۆیە ئەگەر نوێ بە هەمان درێژی بەڵام ناونیشانی جیاواز بگات، شاشەکە ناونیشانە کۆنەکان دەهێڵێتەوە. ئەمە نرخی هەڵبژاردنی تەنها درێژییەکەیە.

Summaryپوختە

  • A Selector is a Consumer that rebuilds only when the part you select changes.
  • Selector<T1, T2>: T1 is the provider's type (to find it), T2 is the type of the selected part.
  • Two callbacks: selector makes new data from the provider's value; builder builds widgets from it.
  • After every notification the selector runs; the builder runs only if the new result differs from the previous one (deep equality).
  • Whatever the builder reads with listen: false updates only together with the selected part.
  • context.select is the same filter inside build. Select small, immutable values; use a record for several.
  • Selector جۆرە Consumerـێکە کە تەنها کاتێک ئەو بەشەی هەڵیدەبژێریت دەگۆڕێت دووبارە دروست دەبێتەوە.
  • Selector<T1, T2>: T1 جۆری providerـەکەیە (بۆ دۆزینەوەی)، T2 جۆری بەشە هەڵبژێردراوەکەیە.
  • دوو : selector لە نرخی providerـەکە دراوەی نوێ دروست دەکات؛ builder لەوەوە دروست دەکات.
  • دوای هەموو ئاگادارکردنەوەیەک selector جێبەجێ دەبێت؛ builder تەنها ئەگەر ئەنجامە نوێیەکە لە پێشوو جیاواز بێت جێبەجێ دەبێت (یەکسانیی قووڵ).
  • هەرچی builder بە listen: false دەیخوێنێتەوە تەنها لەگەڵ بەشە هەڵبژێردراوەکەدا نوێ دەبێتەوە.
  • context.select هەمان پاڵاوتەیە لەناو buildـدا. نرخی بچووک و هەڵبژێرە؛ بۆ چەند نرخێک record بەکار بهێنە.

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

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

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

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

ChangeNotifier, ChangeNotifierProvider and MultiProviderChangeNotifier، ChangeNotifierProvider و MultiProvider

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

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