من اكثر التحديات المواجهه لمبرمجين تطبيقات Flutter هو ادارة الحالة للتطبيق “State Management” ويزداد تعقيد كلما كان التطبيق كبير ويحتوي على الكثير من الشاشات والاوامر والعمليات. ساقوم بشرح ادارة حالة التطبيق الاكثر شيوعاً والمفضلة لمطوري Flutter.
ماهو BLoC ?
اختصاراً Business Logic Component وهو من تطوير مبرمجين شركة Google والذي تم الاعالن عنه في GOOGLE I/O 2018 وهو يعتمد على Reactive Programming للتعامل مع تدفق البيانات للتطبيق .
وهو وسيط بين مصدر البيانات من ناحية وواجهات التطبيق بحيث يقوم بالتنبه للتغييرات من مصدر البيانات “كقواعد البيانات مثلاً” و يقوم بتحديثها في واجهة المستخدم
في هذا الدرس سنقوم باستخدام مكتبة تساعدنا في التعامل مع BLoc بشكل سهل وبسيط بعيداً عن تعقيدات rxDart. المكتبة مدعومة ويقوم مطورها بتحديثها بشكل كامل واضافة مميزات جديدة في كل عملية تحديث مع اصلاح الاخطاء اذا وجدت
انشاء تطبيق جديد واضافة الباكج.
انشئ تطبيق Flutter جديد باسم counter_bloc وقم باضافة الباكج في ملف pubspec.yaml.
flutter create counter_bloc
flutter create counter_bloc
name: flutter_counter
description: A new Flutter project.
version: 1.0.0+1
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^0.21.0
meta: ^1.1.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
التطبيق بسيط سيتكون من زرين واحد مهمته زيادة الرقم والآخر يقوم بانقاص الرقم ونص لعرض قيمة العدد.
Counter Event | اوامر
من خلال شرح فكرة التطبيق يتضح لديك اننا نحتاج الى أمرين فقط واحد لزيادة العدد والآخر يكون لانقاص العدد. عند تنفيذ اي من الاوامر السابقة تتغير حالة التطبيق ويعرض رقم جديد.
enum CounterEvent { increment, decrement }
لانحتاج الى استخدام اوامر معقدة في البداية حتى يتضح لك الفكرة من Bloc بعد ذلك ساقوم بشرح تطبيق اعقد باذن الله
Counter State | حالات
نلاحظ اننا هنا نتعامل مع ارقام وعلى اثر ذلك لانحتاج الى انشاء كائن للحالة.
Counter BLoC
الان سنقوم بكتابة اهم جزئية في التطبيق وهو الوسيط الذي يقوم بالتعامل مع الاوامر وتحديث الحالات على واجهة التطبيق.
class CounterBloc extends Bloc<CounterEvent, int> {
@override
int get initialState => 0;
@override
Stream<int> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
yield currentState - 1;
break;
case CounterEvent.increment:
yield currentState + 1;
break;
}
}
}
عند البداية في كتابة الكائن المسؤول عن BLoc سيقوم باضافة دالتين الأولى هي
int get initialState => 0;
هذه الدالة تقوم باخذ الحالة المبدئية للتطبيق وفي هذه الحالة يكون 0 لان الحلات عندنا من نوع عدد صحيح للتعامل مع الداد.
@override
Stream<int> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
yield currentState - 1;
break;
case CounterEvent.increment:
yield currentState + 1;
break;
}
}
Stream يستمع الى التغييرات في التطبيق ويقوم بتحديث الحالة على حسب الامر المعطى فتلاحظ هناك انقاص عدد وزيادة عدد ويقوم بتحديث التطبيق عند القيام بتغيير الامر والنتيجة.
تطبيق Counter
الان بعد الانتهاء من الجزء الخاص بBLoc سنقوم باضافته مع واجهة التطبيق الخاصة بالتطبيق
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: BlocProvider<CounterBloc>(
builder: (context) => CounterBloc(),
child: CounterPage(),
),
);
}
}
BlocPrivider من مكتبة flutter_bloc وضعناه في بداية التطبيق حتى يقوم بالتعامل مع جميع الوجهات في التطبيق . يفضل عدم وضع BlocProvider في بداية التطبيق الا في حالة تغيير اللغة او التصميم الخاص بالتطبيق وايضاً لعمليات التحقق من تسجيل دخول المستخدم
ايضاً لا نحتاج الى استخدام StateFullWidget مع التطبيق الخاص بنا
شاشة الـCounter
الان انتهينا من جزئ كبير من التطبيق يتبقى علينا ان نقوم بوضع الاوامر لزيادة وانقاص العدد
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Center(
child: Text(
'$count',
style: TextStyle(fontSize: 24.0),
),
);
},
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
counterBloc.dispatch(CounterEvent.increment);
},
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.remove),
onPressed: () {
counterBloc.dispatch(CounterEvent.decrement);
},
),
),
],
),
);
}
}
نستطيع الوصول الى counterBloc لاننا قمنا باضافة شاشة العداد داخل BlocProvider.
استخدمنا BlocBuilder لاعادة بناء الواجة الخاصة بالتطبيق عند تغيير قيمة العداد
نستطيع اضافة bloc كبراميتر اختياري اذا لم نقم بتعريف نوع Bloc عند كتابة BlocBuilder
معلومات اضافية
- يفضل لكل شاشة BLoC خاص بها.
- في حال لديك اكثر من Widget مرتبطة في بعض قم بفصل BLoCs واستخدام Subscribe للتبع الحالة.
- لاتقم بتحديث BLoC معتمد على شاشة اخرى الا مع التاكد من بقاء البيانات
يعيطيك العافية أستاذ أحمد ، من أجمل المقالات يلي قرأتها عن ال BLoC .
شكرا جزيلاً
الله يعافيك
أود فهم ماهي فائدته بالضبط وإذاكان Satstefullwidget يؤدي الغرض فلماذا اتعقد من استخدام Bloc
السبب من استخدام BLoC هو لإدارة حالة التطبيق وفصل UI عن Logic, بحيث يكون اسهل للاختبار وأيضا للتعديل لا حقاً