نحوه استفاده از RxDart به همراه الگوی بلاک در فلاتر
درباره برنامه نویسی Reactive احتمالا تا به حال زیاد شنیده اید.
یکی از روش های نوشتن برنامه های Reactive استفاده از کتابخانه های خانواده ReactiveX می باشد که برای فلاتر نسخه RxDart استفاده می شود.
خود زبان دارت چند پکیج مختلف برای کار کردن با استریم ها دارد که میتونیم اول این پکیج ها و بررسی کنیم.
استریم چیست
استریم ها مجموعه شناوری از دیتا و رویداد ها هستند که میتونید به تغییرات آنها گوش دهید.
برای مثال اگر با ویجت StreamBuilder در فلاتر کار کنید میبینید که این ویجت با متصل شدن به یک استریم خودش و با آخرین تغییرات دیتا و رویداد بروز رسانی میکنید و دوباره از اول میسازد.
درباره الگوی بلاک هم قبلا صحبت کردیم که میتونید مطالعه کنید.
کتابخانه RxDart
در حال حاضر پکیح RxDart نسخه ۰٫۲۳ منتشر شده و بر اساس همین نسخه هم کار میکنیم.
قبل از شروع درباره یک سری از ویژگی ها آن خوبه که صحبت کنیم.
Observable class
Observable یا قابلیت مشاهده شدن به ما این امکان و میده تا یک نوتیفیکیشن به ویجتی که در حال مشاده کردن یا observing هست ارسال کنیم تا از تغییرات با خبر شود و کاری که نیاز هست را انجام دهد.
کلاس Observable هم از کلاس استریم ارث بری میکند که امکانات زیر و در اختیارمون میذاره:
- تمام متد های تعریف شده در کلاس استریم در Observable هم قابل دسترس است.
- همه Observable ها به هر متد و API ایی که به عنوان ورودی یک استریم را میپذیرد میتوان پاس داد.
PublishSubject class
این موضوع خیلی سادست و Subject در واقع اجازه میده تا رویداد هایی مثل خطا, اطلاعات و پایان کار را به listener ارسال کنیم.
PublishSubject<int> subject = new PublishSubject<int>();
/*this listener below will print every integer added to the subject: 1, 2, 3, ...*/
subject.stream.listen(print);
subject.add(1);
subject.add(2);
/*but this listener below will print only the integer added after his initialization: 3, .../*
subject.stream.listen(print);
subject.add(3);
BehaviorSubject class
این ویژگی ها بسیار شبیه به قبلی هست و به ما اجازه میده تا رویداد هایی مثل خطا, اطلاعات و پایان کار را به listener ارسال کنیم اما آخرین آیتمی که در Subject اضافه شده است به تمام listeners ها ارسال می شود.
BehaviorSubject<int> subject = new BehaviorSubject<int>();
subject.stream.listen(print); // prints 1,2,3
subject.add(1);
subject.add(2);
subject.add(3);
subject.stream.listen(print); // prints 3
ReplaySubject class
ReplaySubject هم دقیقا کار نمونه های قبلی و انجام میده اما به یک روش دیگر.
زمانی که آیتم ها درون Subject اضافه می شوند ابتدا ذخیره می شوند و سپس هر زمان که یک استریم شروع به گوش دادن کرد آیتم ها به listener ارسال می شوند.
ReplaySubject<int> subject = new ReplaySubject<int>();
subject.add(1);
subject.add(2);
subject.add(3);
subject.stream.listen(print); // prints 1, 2, 3
برای شروع کار و تمرین کردن RxDart به طورعملی بهترین کار استفاده از پروژه پیش فرض خود Flutter می باشد.
به همین خاطر یک پروژه جدید بسازید و پکیج RxDart را هم به عنوان اضافه کنید.
در این پروژه که خودش تابع اضافه کردن مقدار به یک عدد و از اول دارد ما میخوایم که عمل کم کردن از عدد را پیاده سازی کنیم.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void _decrementCounter() {
setState(() {
_counter--;
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text('You have pushed the button this many times:',),
new Text('$_counter', style: Theme.of(context).textTheme.display1),
],
),
),
floatingActionButton: new Column(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[
new Padding(padding: EdgeInsets.only(bottom: 10), child:
new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
)
),
new FloatingActionButton(
onPressed: _decrementCounter,
tooltip: 'Decrement',
child: new Icon(Icons.remove),
),
])
);
}
}
همونجور که کد بالا و میبینید دو بخش منطقی در برنامه داریم که متد های اضافه و کم کردن عدد هستند.
اما هنوز الگوی Bloc و پیاده سازی نکردیم ولی کد به شکل کامل و درست کار میکنه.
حالا فرض کنید خواستیم تغییراتی در کد اعمال کنیم مثلا به جای کم و زیاد کردن به اندازه یک واحد بیایم و دو واحد تغییرات اعمال کنیم.
خب این کار ساده هست اما اگر پروژه واقعی بود و تغییراتی که میخواستیم زیاد بود خیلی سخت میشد.
نکته بعدی اینکه اعمال تغییرات در این بخش با طراحی رابط کاربری کاری ندارد پس بهتره که بخش منطقی برنامه و از رابط کاربری جدا کنیم.
import 'package:rxdart/rxdart.dart';
class CounterBloc {
int initialCount = 0; //if the data is not passed by paramether it initializes with 0
BehaviorSubject<int> _subjectCounter;
CounterBloc({this.initialCount}){
_subjectCounter = new BehaviorSubject<int>.seeded(this.initialCount); //initializes the subject with element already
}
Observable<int> get counterObservable => _subjectCounter.stream;
void increment(){
initialCount++;
_subjectCounter.sink.add(initialCount);
}
void decrement(){
initialCount--;
_subjectCounter.sink.add(initialCount);
}
void dispose(){
_subjectCounter.close();
}
}
در توضیحات کد بالا, ابتدا ما یک کلاس به نام CounterBloc ساختیم که کتابخانه rxdart را ایمپورت کرده است.
در این لحظه نیاز داریم تا مقدار اولیه برای شروع شمارش و به دست بیاریم که بهش initialCount میگیم.
برای پیاده سازی این مثال من از BehaviorSubeject استفاده میکنم و زمانی که ویجت به یک گوش دهنده Subject تبدیل شد اولین مقدار و از طریق استریم ارسال میکنم تا initialCount مقداردهی شود در متد سازنده کلاس.
ما یک متد به اسم counterObeservable نیز داریم.
این متد یک Observable از نوع Subject برمیگرداند.
در واقع این آبجکتی است که در زمان تغییر اطلاعات به ویجت ارسال می شود.
import 'package:flutter/material.dart';
import 'package:bloc_example/bloc/CounterBloc.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
CounterBloc _counterBloc = new CounterBloc(initialCount: 0);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text('You have pushed the button this many times:'),
new StreamBuilder(stream: _counterBloc.counterObservable, builder: (context, AsyncSnapshot<int> snapshot){
return new Text('${snapshot.data}', style: Theme.of(context).textTheme.display1);
})
],
),
),
floatingActionButton: new Column(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[
new Padding(padding: EdgeInsets.only(bottom: 10), child:
new FloatingActionButton(
onPressed: _counterBloc.increment,
tooltip: 'Increment',
child: new Icon(Icons.add),
)
),
new FloatingActionButton(
onPressed: _counterBloc.decrement,
tooltip: 'Decrement',
child: new Icon(Icons.remove),
),
])
);
}
@override
void dispose() {
_counterBloc.dispose();
super.dispose();
}
}
مقدار initialCount و برابر صفر قرار دادیم.
متد های increment و decrement رو هم پاک کردیم.
زمانی که هر دوتا FloatingActionButton کلیک شوند متد correspondent صدا زده می شود.
از یک StreamBuilder هم برای نمایش ویجت ها استفاده میکنیم.
دیدگاهتان را بنویسید