الآن بعد أن تعرفت على برمجة واجهة المستخدم التعريفية والفرق بين الحالة المؤقتة وحالة التطبيق ، فأنت جاهز لتعلم كيفية إدارة حالة التطبيق بسهولة.
سوف نستخدم الحزمة
provider. إذا كنت جديدًا على Flutter وليس لديك سبب مقنع لاختيار أسلوب مختلف (Redux ، Rx ، hooks ، إلخ) ، فمن المحتمل أن يكون هذا هو أفضل نهج للبدء. الحزمة provider سهلة التعلم ولا تتطلب الكثير من التعليمات البرمجية. يعمل أيضًا بمفاهيم قابلة للتطبيق في جميع الأساليب الأخرى.
ومع ذلك ، إذا كان لديك بالفعل خبرة كبيرة في إدارة الحالة من أطر تفاعلية أخرى ، فيمكنك البحث عن الحزم والبرامج التعليمية الأخرى المدرجة في صفحة الخيارات .
مثال
كمثال ، ضع في اعتبارك التطبيق البسيط التالي.
يحتوي التطبيق على شاشتين منفصلتين: الكتالوج وعربة التسوق (تمثلها عناصر واجهة المستخدم
MyCatalogوعلى MyCartالتوالي). في هذه الحالة ، يعد هذا تطبيقًا للتسوق ، ولكن يمكنك تخيل نفس البنية في تطبيق بسيط للشبكات الاجتماعية (استبدل الكتالوج بكلمة "wall" و cart بـ "المفضلة").
تشتمل شاشة الكتالوج على شريط تطبيق قابل للتخصيص (
MyAppBar) وعرض بالتمرير لعناصر قائمة متعددة ( MyListItems).
هذا هو التطبيق على شكل شجرة من عناصر واجهة المستخدم:
إذن ، لدينا 5 فئات فرعية على الأقل
Widget. يحتاج الكثير منهم إلى الوصول ليقولوا إنهم لا يمتلكون. على سبيل المثال ، كلMyListItemيجب أن تكون قادرًا على إضافة نفسك إلى عربة التسوق. قد يحتاجون أيضًا إلى التحقق مما إذا كان العنصر المعروض حاليًا في سلة التسوق.
يقودنا هذا إلى سؤالنا الأول: أين يجب أن نضع الوضع الحالي للدلو؟
زيادة الشرط
في Flutter ، من المنطقي وضع الحالة فوق الأدوات التي تستخدمها.
لاجل ماذا؟ في الأطر التعريفية مثل Flutter ، إذا كنت تريد تغيير واجهة المستخدم ، فعليك إعادة بنائها. لا يمكنك الذهاب والكتابة فقط
MyCart.updateWith(somethingNew). بمعنى آخر ، من الصعب فرض تغيير عنصر واجهة المستخدم من الخارج عن طريق استدعاء طريقة عليه. وحتى إذا تمكنت من تشغيله ، فستقاتل إطار العمل بدلاً من السماح له بمساعدتك.
// :
void myTapHandler() {
var cartWidget = somehowGetMyCartWidget();
cartWidget.updateWith(item);
}
حتى إذا حصلت على الكود أعلاه للعمل ، فعليك التعامل
MyCartمع ما يلي في الأداة :
// :
Widget build(BuildContext context) {
return SomeWidget(
// .
);
}
void updateWith(Item item) {
// - UI.
}
ستحتاج إلى مراعاة الحالة الحالية لواجهة المستخدم وتطبيق البيانات الجديدة عليها. سيكون من الصعب تجنب الأخطاء هنا.
في Flutter ، تقوم بإنشاء عنصر واجهة مستخدم جديد في كل مرة يتغير محتواها. بدلاً من
MyCart.updateWith(somethingNew)(استدعاء الطريقة) ، يمكنك استخدام MyCart(contents)(منشئ). نظرًا contentsلأنه يمكنك فقط إنشاء عناصر واجهة مستخدم جديدة في طرق بناء الوالدين ، إذا كنت تريد تغييرها ، فيجب أن تكون في الأصل MyCartأو أعلى.
//
void myTapHandler(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
cartModel.add(item);
}
MyCartيوجد
الآن مسار واحد فقط لتنفيذ التعليمات البرمجية لإنشاء أي إصدار من واجهة المستخدم.
//
Widget build(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
return SomeWidget(
// , .
// ···
);
}
في مثالنا ،
contentsيجب أن يكون بتنسيق MyApp. في كل مرة يتغير فيها ، فإنه يعيد بناء MyCart في الأعلى (المزيد عن ذلك لاحقًا). بهذه MyCartالطريقة لا داعي للقلق بشأن دورة الحياة - فهي توضح فقط ما يجب إظهاره لأي محتويات معينة. عندما تتغير ، MyCartستختفي الأداة القديمة ويتم استبدالها بالكامل بالأخرى الجديدة.
هذا ما نعنيه عندما نقول أن الأدوات غير قابلة للتغيير. لا يتغيرون - يتم استبدالهم.
الآن بعد أن عرفنا مكان وضع حالة الجرافة ، دعنا نرى كيفية الوصول إليها.
وصول الدولة
عندما ينقر المستخدم على أحد العناصر الموجودة في الكتالوج ، تتم إضافته إلى سلة التسوق. ولكن بما أن العربة قد انتهت
MyListItem، كيف نفعل ذلك؟
خيار بسيط هو توفير رد اتصال
MyListItemيمكن استدعاؤه عند النقر. وظائف Dart هي كائنات من الدرجة الأولى ، لذا يمكنك تمريرها بأي طريقة تريدها. لذلك ، داخليًا ، MyCatalogيمكنك تحديد ما يلي:
@override
Widget build(BuildContext context) {
return SomeWidget(
// , .
MyListItem(myTapCallback),
);
}
void myTapCallback(Item item) {
print('user tapped on $item');
}
يعمل هذا بشكل جيد ، ولكن بالنسبة لحالة التطبيق التي تحتاج إلى تغييرها من العديد من الأماكن المختلفة ، فسيتعين عليك اجتياز الكثير من عمليات الاسترجاعات ، والتي تصبح مملة بسرعة كبيرة.
لحسن الحظ ، يحتوي Flutter على آليات تسمح للأدوات الذكية بتوفير البيانات والخدمات لأحفادها (بمعنى آخر ، ليس فقط لأحفادهم ، ولكن لأية عناصر واجهة مستخدم مصبغة). كما تتوقعون من الرفرفة، حيث كل شيء هو القطعة ، وهذه الآليات هي أنواع مجرد خاصة من الحاجيات:
InheritedWidget، InheritedNotifier، InheritedModelوغيرها. لن نصفهم هنا لأنهم خارج نطاق ما نحاول القيام به.
بدلاً من ذلك ، سنستخدم حزمة تعمل مع عناصر واجهة مستخدم منخفضة المستوى ولكنها سهلة الاستخدام. انها تسمى
provider.
مع ،
providerلا داعي للقلق بشأن عمليات رد الاتصال أو InheritedWidgets. لكن عليك أن تفهم 3 مفاهيم:
- ChangeNotifier
- ChangeNotifierProvider
- مستهلك
ChangeNotifier
ChangeNotifierعبارة عن فئة بسيطة مضمنة في Flutter SDK والتي تقدم إشعارًا بتغيير الحالة لمستمعيها. بمعنى آخر ، إذا كان هناك شيء ما ChangeNotifier، فيمكنك الاشتراك في تغييراته. (هذا هو شكل من أشكال ملاحظتها - لأولئك الذين لم يعتادوا على هذا المصطلح.)
ChangeNotifierفي providerوسيلة واحدة لتغليف حالة التطبيق. بالنسبة للتطبيقات البسيطة جدًا ، يمكنك الحصول عليها بواحد ChangeNotifier. في النماذج الأكثر تعقيدًا ، سيكون لديك عدة طرز وبالتالي عدة نماذج ChangeNotifiers. (لا تحتاج إلى استخدام ChangeNotifierمع على الإطلاق provider، ولكن من السهل التعامل مع هذا الفصل.)
في نموذج تطبيق التسوق الخاص بنا ، نريد إدارة حالة سلة التسوق
ChangeNotifier. نقوم بإنشاء فئة جديدة تتوسع فيها ، على سبيل المثال:
class CartModel extends ChangeNotifier {
/// .
final List<Item> _items = [];
/// .
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
/// ( , 42 ).
int get totalPrice => _items.length * 42;
/// [item] . [removeAll] - .
void add(Item item) {
_items.add(item);
// , , .
notifyListeners();
}
/// .
void removeAll() {
_items.clear();
// , , .
notifyListeners();
}
}
الجزء الوحيد من الكود المحدد
ChangeNotifierهو المكالمة notifyListeners(). استدع هذه الطريقة في كل مرة يتغير فيها النموذج بطريقة يمكن أن تنعكس في واجهة المستخدم الخاصة بتطبيقك. كل شيء آخر في CartModelالنموذج نفسه ومنطق عمله.
ChangeNotifierهي جزء من flutter:foundationأي فئات ذات مستوى أعلى في Flutter ولا تعتمد عليها. من السهل اختباره (لا تحتاج حتى إلى استخدام اختبار الأدوات لذلك). على سبيل المثال ، إليك اختبار وحدة بسيط CartModel:
test('adding item increases total cost', () {
final cart = CartModel();
final startingPrice = cart.totalPrice;
cart.addListener(() {
expect(cart.totalPrice, greaterThan(startingPrice));
});
cart.add(Item('Dash'));
});
ChangeNotifierProvider
ChangeNotifierProviderهو عنصر واجهة مستخدم يوفر مثيلاً ChangeNotifierلأبنائه. يأتي في حزمة provider.
نحن نعلم بالفعل مكان وضعه
ChangeNotifierProvider: فوق الحاجيات التي تحتاج إلى الوصول إليها. في حالة CartModelوجود شيء ما أعلاه MyCartو MyCatalog.
لا تريد أن تنشر
ChangeNotifierProviderأعلى من اللازم (لأنك لا تريد أن تلوث النطاق). ولكن في حالتنا، فقط القطعة التي انتهت MyCartو MyCatalog- و MyApp.
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MyApp(),
),
);
}
لاحظ أننا نحدد مُنشئًا ينشئ مثيلًا جديدًا
CartModel. ChangeNotifierProviderذكيًا بما يكفي لعدم إعادة البناء CartModelما لم يكن ضروريًا للغاية. يستدعي أيضًا الأمر dispose () في نموذج CartModel تلقائيًا عندما لا تكون هناك حاجة إلى المثيل.
إذا كنت ترغب في تقديم أكثر من فصل ، يمكنك استخدام
MultiProvider:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
Provider(create: (context) => SomeOtherClass()),
],
child: MyApp(),
),
);
}
مستهلك
الآن بعد أن تم
CartModelتوفيره للأدوات في تطبيقنا عبر الإعلان ChangeNotifierProviderفي الأعلى ، يمكننا البدء في استخدامه.
يتم ذلك من خلال القطعة
Consumer.
return Consumer<CartModel>(
builder: (context, cart, child) {
return Text("Total price: ${cart.totalPrice}");
},
);
علينا تحديد نوع النموذج الذي نريد الوصول إليه. في هذه الحالة ، نحتاجها
CartModel، فنكتب Consumer<CartModel>. إذا لم تحدد عام ( <CartModel>) ، providerفلن تتمكن الحزمة من مساعدتك. providerيعتمد على النوع وبدون النوع لن يفهم ما تريد.
الوسيطة الوحيدة المطلوبة للأداة
Consumerهي builder. Builder هي وظيفة يتم استدعاؤها عند التغيير ChangeNotifier. (بعبارة أخرى ، عندما تستدعي notifyListeners()النموذج الخاص بك ، يتم Consumerاستدعاء جميع عمليات
الإنشاء لجميع عناصر واجهة المستخدم ذات الصلة .) يتم استدعاء المُنشئ بثلاث وسائط. الأول هو
contextالذي تحصل عليه أيضًا في كل طريقة بناء.
الوسيطة الثانية لوظيفة builder هي مثال
ChangeNotifier... هذا ما طلبناه منذ البداية. يمكنك استخدام بيانات النموذج لتحديد الكيفية التي يجب أن تنظر بها واجهة المستخدم إلى أي نقطة معينة.
الحجة الثالثة هي
childأنها ضرورية من أجل التحسين. إذا كان لديك عنصر واجهة مستخدم كبير أسفل شجرتك Consumerولا يتغير عندما يتغير النموذج ، فيمكنك بنائه مرة واحدة والحصول عليه عبر أداة الإنشاء .
return Consumer<CartModel>(
builder: (context, cart, child) => Stack(
children: [
// SomeExhibitedWidget, .
child,
Text("Total price: ${cart.totalPrice}"),
],
),
// .
child: SomeExpensiveWidget(),
);
من الأفضل وضع عناصر واجهة المستخدم الخاصة بك في عمق الشجرة قدر الإمكان. لا تريد إعادة بناء أجزاء كبيرة من واجهة المستخدم لمجرد أن بعض التفاصيل قد تغيرت في مكان ما.
//
return Consumer<CartModel>(
builder: (context, cart, child) {
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Text('Total price: ${cart.totalPrice}'),
),
);
},
);
بدلا من هذا:
//
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
),
),
);
Provider.of
في بعض الأحيان لا تحتاج حقًا إلى البيانات الموجودة في النموذج لتغيير واجهة المستخدم ، ولكنك لا تزال بحاجة إلى الوصول إليها. على سبيل المثال ،
ClearCartيسمح الزر للمستخدم بإزالة كل شيء من عربة التسوق. ليس من الضروري عرض محتويات العربة ، فقط اتصل بالطريقة clear().
يمكننا استخدامه لذلك
Consumer<CartModel>، لكن هذا سيكون مضيعة للوقت. سنطلب من إطار العمل إعادة بناء الأداة ، والتي لا تحتاج إلى إعادة بنائها.
في حالة الاستخدام هذه ، يمكننا استخدامها
Provider.ofمع listenضبط المعلمة على false.
Provider.of<CartModel>(context, listen: false).removeAll();
لن يؤدي استخدام السطر أعلاه في طريقة الإنشاء إلى إعادة إنشاء عنصر واجهة المستخدم عند استدعائه
notifyListeners .
ضع كل شيء معا
يمكنك التحقق من المثال الذي تمت مناقشته في هذه المقالة. إذا كنت تبحث عن شيء أبسط قليلاً ، فتحقق من شكل تطبيق Counter المصمم بواسطة المزود .
عندما تكون مستعدًا للعب مع
providerنفسك ، تذكر أن تضيف تبعيتها إلى تبعيتك أولاً pubspec.yaml.
name: my_name
description: Blah blah blah.
# ...
dependencies:
flutter:
sdk: flutter
provider: ^3.0.0
dev_dependencies:
# ...
الآن يمكنك
'package:provider/provider.dart'؛ وابدأ في البناء ...
