دليل الانتقال (Migration Guide)
v10.0.0
Section titled “v10.0.0”package:bloc_test
Section titled “package:bloc_test”❗✨ فصل blocTest عن BlocBase
Section titled “❗✨ فصل blocTest عن BlocBase”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”يجب أن يستخدم blocTest واجهات bloc الأساسية كلما أمكن ذلك لزيادة المرونة
وقابلية إعادة الاستخدام. سابقاً، لم يكن هذا ممكناً لأن BlocBase كان يطبق
StateStreamableSource وهو ما لم يكن كافياً لـ blocTest بسبب الاعتماد الداخلي
على API الـ emit.
package:hydrated_bloc
Section titled “package:hydrated_bloc”❗✨ دعم WebAssembly
Section titled “❗✨ دعم WebAssembly”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”سابقاً، لم يكن من الممكن ترجمة التطبيقات إلى wasm عند استخدام hydrated_bloc.
في الإصدار v10.0.0، تمت إعادة هيكلة الحزمة للسماح بالترجمة إلى wasm.
v9.x.x
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorage.webStorageDirectory : await getTemporaryDirectory(), ); runApp(App());}v10.x.x
void main() async { WidgetsFlutterBinding.ensureInitialized(); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorageDirectory.web : HydratedStorageDirectory((await getTemporaryDirectory()).path), ); runApp(const App());}v9.0.0
Section titled “v9.0.0”package:bloc
Section titled “package:bloc”❗🧹 إزالة واجهات برمجة التطبيقات (APIs) المهجورة
Section titled “❗🧹 إزالة واجهات برمجة التطبيقات (APIs) المهجورة”- تمت إزالة
BlocOverridesلصالحBloc.observerوBloc.transformer.
❗✨ تقديم واجهة EmittableStateStreamableSource الجديدة
Section titled “❗✨ تقديم واجهة EmittableStateStreamableSource الجديدة”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”كانت package:bloc_test سابقاً مرتبطة بشكل وثيق بـ BlocBase. تم تقديم واجهة
EmittableStateStreamableSource للسماح بفصل blocTest عن التطبيق الفعلي لـ
BlocBase.
package:hydrated_bloc
Section titled “package:hydrated_bloc”✨ إعادة تقديم API الـ HydratedBloc.storage
Section titled “✨ إعادة تقديم API الـ HydratedBloc.storage”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”راجع الأسباب الكامنة وراء إعادة تقديم واجهات Bloc.observer و Bloc.transformer.
v8.x.x
Future<void> main() async { final storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorage.webStorageDirectory : await getTemporaryDirectory(), ); HydratedBlocOverrides.runZoned( () => runApp(App()), storage: storage, );}v9.0.0
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorage.webStorageDirectory : await getTemporaryDirectory(), ); runApp(App());}v8.1.0
Section titled “v8.1.0”package:bloc
Section titled “package:bloc”✨ إعادة تقديم واجهات برمجة التطبيقات Bloc.observer و Bloc.transformer
Section titled “✨ إعادة تقديم واجهات برمجة التطبيقات Bloc.observer و Bloc.transformer”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”تم تقديم API الـ BlocOverrides في الإصدار v8.0.0 في محاولة لدعم نطاق (scoping)
إعدادات الـ bloc المحددة مثل BlocObserver و EventTransformer و
HydratedStorage. في تطبيقات Dart الصرفة، عملت التغييرات بشكل جيد؛ ومع ذلك، في
تطبيقات Flutter، تسبب API الجديد في مشاكل أكثر مما حلها.
تم استلهام API الـ BlocOverrides من واجهات مماثلة في Flutter/Dart:
المشاكل
على الرغم من أنه لم يكن السبب الرئيسي لهذه التغييرات، إلا أن API الـ
BlocOverrides أضاف تعقيداً إضافياً للمطورين. فبالإضافة إلى زيادة مقدار التداخل
(nesting) وعدد أسطر الكود اللازمة لتحقيق نفس التأثير، تطلب API الـ
BlocOverrides من المطورين أن يكون لديهم فهم قوي للـ
Zones في Dart.
الـ Zones ليست مفهوماً سهلاً للمبتدئين، والفشل في فهم كيفية عملها قد يؤدي إلى
ظهور أخطاء (مثل عدم تهيئة المراقبين، المحولات، أو مثيلات التخزين).
على سبيل المثال، كان لدى العديد من المطورين كود مثل:
void main() { WidgetsFlutterBinding.ensureInitialized(); BlocOverrides.runZoned(...);}الكود أعلاه، رغم أنه يبدو غير ضار، يمكن أن يؤدي في الواقع إلى العديد من الأخطاء
التي يصعب تتبعها. فأي Zone يتم استدعاء WidgetsFlutterBinding.ensureInitialized
منها في البداية ستكون هي الـ Zone التي يتم فيها التعامل مع أحداث الإيماءات (مثل
استدعاءات onTap و onPressed) بسبب GestureBinding.initInstances. هذه مجرد
واحدة من العديد من المشاكل الناجمة عن استخدام zoneValues.
بالإضافة إلى ذلك، يقوم Flutter بالعديد من الأشياء خلف الكواليس والتي تتضمن تفريع/تعديل الـ Zones (خاصة عند تشغيل الاختبارات) مما قد يؤدي إلى سلوكيات غير متوقعة (وفي كثير من الحالات سلوكيات خارجة عن سيطرة المطور — انظر المشاكل أدناه).
بسبب استخدام
runZoned، أدى
الانتقال إلى API الـ BlocOverrides إلى اكتشاف العديد من الأخطاء/القيود في
Flutter (تحديداً حول اختبارات الـ Widget والـ Integration):
- https://github.com/flutter/flutter/issues/96939
- https://github.com/flutter/flutter/issues/94123
- https://github.com/flutter/flutter/issues/93676
والتي أثرت على العديد من المطورين الذين يستخدمون مكتبة bloc:
- https://github.com/felangel/bloc/issues/3394
- https://github.com/felangel/bloc/issues/3350
- https://github.com/felangel/bloc/issues/3319
v8.0.x
void main() { BlocOverrides.runZoned( () { // ... }, blocObserver: CustomBlocObserver(), eventTransformer: customEventTransformer(), );}v8.1.0
void main() { Bloc.observer = CustomBlocObserver(); Bloc.transformer = customEventTransformer();
// ...}v8.0.0
Section titled “v8.0.0”package:bloc
Section titled “package:bloc”❗✨ تقديم API الـ BlocOverrides الجديد
Section titled “❗✨ تقديم API الـ BlocOverrides الجديد”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”كان API السابق المستخدم لتجاوز الـ BlocObserver والـ EventTransformer
الافتراضيين يعتمد على “singleton” عالمي لكل منهما.
ونتيجة لذلك، لم يكن من الممكن:
- امتلاك تطبيقات متعددة لـ
BlocObserverأوEventTransformerمحصورة في أجزاء مختلفة من التطبيق. - جعل تجاوزات
BlocObserverأوEventTransformerمحصورة في حزمة (package) معينة.- إذا كانت الحزمة تعتمد على
package:blocوسجلت الـBlocObserverالخاص بها، فسيتعين على أي مستخدم للحزمة إما الكتابة فوق الـBlocObserverالخاص بالحزمة أو إرسال التقارير إليه.
- إذا كانت الحزمة تعتمد على
كما كان من الصعب أيضاً إجراء الاختبارات بسبب الحالة العالمية المشتركة عبر الاختبارات.
يقدم Bloc v8.0.0 فئة BlocOverrides التي تسمح للمطورين بتجاوز BlocObserver
و/أو EventTransformer لـ Zone معينة بدلاً من الاعتماد على singleton عالمي
قابل للتغيير.
v7.x.x
void main() { Bloc.observer = CustomBlocObserver(); Bloc.transformer = customEventTransformer();
// ...}v8.0.0
void main() { BlocOverrides.runZoned( () { // ... }, blocObserver: CustomBlocObserver(), eventTransformer: customEventTransformer(), );}ستستخدم مثيلات Bloc الـ BlocObserver و/أو الـ EventTransformer للـ Zone
الحالية عبر BlocOverrides.current. إذا لم تكن هناك BlocOverrides للـ zone،
فستستخدم القيم الافتراضية الداخلية الموجودة (لا تغيير في السلوك/الوظيفة).
هذا يسمح لكل Zone بالعمل بشكل مستقل مع BlocOverrides الخاصة بها.
BlocOverrides.runZoned( () { // BlocObserverA and eventTransformerA final overrides = BlocOverrides.current;
// Blocs in this zone report to BlocObserverA // and use eventTransformerA as the default transformer. // ...
// Later... BlocOverrides.runZoned( () { // BlocObserverB and eventTransformerB final overrides = BlocOverrides.current;
// Blocs in this zone report to BlocObserverB // and use eventTransformerB as the default transformer. // ... }, blocObserver: BlocObserverB(), eventTransformer: eventTransformerB(), ); }, blocObserver: BlocObserverA(), eventTransformer: eventTransformerA(),);❗✨ تحسين معالجة الأخطاء والإبلاغ عنها
Section titled “❗✨ تحسين معالجة الأخطاء والإبلاغ عنها”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”الهدف من هذه التغييرات هو:
- جعل الاستثناءات الداخلية غير المعالجة واضحة للغاية مع الحفاظ على وظائف bloc.
- دعم
addErrorدون تعطيل تدفق التحكم.
سابقاً، كان التعامل مع الأخطاء والإبلاغ عنها يختلف اعتماداً على ما إذا كان
التطبيق يعمل في وضع debug أو release. بالإضافة إلى ذلك، كانت الأخطاء المبلغ عنها
عبر addError تُعامل كاستثناءات غير ملتقطة في وضع debug، مما أدى إلى تجربة مطور
سيئة عند استخدام API الـ addError (تحديداً عند كتابة اختبارات الوحدة).
في v8.0.0، يمكن استخدام addError بأمان للإبلاغ عن الأخطاء ويمكن استخدام
blocTest للتحقق من الإبلاغ عن الأخطاء. لا تزال جميع الأخطاء تُبلغ إلى
onError؛ ومع ذلك، يتم فقط إعادة رمي الاستثناءات غير الملتقطة (بغض النظر عن وضع
debug أو release).
❗🧹 جعل BlocObserver فئة مجردة (abstract)
Section titled “❗🧹 جعل BlocObserver فئة مجردة (abstract)”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”كان المقصود من BlocObserver أن يكون واجهة (interface). وبما أن التطبيقات
الافتراضية للـ API هي عمليات فارغة (no-ops)، فإن BlocObserver الآن فئة
abstract للتواصل بوضوح بأن الفئة مخصصة للتوسيع وليس لإنشاء مثيلات منها مباشرة.
v7.x.x
void main() { // كان من الممكن إنشاء مثيل من الفئة الأساسية.final observer = BlocObserver(); }
**v8.0.0**
```dartclass MyBlocObserver extends BlocObserver {...}
void main() { // لا يمكن إنشاء مثيل من الفئة الأساسية. final observer = BlocObserver(); // خطأ (ERROR)
// قم بتوسيع `BlocObserver` بدلاً من ذلك. final observer = MyBlocObserver(); // مقبول (OK)}❗✨ استدعاء add يرمي StateError إذا كان الـ Bloc مغلقاً
Section titled “❗✨ استدعاء add يرمي StateError إذا كان الـ Bloc مغلقاً”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”سابقاً، كان من الممكن استدعاء add على bloc مغلق وكان الخطأ الداخلي يتم تجاهله،
مما يجعل من الصعب تصحيح سبب عدم معالجة الحدث المضاف. لجعل هذا السيناريو أكثر
وضوحاً، في v8.0.0، سيؤدي استدعاء add على bloc مغلق إلى رمي StateError والذي
سيتم الإبلاغ عنه كاستثناء غير ملتقط وتمريره إلى onError.
❗✨ استدعاء emit يرمي StateError إذا كان الـ Bloc مغلقاً
Section titled “❗✨ استدعاء emit يرمي StateError إذا كان الـ Bloc مغلقاً”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”سابقاً، كان من الممكن استدعاء emit داخل bloc مغلق ولم يكن يحدث أي تغيير في
الحالة، ولكن لم يكن هناك أيضاً أي إشارة إلى الخطأ، مما يجعل التصحيح صعباً. لجعل
هذا السيناريو أكثر وضوحاً، في v8.0.0، سيؤدي استدعاء emit داخل bloc مغلق إلى
رمي StateError والذي سيتم الإبلاغ عنه كاستثناء غير ملتقط وتمريره إلى
onError.
❗🧹 إزالة واجهات برمجة التطبيقات (APIs) المهجورة
Section titled “❗🧹 إزالة واجهات برمجة التطبيقات (APIs) المهجورة”- تمت إزالة
mapEventToStateلصالحon<Event>. - تمت إزالة
transformEventsلصالح API الـEventTransformer. - تمت إزالة تعريف النوع
TransitionFunctionلصالح API الـEventTransformer. - تمت إزالة
listenلصالحstream.listen.
package:bloc_test
Section titled “package:bloc_test”✨ MockBloc و MockCubit لم يعودا يتطلبان registerFallbackValue
Section titled “✨ MockBloc و MockCubit لم يعودا يتطلبان registerFallbackValue”تكون هناك حاجة لـ registerFallbackValue فقط عند استخدام المطابق any() من
package:mocktail لنوع مخصص. سابقاً، كانت هناك حاجة لـ registerFallbackValue
لكل Event و State عند استخدام MockBloc أو MockCubit.
v8.x.x
class FakeMyEvent extends Fake implements MyEvent {}class FakeMyState extends Fake implements MyState {}class MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}
void main() { setUpAll(() { registerFallbackValue(FakeMyEvent()); registerFallbackValue(FakeMyState()); });
// الاختبارات...}v9.0.0
class MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}
void main() { // الاختبارات...}package:hydrated_bloc
Section titled “package:hydrated_bloc”❗✨ تقديم API الـ HydratedBlocOverrides الجديد
Section titled “❗✨ تقديم API الـ HydratedBlocOverrides الجديد”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”سابقاً، كان يتم استخدام singleton عالمي لتجاوز تطبيق الـ Storage.
ونتيجة لذلك، لم يكن من الممكن امتلاك تطبيقات Storage متعددة محصورة في أجزاء
مختلفة من التطبيق. كما كان من الصعب أيضاً إجراء الاختبارات بسبب الحالة العالمية
المشتركة عبر الاختبارات.
يقدم HydratedBloc v8.0.0 فئة HydratedBlocOverrides التي تسمح للمطورين بتجاوز
الـ Storage لـ Zone معينة بدلاً من الاعتماد على singleton عالمي قابل
للتغيير.
v7.x.x
void main() async { HydratedBloc.storage = await HydratedStorage.build( storageDirectory: await getApplicationSupportDirectory(), );
// ...}v8.0.0
void main() { final storage = await HydratedStorage.build( storageDirectory: await getApplicationSupportDirectory(), );
HydratedBlocOverrides.runZoned( () { // ... }, storage: storage, );}ستستخدم مثيلات HydratedBloc الـ Storage للـ Zone الحالية عبر
HydratedBlocOverrides.current.
هذا يسمح لكل Zone بالعمل بشكل مستقل مع BlocOverrides الخاصة بها.
v7.2.0
Section titled “v7.2.0”package:bloc
Section titled “package:bloc”✨ تقديم API الـ on<Event> الجديد
Section titled “✨ تقديم API الـ on<Event> الجديد”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”تم تقديم API الـ on<Event> كجزء من
مقترح استبدال mapEventToState بـ on<Event> في Bloc.
بسبب مشكلة في Dart، ليس من
الواضح دائماً ما ستكون عليه قيمة الـ state عند التعامل مع مولدات غير متزامنة
متداخلة (async*). على الرغم من وجود طرق للالتفاف على المشكلة، إلا أن أحد
المبادئ الأساسية لمكتبة bloc هو أن تكون قابلة للتنبؤ. تم إنشاء API الـ
on<Event> لجعل المكتبة آمنة قدر الإمكان للاستخدام وللقضاء على أي شك فيما يتعلق
بتغييرات الحالة.
يسمح لك on<E> بتسجيل معالج أحداث (event handler) لجميع الأحداث من النوع E.
بشكل افتراضي، سيتم معالجة الأحداث بشكل متزامن (concurrently) عند استخدام on<E>
على عكس mapEventToState الذي يعالج الأحداث بشكل تسلسلي (sequentially).
v7.1.0
abstract class CounterEvent {}class Increment extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0);
@override Stream<int> mapEventToState(CounterEvent event) async* { if (event is Increment) { yield state + 1; } }}v7.2.0
abstract class CounterEvent {}class Increment extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0) { on<Increment>((event, emit) => emit(state + 1)); }}إذا كنت ترغب في الاحتفاظ بنفس السلوك تماماً كما في v7.1.0، يمكنك تسجيل معالج
أحداث واحد لجميع الأحداث وتطبيق محول تسلسلي (sequential transformer):
import 'package:bloc/bloc.dart';import 'package:bloc_concurrency/bloc_concurrency.dart';
class MyBloc extends Bloc<MyEvent, MyState> { MyBloc() : super(MyState()) { on<MyEvent>(_onEvent, transformer: sequential()) }
FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async { // TODO: المنطق البرمجي يوضع هنا... }}يمكنك أيضاً تجاوز الـ EventTransformer الافتراضي لجميع الـ blocs في تطبيقك:
import 'package:bloc/bloc.dart';import 'package:bloc_concurrency/bloc_concurrency.dart';
void main() { Bloc.transformer = sequential<dynamic>(); ...}✨ تقديم API الـ EventTransformer الجديد
Section titled “✨ تقديم API الـ EventTransformer الجديد”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”فتح API الـ on<Event> الباب أمام القدرة على توفير محول أحداث مخصص لكل معالج
أحداث. تم تقديم تعريف نوع (typedef) جديد باسم EventTransformer والذي يمكّن
المطورين من تحويل تدفق الأحداث الواردة لكل معالج أحداث بدلاً من الاضطرار إلى
تحديد محول أحداث واحد لجميع الأحداث.
الـ EventTransformer مسؤول عن أخذ تدفق الأحداث الواردة جنباً إلى جنب مع
EventMapper (معالج الأحداث الخاص بك) وإرجاع تدفق جديد من الأحداث.
typedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)يقوم الـ EventTransformer الافتراضي بمعالجة جميع الأحداث بشكل متزامن ويبدو
كالتالي:
EventTransformer<E> concurrent<E>() { return (events, mapper) => events.flatMap(mapper);}v7.1.0
@overrideStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) { return events .debounceTime(const Duration(milliseconds: 300)) .flatMap(transitionFn);}v7.2.0
/// تعريف `EventTransformer` مخصصEventTransformer<MyEvent> debounce<MyEvent>(Duration duration) { return (events, mapper) => events.debounceTime(duration).flatMap(mapper);}
MyBloc() : super(MyState()) { /// تطبيق الـ `EventTransformer` المخصص على الـ `EventHandler` on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))}⚠️ وضع علامة “مهجور” على API الـ transformTransitions
Section titled “⚠️ وضع علامة “مهجور” على API الـ transformTransitions”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”تجعل أداة الحصول (getter) الـ stream في Bloc من السهل تجاوز تدفق الحالات
الصادر، وبالتالي لم يعد من المفيد الحفاظ على API منفصل لـ
transformTransitions.
v7.1.0
@overrideStream<Transition<Event, State>> transformTransitions( Stream<Transition<Event, State>> transitions,) { return transitions.debounceTime(const Duration(milliseconds: 42));}v7.2.0
@overrideStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));v7.0.0
Section titled “v7.0.0”package:bloc
Section titled “package:bloc”❗ الـ Bloc والـ Cubit يوسعان BlocBase
Section titled “❗ الـ Bloc والـ Cubit يوسعان BlocBase”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”كمطور، كانت العلاقة بين الـ blocs والـ cubits غريبة بعض الشيء. عندما تم تقديم cubit لأول مرة، بدأ كفئة أساسية للـ blocs، وهو ما كان منطقياً لأنه كان يحتوي على مجموعة فرعية من الوظائف وكان الـ blocs يوسعون Cubit ببساطة ويعرفون واجهات برمجة تطبيقات إضافية. جاء ذلك مع بعض العيوب:
-
كان لابد من إعادة تسمية جميع واجهات برمجة التطبيقات لتقبل cubit من أجل الدقة، أو كان لابد من إبقائها كـ bloc من أجل الاتساق على الرغم من أنها غير دقيقة هرمياً (#1708، #1560).
-
كان على Cubit توسيع Stream وتطبيق EventSink من أجل الحصول على قاعدة مشتركة يمكن بناء عناصر واجهة المستخدم مثل BlocBuilder و BlocListener وغيرها عليها (#1429).
لاحقاً، جربنا عكس العلاقة وجعل bloc هو القاعدة
والذي حل جزئياً النقطة الأولى أعلاه ولكنه قدم مشاكل أخرى:
- أصبح API الـ cubit متضخماً بسبب واجهات برمجة تطبيقات bloc الأساسية مثل
mapEventToState و add وغيرها
(#2228).
- يمكن للمطورين تقنياً استدعاء هذه الواجهات وتخريب الأشياء.
- لا نزال نواجه نفس المشكلة المتمثلة في كشف cubit لكامل API الـ stream كما كان من قبل (#1429).
لمعالجة هذه المشكلات، قدمنا فئة أساسية لكل من Bloc و Cubit تسمى BlocBase
بحيث لا تزال المكونات العلوية قادرة على التعامل مع كل من مثيلات bloc و cubit
ولكن دون كشف كامل واجهات Stream و EventSink مباشرة.
BlocObserver
v6.1.x
class SimpleBlocObserver extends BlocObserver { @override void onCreate(Cubit cubit) {...}
@override void onEvent(Bloc bloc, Object event) {...}
@override void onChange(Cubit cubit, Object event) {...}
@override void onTransition(Bloc bloc, Transition transition) {...}
@override void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}
@override void onClose(Cubit cubit) {...}}v7.0.0
class SimpleBlocObserver extends BlocObserver { @override void onCreate(BlocBase bloc) {...}
@override void onEvent(Bloc bloc, Object event) {...}
@override void onChange(BlocBase bloc, Object? event) {...}
@override void onTransition(Bloc bloc, Transition transition) {...}
@override void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}
@override void onClose(BlocBase bloc) {...}}Bloc/Cubit
v6.1.x
final bloc = MyBloc();bloc.listen((state) {...});
final cubit = MyCubit();cubit.listen((state) {...});v7.0.0
final bloc = MyBloc();bloc.stream.listen((state) {...});
final cubit = MyCubit();cubit.stream.listen((state) {...});package:bloc_test
Section titled “package:bloc_test”❗ seed ترجع دالة لدعم القيم الديناميكية
Section titled “❗ seed ترجع دالة لدعم القيم الديناميكية”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”من أجل دعم وجود قيمة “بذرة” (seed) قابلة للتغيير ويمكن تحديثها ديناميكياً في
setUp ، فإن seed ترجع دالة.
v7.x.x
blocTest( '...', seed: MyState(), ...);v8.0.0
blocTest( '...', seed: () => MyState(), ...);❗ expect ترجع دالة لدعم القيم الديناميكية وتتضمن دعم الـ matcher
Section titled “❗ expect ترجع دالة لدعم القيم الديناميكية وتتضمن دعم الـ matcher”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”من أجل دعم وجود توقع (expectation) قابل للتغيير ويمكن تحديثه ديناميكياً في
setUp ، فإن expect ترجع دالة. كما يدعم expect أيضاً الـ Matchers.
v7.x.x
blocTest( '...', expect: [MyStateA(), MyStateB()], ...);v8.0.0
blocTest( '...', expect: () => [MyStateA(), MyStateB()], ...);
// يمكن أن يكون أيضاً `Matcher`blocTest( '...', expect: () => contains(MyStateA()), ...);❗ errors ترجع دالة لدعم القيم الديناميكية وتتضمن دعم الـ matcher
Section titled “❗ errors ترجع دالة لدعم القيم الديناميكية وتتضمن دعم الـ matcher”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”من أجل دعم وجود أخطاء قابلة للتغيير ويمكن تحديثها ديناميكياً في setUp ، فإن
errors ترجع دالة. كما يدعم errors أيضاً الـ Matchers.
v7.x.x
blocTest( '...', errors: [MyError()], ...);v8.0.0
blocTest( '...', errors: () => [MyError()], ...);
// يمكن أن يكون أيضاً `Matcher`blocTest( '...', errors: () => contains(MyError()), ...);❗ MockBloc و MockCubit
Section titled “❗ MockBloc و MockCubit”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”لدعم محاكاة (stubbing) مختلف واجهات برمجة التطبيقات الأساسية، يتم تصدير
MockBloc و MockCubit كجزء من حزمة bloc_test. سابقاً، كان يجب استخدام
MockBloc لكل من مثيلات Bloc و Cubit وهو ما لم يكن بديهياً.
v7.x.x
class MockMyBloc extends MockBloc<MyState> implements MyBloc {}class MockMyCubit extends MockBloc<MyState> implements MyBloc {}v8.0.0
class MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}class MockMyCubit extends MockCubit<MyState> implements MyCubit {}❗ التكامل مع Mocktail
Section titled “❗ التكامل مع Mocktail”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”نظراً للقيود المختلفة لحزمة package:mockito
الآمنة من القيم الخالية (null-safe) والموضحة
هنا،
يتم استخدام package:mocktail بواسطة
MockBloc و MockCubit. يتيح ذلك للمطورين الاستمرار في استخدام واجهة محاكاة
مألوفة دون الحاجة إلى كتابة stubs يدوياً أو الاعتماد على توليد الكود.
v7.x.x
import 'package:mockito/mockito.dart';
...
when(bloc.state).thenReturn(MyState());verify(bloc.add(any)).called(1);v8.0.0
import 'package:mocktail/mocktail.dart';
...
when(() => bloc.state).thenReturn(MyState());verify(() => bloc.add(any())).called(1);يرجى الرجوع إلى #347 بالإضافة إلى توثيق mocktail لمزيد من المعلومات.
package:flutter_bloc
Section titled “package:flutter_bloc”❗ إعادة تسمية معامل cubit إلى bloc
Section titled “❗ إعادة تسمية معامل cubit إلى bloc”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”نتيجة لإعادة الهيكلة في package:bloc لتقديم BlocBase الذي يوسعه كل من Bloc
و Cubit ، تمت إعادة تسمية معاملات BlocBuilder و BlocConsumer و
BlocListener من cubit إلى bloc لأن هذه العناصر تعمل على نوع BlocBase.
كما أن هذا يتماشى بشكل أكبر مع اسم المكتبة ونأمل أن يحسن من قابلية القراءة.
v6.1.x
BlocBuilder( cubit: myBloc, ...)
BlocListener( cubit: myBloc, ...)
BlocConsumer( cubit: myBloc, ...)v7.0.0
BlocBuilder( bloc: myBloc, ...)
BlocListener( bloc: myBloc, ...)
BlocConsumer( bloc: myBloc, ...)package:hydrated_bloc
Section titled “package:hydrated_bloc”❗ storageDirectory مطلوب عند استدعاء HydratedStorage.build
Section titled “❗ storageDirectory مطلوب عند استدعاء HydratedStorage.build”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”من أجل جعل package:hydrated_bloc حزمة Dart صرفة، تمت إزالة الاعتماد على
package:path_provider وأصبح معامل
storageDirectory عند استدعاء HydratedStorage.build مطلوباً ولم يعد يعود
افتراضياً إلى getTemporaryDirectory.
v6.x.x
HydratedBloc.storage = await HydratedStorage.build();v7.0.0
import 'package:path_provider/path_provider.dart';
...
HydratedBloc.storage = await HydratedStorage.build( storageDirectory: await getTemporaryDirectory(),);v6.1.0
Section titled “v6.1.0”package:flutter_bloc
Section titled “package:flutter_bloc”❗ وضع علامة “مهجور” على context.bloc و context.repository لصالح context.read و context.watch
Section titled “❗ وضع علامة “مهجور” على context.bloc و context.repository لصالح context.read و context.watch”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”تمت إضافة context.read و context.watch و context.select لتتماشى مع واجهة
برمجة تطبيقات provider الحالية التي يألفها
العديد من المطورين وللمعالجة المشكلات التي أثارها المجتمع. لتحسين أمان الكود
والحفاظ على الاتساق، تم وضع علامة “مهجور” على context.bloc لأنه يمكن استبداله
بـ context.read أو context.watch اعتماداً على ما إذا كان يتم استخدامه مباشرة
داخل build.
context.watch
يعالج context.watch طلب الحصول على
MultiBlocBuilder لأنه يمكننا
مراقبة عدة blocs داخل Builder واحد من أجل عرض واجهة المستخدم بناءً على حالات
متعددة:
Builder( builder: (context) { final stateA = context.watch<BlocA>().state; final stateB = context.watch<BlocB>().state; final stateC = context.watch<BlocC>().state;
// إرجاع Widget يعتمد على حالة BlocA و BlocB و BlocC });context.select
يسمح context.select للمطورين بعرض/تحديث واجهة المستخدم بناءً على جزء من حالة
الـ bloc ويعالج طلب الحصول على
buildWhen أبسط.
final name = context.select((UserBloc bloc) => bloc.state.user.name);تسمح لنا القطعة البرمجية أعلاه بالوصول إلى الـ widget وإعادة بنائه فقط عندما يتغير اسم المستخدم الحالي.
context.read
على الرغم من أن context.read يبدو مطابقاً لـ context.bloc ، إلا أن هناك بعض
الاختلافات الدقيقة ولكن الهامة. كلاهما يسمح لك بالوصول إلى bloc باستخدام
BuildContext ولا يؤديان إلى إعادة البناء؛ ومع ذلك، لا يمكن استدعاء
context.read مباشرة داخل طريقة build. هناك سببان رئيسيان لاستخدام
context.bloc داخل build:
- للوصول إلى حالة الـ bloc
@overrideWidget build(BuildContext context) { final state = context.bloc<MyBloc>().state; return Text('$state');}الاستخدام أعلاه عرضة للخطأ لأن عنصر الـ Text لن يتم إعادة بنائه إذا تغيرت حالة
الـ bloc. في هذا السيناريو، يجب استخدام إما BlocBuilder أو context.watch.
@overrideWidget build(BuildContext context) { final state = context.watch<MyBloc>().state; return Text('$state');}أو
@overrideWidget build(BuildContext context) { return BlocBuilder<MyBloc, MyState>( builder: (context, state) => Text('$state'), );}- للوصول إلى الـ bloc حتى يمكن إضافة حدث
@overrideWidget build(BuildContext context) { final bloc = context.bloc<MyBloc>(); return ElevatedButton( onPressed: () => bloc.add(MyEvent()), ... )}الاستخدام أعلاه غير فعال لأنه يؤدي إلى البحث عن الـ bloc في كل عملية إعادة بناء
بينما لا تكون هناك حاجة للـ bloc إلا عندما ينقر المستخدم على الـ
ElevatedButton. في هذا السيناريو، يفضل استخدام context.read للوصول إلى الـ
bloc مباشرة حيث تبرز الحاجة إليه (في هذه الحالة، في استدعاء onPressed).
@overrideWidget build(BuildContext context) { return ElevatedButton( onPressed: () => context.read<MyBloc>().add(MyEvent()), ... )}v6.0.x
@overrideWidget build(BuildContext context) { final bloc = context.bloc<MyBloc>(); return ElevatedButton( onPressed: () => bloc.add(MyEvent()), ... )}v6.1.x
@overrideWidget build(BuildContext context) { return ElevatedButton( onPressed: () => context.read<MyBloc>().add(MyEvent()), ... )}?> إذا كنت تصل إلى bloc لإضافة حدث، فقم بالوصول إليه باستخدام context.read في
الاستدعاء (callback) حيث تبرز الحاجة إليه.
v6.0.x
@overrideWidget build(BuildContext context) { final state = context.bloc<MyBloc>().state; return Text('$state');}v6.1.x
@overrideWidget build(BuildContext context) { final state = context.watch<MyBloc>().state; return Text('$state');}?> استخدم context.watch عند الوصول إلى حالة الـ bloc لضمان إعادة بناء الـ
widget عند تغير الحالة.
v6.0.0
Section titled “v6.0.0”package:bloc
Section titled “package:bloc”❗ BlocObserver.onError يأخذ Cubit
Section titled “❗ BlocObserver.onError يأخذ Cubit”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”بسبب دمج Cubit ، أصبحت onError الآن مشتركة بين كل من مثيلات Bloc و
Cubit. وبما أن Cubit هو القاعدة، فإن BlocObserver سيقبل نوع Cubit بدلاً
من نوع Bloc في تجاوز onError.
v5.x.x
class MyBlocObserver extends BlocObserver { @override void onError(Bloc bloc, Object error, StackTrace stackTrace) { super.onError(bloc, error, stackTrace); }}v6.0.0
class MyBlocObserver extends BlocObserver { @override void onError(Cubit cubit, Object error, StackTrace stackTrace) { super.onError(cubit, error, stackTrace); }}❗ الـ Bloc لا يرسل الحالة الأخيرة عند الاشتراك
Section titled “❗ الـ Bloc لا يرسل الحالة الأخيرة عند الاشتراك”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”تم إجراء هذا التغيير لمواءمة Bloc و Cubit مع سلوك الـ Stream المدمج في
Dart. بالإضافة إلى ذلك، أدى الالتزام بالسلوك القديم في سياق Cubit إلى العديد
من الآثار الجانبية غير المقصودة وعقد بشكل عام التطبيقات الداخلية للحزم الأخرى
مثل flutter_bloc و bloc_test دون داعٍ (مما تطلب استخدام skip(1) ، إلخ…).
v5.x.x
final bloc = MyBloc();bloc.listen(print);سابقاً، كانت القطعة البرمجية أعلاه تطبع الحالة الأولية للـ bloc متبوعة بتغييرات الحالة اللاحقة.
v6.x.x
في v6.0.0، لا تطبع القطعة البرمجية أعلاه الحالة الأولية وتطبع فقط تغييرات الحالة اللاحقة. يمكن تحقيق السلوك السابق بما يلي:
final bloc = MyBloc();print(bloc.state);bloc.listen(print);?> ملاحظة: سيؤثر هذا التغيير فقط على الكود الذي يعتمد على اشتراكات bloc
المباشرة. عند استخدام BlocBuilder أو BlocListener أو BlocConsumer ، لن
يكون هناك تغيير ملحوظ في السلوك.
package:bloc_test
Section titled “package:bloc_test”❗ MockBloc يتطلب نوع الحالة (State) فقط
Section titled “❗ MockBloc يتطلب نوع الحالة (State) فقط”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”هذا ليس ضرورياً ويزيل الكود الزائد مع جعل MockBloc متوافقاً مع Cubit.
v5.x.x
class MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}v6.0.0
class MockCounterBloc extends MockBloc<int> implements CounterBloc {}❗ whenListen يتطلب نوع الحالة (State) فقط
Section titled “❗ whenListen يتطلب نوع الحالة (State) فقط”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”هذا ليس ضرورياً ويزيل الكود الزائد مع جعل whenListen متوافقاً مع Cubit.
v5.x.x
whenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));v6.0.0
whenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));❗ blocTest لا يتطلب نوع الحدث (Event)
Section titled “❗ blocTest لا يتطلب نوع الحدث (Event)”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”هذا ليس ضرورياً ويزيل الكود الزائد مع جعل blocTest متوافقاً مع Cubit.
v5.x.x
blocTest<CounterBloc, CounterEvent, int>( 'emits [1] when increment is called', build: () async => CounterBloc(), act: (bloc) => bloc.add(CounterEvent.increment), expect: const <int>[1],);v6.0.0
blocTest<CounterBloc, int>( 'emits [1] when increment is called', build: () => CounterBloc(), act: (bloc) => bloc.add(CounterEvent.increment), expect: const <int>[1],);❗ القيمة الافتراضية لـ skip في blocTest هي 0
Section titled “❗ القيمة الافتراضية لـ skip في blocTest هي 0”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”بما أن مثيلات bloc و cubit لن ترسل الحالة الأخيرة للاشتراكات الجديدة، لم يعد
من الضروري جعل القيمة الافتراضية لـ skip هي 1.
v5.x.x
blocTest<CounterBloc, CounterEvent, int>( 'emits [0] when skip is 0', build: () async => CounterBloc(), skip: 0, expect: const <int>[0],);v6.0.0
blocTest<CounterBloc, int>( 'emits [] when skip is 0', build: () => CounterBloc(), skip: 0, expect: const <int>[],);يمكن اختبار الحالة الأولية لـ bloc أو cubit بما يلي:
test('initial state is correct', () { expect(MyBloc().state, InitialState());});❗ جعل build في blocTest متزامناً (synchronous)
Section titled “❗ جعل build في blocTest متزامناً (synchronous)”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”سابقاً، تم جعل build غير متزامن (async) بحيث يمكن إجراء تحضيرات متنوعة لوضع
الـ bloc تحت الاختبار في حالة معينة. لم يعد هذا ضرورياً كما أنه يحل العديد من
المشكلات الناتجة عن التأخير المضاف بين البناء والاشتراك داخلياً. بدلاً من إجراء
تحضير غير متزامن لوضع bloc في حالة مطلوبة، يمكننا الآن تعيين حالة الـ bloc عن
طريق ربط emit بالحالة المطلوبة.
v5.x.x
blocTest<CounterBloc, CounterEvent, int>( 'emits [2] when increment is added', build: () async { final bloc = CounterBloc(); bloc.add(CounterEvent.increment); await bloc.take(2); return bloc; } act: (bloc) => bloc.add(CounterEvent.increment), expect: const <int>[2],);v6.0.0
blocTest<CounterBloc, int>( 'emits [2] when increment is added', build: () => CounterBloc()..emit(1), act: (bloc) => bloc.add(CounterEvent.increment), expect: const <int>[2],);package:flutter_bloc
Section titled “package:flutter_bloc”❗ إعادة تسمية معامل bloc في BlocBuilder إلى cubit
Section titled “❗ إعادة تسمية معامل bloc في BlocBuilder إلى cubit”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”من أجل جعل BlocBuilder يعمل مع مثيلات bloc و cubit ، تمت إعادة تسمية معامل
bloc إلى cubit (بما أن Cubit هو الفئة الأساسية).
v5.x.x
BlocBuilder( bloc: myBloc, builder: (context, state) {...})v6.0.0
BlocBuilder( cubit: myBloc, builder: (context, state) {...})❗ إعادة تسمية معامل bloc في BlocListener إلى cubit
Section titled “❗ إعادة تسمية معامل bloc في BlocListener إلى cubit”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”من أجل جعل BlocListener يعمل مع مثيلات bloc و cubit ، تمت إعادة تسمية
معامل bloc إلى cubit (بما أن Cubit هو الفئة الأساسية).
v5.x.x
BlocListener( bloc: myBloc, listener: (context, state) {...})v6.0.0
BlocListener( cubit: myBloc, listener: (context, state) {...})❗ إعادة تسمية معامل bloc في BlocConsumer إلى cubit
Section titled “❗ إعادة تسمية معامل bloc في BlocConsumer إلى cubit”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”من أجل جعل BlocConsumer يعمل مع مثيلات bloc و cubit ، تمت إعادة تسمية
معامل bloc إلى cubit (بما أن Cubit هو الفئة الأساسية).
v5.x.x
BlocConsumer( cubit: myBloc, listener: (context, state) {...}, builder: (context, state) {...})v6.0.0
BlocConsumer( cubit: myBloc, listener: (context, state) {...}, builder: (context, state) {...})v5.0.0
Section titled “v5.0.0”package:bloc
Section titled “package:bloc”❗ إزالة initialState
Section titled “❗ إزالة initialState”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”كمطور، كان الاضطرار إلى تجاوز initialState عند إنشاء bloc يمثل مشكلتين
رئيسيتين:
- يمكن أن تكون الـ
initialStateللـ bloc ديناميكية ويمكن أيضاً الرجوع إليها في وقت لاحق (حتى خارج الـ bloc نفسه). بطريقة ما، يمكن اعتبار ذلك تسريباً لمعلومات الـ bloc الداخلية إلى طبقة واجهة المستخدم. - إنها طريقة مطولة (verbose).
v4.x.x
class CounterBloc extends Bloc<CounterEvent, int> { @override int get initialState => 0;
...}v5.0.0
class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0);
...}?> لمزيد من المعلومات، راجع #1304
❗ إعادة تسمية BlocDelegate إلى BlocObserver
Section titled “❗ إعادة تسمية BlocDelegate إلى BlocObserver”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”لم يكن اسم BlocDelegate وصفاً دقيقاً للدور الذي تلعبه الفئة. يوحي اسم
BlocDelegate بأن الفئة تلعب دوراً نشطاً، بينما في الواقع كان الدور المقصود لـ
BlocDelegate هو أن يكون مكوناً سلبياً يراقب ببساطة جميع الـ blocs في التطبيق.
v4.x.x
class MyBlocDelegate extends BlocDelegate { ...}v5.0.0
class MyBlocObserver extends BlocObserver { ...}❗ إزالة BlocSupervisor
Section titled “❗ إزالة BlocSupervisor”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”كان BlocSupervisor مكوناً آخر يتعين على المطورين معرفته والتفاعل معه لغرض وحيد
هو تحديد BlocDelegate مخصص. مع التغيير إلى BlocObserver ، شعرنا أنه من
الأفضل لتجربة المطور تعيين المراقب مباشرة على الـ bloc نفسه.
?> مكننا هذا التغيير أيضاً من فصل إضافات bloc الأخرى مثل HydratedStorage عن
الـ BlocObserver.
v4.x.x
BlocSupervisor.delegate = MyBlocDelegate();v5.0.0
Bloc.observer = MyBlocObserver();package:flutter_bloc
Section titled “package:flutter_bloc”❗ إعادة تسمية condition في BlocBuilder إلى buildWhen
Section titled “❗ إعادة تسمية condition في BlocBuilder إلى buildWhen”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”عند استخدام BlocBuilder ، كان بإمكاننا سابقاً تحديد condition لتحديد ما إذا
كان يجب على الـ builder إعادة البناء.
BlocBuilder<MyBloc, MyState>( condition: (previous, current) { // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ builder }, builder: (context, state) {...})اسم condition ليس واضحاً جداً أو بديهياً، والأهم من ذلك، عند التفاعل مع
BlocConsumer ، أصبحت واجهة برمجة التطبيقات غير متسقة لأن المطورين يمكنهم تقديم
شرطين (واحد للـ builder وواحد للـ listener). ونتيجة لذلك، كشف API الـ
BlocConsumer عن buildWhen و listenWhen.
BlocConsumer<MyBloc, MyState>( listenWhen: (previous, current) { // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ listener }, listener: (context, state) {...}, buildWhen: (previous, current) { // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ builder }, builder: (context, state) {...},)من أجل مواءمة واجهة برمجة التطبيقات وتوفير تجربة مطور أكثر اتساقاً، تمت إعادة
تسمية condition إلى buildWhen.
v4.x.x
BlocBuilder<MyBloc, MyState>( condition: (previous, current) { // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ builder }, builder: (context, state) {...})v5.0.0
BlocBuilder<MyBloc, MyState>( buildWhen: (previous, current) { // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ builder }, builder: (context, state) {...})❗ إعادة تسمية condition في BlocListener إلى listenWhen
Section titled “❗ إعادة تسمية condition في BlocListener إلى listenWhen”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”لنفس الأسباب المذكورة أعلاه، تمت أيضاً إعادة تسمية شرط BlocListener.
v4.x.x
BlocListener<MyBloc, MyState>( condition: (previous, current) { // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ listener }, listener: (context, state) {...})v5.0.0
BlocListener<MyBloc, MyState>( listenWhen: (previous, current) { // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ listener }, listener: (context, state) {...})package:hydrated_bloc
Section titled “package:hydrated_bloc”❗ إعادة تسمية HydratedStorage و HydratedBlocStorage
Section titled “❗ إعادة تسمية HydratedStorage و HydratedBlocStorage”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”من أجل تحسين إعادة استخدام الكود بين
hydrated_bloc و
hydrated_cubit ، تمت إعادة تسمية
تطبيق التخزين الافتراضي الفعلي من HydratedBlocStorage إلى HydratedStorage.
بالإضافة إلى ذلك، تمت إعادة تسمية واجهة HydratedStorage من HydratedStorage
إلى Storage.
v4.0.0
class MyHydratedStorage implements HydratedStorage { ...}v5.0.0
class MyHydratedStorage implements Storage { ...}❗ فصل HydratedStorage عن BlocDelegate
Section titled “❗ فصل HydratedStorage عن BlocDelegate”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”كما ذكرنا سابقاً، تمت إعادة تسمية BlocDelegate إلى BlocObserver وتم تعيينه
مباشرة كجزء من الـ bloc عبر:
Bloc.observer = MyBlocObserver();تم إجراء التغيير التالي من أجل:
- البقاء متسقاً مع API مراقب bloc الجديد.
- إبقاء التخزين محصوراً في
HydratedBlocفقط. - فصل الـ
BlocObserverعن الـStorage.
v4.0.0
BlocSupervisor.delegate = await HydratedBlocDelegate.build();v5.0.0
HydratedBloc.storage = await HydratedStorage.build();❗ تبسيط عملية التهيئة (Initialization)
Section titled “❗ تبسيط عملية التهيئة (Initialization)”الأسباب (Rationale)
Section titled “الأسباب (Rationale)”سابقاً، كان على المطورين استدعاء super.initialState ?? DefaultInitialState()
يدوياً من أجل إعداد مثيلات HydratedBloc الخاصة بهم. كان هذا الأمر ثقيلاً
ومطولاً وغير متوافق أيضاً مع التغييرات الجذرية في initialState في bloc.
ونتيجة لذلك، أصبحت تهيئة HydratedBloc في v5.0.0 مطابقة لتهيئة Bloc العادية.
v4.0.0
class CounterBloc extends HydratedBloc<CounterEvent, int> { @override int get initialState => super.initialState ?? 0;}v5.0.0
class CounterBloc extends HydratedBloc<CounterEvent, int> { CounterBloc() : super(0);
...}