Masoud-Dev
Masoud-Dev
خواندن ۹ دقیقه·۲ سال پیش

RxDart, BloC Pattern, Retrofit, JsonSerializable

سلام دوستان

پکیج‌هایی که قراره ازشون استفاده کنیم:

پکیج‌های مورد استفاده در این آموزش
پکیج‌های مورد استفاده در این آموزش


یه توضیح مختصر بریم روی RxDart که اصلا این بنده خدا قراره برامون چیکار کنه!
ما از این پکیج استفاده میکنیم تا بتونیم دیتاهامون رو بصورت Stream فلاکس (جاری) کنیم داخل UI خودمون، به این صورت که با استفاده از پیروی از الگوی BloC یک کلاس می‌سازیم مثلا PostsBloc و هر جا داخل برناممون به اطلاعات Posts‌ احتیاج داشتیم کافیه یه شی از PostsBloc ایجاد و از متدی که بصورت استریم داخلش تعریف کردیم استفاده کنیم.
به همین سادگی شما می‌تونید اطلاعات خودتون رو همه جای برنامه داشته باشید و در عین حال در استفاده از منابع سخت افزاری صرفه‌جویی کنید.

ساختار سینک و استریم
ساختار سینک و استریم

ببینید از سمت چپ تصویر توضیح میدم قسمتی که Sinks داره، همونطور که می‌بینید اطلاعات ما از سمت کاربرها میاد یا سمت سرور و باید طبق این اطلاعات که برای ما ارسال میشه یسری عملیات رو انجام بدیم و در نهایت با پیغام مناسب جواب این رویدادها رو بدیم.
همونطور که اول مقاله گفتم پایه‌ی این پکیج استریم‌ها هستند که ما اطلاعات رو بصورت یک جریان به سمت UI می‌فرستیم برای اینکه بتونیم این اطلاعات رو روی استریم داشته باشیم باید اون رو Sink‌ کنیم (همون Input در نظر بگیرید) بعد اینجا BloC وارد عمل میشه و رویداد رو دریافت میکنه یسری کارها روش انجام میده و اونو میده به PublishSubject, BehaviorSubject, ReplaySubject (آبجکتهای مربوط به پکیج rxdart) که مسئول استریم یا سینک کردن اون به UI‌ هستند.

* این مقاله به مرور زمان تکمیل‌تر میشه یا خیلی عجله دارید می‌تونید گوگل کنید :)


بریم سروقت کدها که خیلی واضح‌تر منظور رو می‌رسونن!
- اجازه بدید به فایل pubspec.yaml پکیج‌ rxdart رو اضافه کنیم:

اضافه کردن پکیج rxdart
اضافه کردن پکیج rxdart



میریم سراغ آبجکت‌های کاربردی که این پکیج در اختیار ما قرار میده:

PublishSubject<T> class

با مثال گروه تلگرامی میریم جلو، فرض کنید وارد یه گروه تلگرامی شدید هر پیامی که قبل شما بوده به شما نشون نمیده و از این به بعد هر پیام، فایل و ... (دیتا و رویداد) که کسی بفرسته شما بهش دسترسی دارید.
- همه‌ی خصوصیات broadcast که در StreamController هست رو داره با یک استثنا:
اینکه هم میتونه Sink کنه هم Stream
- این کلاس rxdart به ما اجازه میده تا دیتاها، ارورها و رویدادهایی که انجام شده رو به listener‌ بفرستیم.

به عنوان مثال:

final subject = PublishSubject<int>(); /// // از این به بعد اگه دیتا یا رویدادی اتفاق بیافته میتونه بشنوه پس الان ۱، ۲، ۳ رو میشنوه با هر چی بعدش بیاد subject.stream.listen(observer1); subject.add(1); subject.add(2); /// // از این به بعد اگه دیتا یا رویدادی اتفاق بیافته میتونه بشنوه پس الان فقط ۳ رو میشنوه با هر چی بعدش بیاد subject.stream.listen(observer2); subject.add(3); subject.close();


BehaviorSubject<T> class

فرض کنید وارد گروه تلگرامی شدیم و ما که تازه واردیم آخرین پیام که داخل گروه بوده (قبل از ما) رو بهمون نشون میده، یا اینکه با استفاده از seeded یه مقدار اولیه (مثلا پیام خوش آمد گویی) رو به ما نشون میده.
این کلاس از rxdart آخرین آیتمی که add میشه رو به همه‌ی listenerها که بعدا میان سابسکرایب می‌کنند میفرسته! و قابلیت seed هم داره یعنی می‌تونیم یه مقدار اولیه بهش بدیم که به دوستان دنبال کننده بفرسته.

final subject = BehaviorSubject<int>(); /// subject.add(1); subject.add(2); subject.add(3); /*دقت کنید آیتم‌ها اضافه شدند و سابسکرایبرها با اینحال میتونند به آخرین آیتم دسترسی داشته باشند*/ subject.stream.listen(print); // prints 3 subject.stream.listen(print); // prints 3 subject.stream.listen(print); // prints 3 /// ### مثال همراه با استفاده از seed final subject = BehaviorSubject<int>.seeded(1); /*این هم مثال سید دار! که یجورایی تغذیه کننده یا مقدار شروع در نظر بگیرید می‌بینیم که سابسکرایبرها میتونند به عدد سید دسترسی داشته باشند*/ subject.stream.listen(print); // prints 1 subject.stream.listen(print); // prints 1 subject.stream.listen(print); // prints 1


ReplaySubject<T> class

خب بریم سراغ بعدی که چیز جالبیه مثلا وقتی بخوایم وارد یه گروه تلگرامی بشیم و بتونیم پیام‌های قبلی رو هم بخونیم که ممبرهای گروه قبلا فرستادن! این میشه کار ReplaySubject!

final subject = ReplaySubject<int>(); subject.add(1); subject.add(2); subject.add(3); /*همه‌ی تازه واردها می‌تونند دیتاهایی که قبلا اضافه شده رو شنود کنند*/ subject.stream.listen(print); // prints 1, 2, 3 subject.stream.listen(print); // prints 1, 2, 3 subject.stream.listen(print); // prints 1, 2, 3 /// ### مثال همراه با maxSize final subject = ReplaySubject<int>(maxSize: 2); subject.add(1); subject.add(2); subject.add(3); /*همه‌ی تازه واردها می‌تونند دیتاهایی که قبلا اضافه شده رو شنود کنند با این تفاوت که بخاطر مکس سایزی که اضافه کردیم فقط به همون اندازه از پیامهای آخر رو میتونند شنود کنند*/ subject.stream.listen(print); // prints 2, 3 subject.stream.listen(print); // prints 2, 3 subject.stream.listen(print); // prints 2, 3


یک مثال ساده:

اینارو گفتیم ولی هیچچی مثل کد و یه مثال ساده مطلب رو جا نمیندازه!

ما قراره داخل این پروژه‌ی ساده یه Json که از سایت https://jsonplaceholder.typicode.com/posts میگیریم رو با استفاده از پکیج json_serializable و json_annotation پارسش کنیم و با استفاده از پکیج retrofit و dio (رتروفیت از دایو استفاده کرده) که وظیفه‌ی هندل کردن سرویسهای مربوط به شبکه و ارتباط با سرور رو داره، درخواستش رو میفرستیم به سرور (jsonplaceholder.com) و بعد با استفاده از کلاس PublishSubject پکیج rxdart اون رو استریم میکنیم روی UI‌ و کاربر اونو می‌بینه (خدایا این همه کار کنیم که کاربر یه لیستی از پست‌ها ببینه خخخخ خدایی نمیصرفه)

ساختار پروژه‌ی ما به این صورته:

ساختار پروژه
ساختار پروژه

قراره از چه پکیج‌هایی استفاده کنیم:

پکیج‌هایی فایل پاب اسپک
پکیج‌هایی فایل پاب اسپک


اول ببینید json که از سمت سرور میاد ساختارش چه شکلیه:

[ { &quotuserId&quot: 1, &quotid&quot: 1, &quottitle&quot: &quotsunt aut facere repellat provident occaecati excepturi optio reprehenderit&quot, &quotbody&quot: &quotquia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto&quot }, { &quotuserId&quot: 1, &quotid&quot: 2, &quottitle&quot: &quotqui est esse&quot, &quotbody&quot: &quotest rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla&quot }, ]

خب قراره یه لیستی از جی سان به ما برگردونه، نحوه‌ی پارس کردن دستی و اتومات رو بهتره برید از این لینک یاد بگیرید که مهندس علی حسین‌پور زحمتش رو کشیدن:

https://vrgl.ir/HBYiP

خب از اونجا که لیستی از پست‌ها برمیگردونه یک کلاس PostsModel میسازیم (میتونید به دو بخش PostsModel‌ و PostsData تقسیمش کنید که من برای پیچیده نشدن یکی رو ایجاد میکنم و از همون استفاده میکنم ولی کلین کد میگه باید جدا بشند)

فولدرهای models, posts و services رو داخل پوشه lib بسازید

داخل پوشه models یک فایل دارت با نام posts_model ایجاد کنید و کدهای زیر رو داخلش بزنید.
نکته: میتونید به این سایت https://app.quicktype.io یه جی سان بدید و بهتون کلاس رو تحویل بده!

class PostsModel
class PostsModel

توضیح کدهای بالا:
- خط ۱: این پکیج برای ساخت فایل g (خط سوم) و حاشیه نویسی (JsonSerializable) (خط پنجم) لازمه.
-خط ۳: اسمی که به part میدیم باید هم اسم فایلی که داخلش داریم پارت رو می‌نویسیم باشه فقط پسوند فایل رو .g.dart میزاریم، کدهای مربوط به پارس داخل این فایل ایجاد میشه)
- خط ۵: کامپایلر وقتی به این خط میرسه میفهمه که قراره یه پارس کردنی رو انجام بدم (از اونجا که ما فقط احتیاج داریم اطلاعات رو بخونیم نیازی به ToJson‌ نداریم پس یه پارامتر به JsonSerializable میدیم که اونو برامون نسازه)
- خط ۱۹: اینجا جایی که اطلاعات لازم برای پارس کردن رو میدیم به _$PostsModelFromJson(json)

-----> در نهایت با اجرا کردن دستور زیر داخل ترمینال در شاخه‌ی اصلی پروژه فایل g ساخته میشه:

flutter pub run build_runner build
فایل posts_model.g.dart
فایل posts_model.g.dart


بریم سراغ ارتباط با سرور و api:

سرویسهای مربوط به شبکه و سرور
سرویسهای مربوط به شبکه و سرور


اول بیایید آدرسی که قراره ازش اطلاعات مربوط به پست رو بگیریم به دو بخش تقسیم کنیم:
https://jsonplaceholder.typicode.com/posts
بخش اول: https://jsonplaceholder.typicode.com
بخش دوم: /posts
بخش اول میشه baseUrl ما و بخش دوم میشه endpoint‌ ما
بریم که فایلهای مربوطه رو ایجاد کنیم.
اول endpoints.dart:

فایل endpoints.dart
فایل endpoints.dart


ما فقط از EndPoints.posts قراره استفاده کنیم.

خب الان بریم سراغ فایل network_handler.dart خودمون:

فایل network_handler.dart
فایل network_handler.dart


خط ۱: پکیج dio پیش نیاز پکیج retrofit‌ هست.
خط ۲: Endpointهایی که ساختیم رو قراره به baseUrl رتروفیت اضافه کنیم.
خط ۳: پکیج retrofit که برای @RestApi و ایجاد محتویات فایل g لازمه.
خط ۴: مدلی که با اطلاعات json ساختیم رو اینجا فراخوانی میکنیم.
خط ۶: هم اسم فایل باید فایل g رو هم بسازیم
خط ۸: @RestApi با baseUrl سرور که برای محتویات فایل g استفاده میشه
خط ۱۰: فکتوری مربوط به کلاس ApiClient که وظیفه‌ی ارتباط با سرور رو داره.
خط ۱۲: @GET با پارامتری از کلاس EndPoints که با متد get از پکیج رتروفیت اطلاعات رو واکشی میکنه.
خط ۱۳: یه متدی میسازیم و یه اسم بهش میدیم مثلا getPosts که از نوع Future هستش و لیستی از کلاس PostsModel

-----> در نهایت با اجرا کردن دستور زیر داخل ترمینال در شاخه‌ی اصلی پروژه فایل g ساخته میشه:

flutter pub run build_runner build
فایل network_handler.g.dart
فایل network_handler.g.dart


بریم سراغ فایل repository.dart

فایل repository.dart در پوشه مربوطه
فایل repository.dart در پوشه مربوطه


محتویات فایل repository.dart
محتویات فایل repository.dart


این فایل وظیفه‌ی ارتباط با فایل post_bloc.dart رو داره، کدها نیازی به توضیح نداره چیز جدیدی داخلش نیست. ریپوزیتوری میشه منبع، ینی جایی که ما ازش اطلاعاتمون رو واکشی میکنیم، پس هر اطلاعاتی که قراره بیاد از روی این کلاس میخونیم و استریمش میکنیم روی UI.

خب رسیدیم به بخش جذاب RxDart. فایلش رو داخل پوشه‌ی مربوطه ایجاد کنید و محتویات زیر رو داخلش بزارید:

محتویات فایل post_bloc.dart
محتویات فایل post_bloc.dart


خط ۱: همونطور که گفتم باید ریپوزیتوری رو بدیم تا برامون استریم کنه.
خط ۲: پکیج rxdart که معرف حضورتون هست.
خط ۳: وقتی میخوایم نوع داده‌ها رو مشخص کنیم باید بگیم از نوع کلاس PostsModel هست.
نکته: قبل اینکه بخوام بقیه کد رو توضیح بدم یه شمای کلی بهتون بدم که قراره چیکار بشه! ببینید اطلاعات از ریپو میاد داخل ما قراره هر چیزی که به ریپو اضافه میشه رو sink کنیم بعد بدیمش به متدی که برامون استریمش کنه روی UI، خب بریم ادامه توضیحات کد.
خط ۶: از ریپو یه شی درست میکنیم.
خط ۷: از پکیج rxdart یه شی میسازیم از کلاس PublishSubject که هم قراره برامون عمل سینک رو به گردن بگیره هم استریم.
خط ۹: متد getter که باید از نوع Stream‌ باشه قبلا Observable بود که الان جاش استریم اومده و کارشو انجام میده. این متد قراره UI ما رو تغذیه کنه پس باید .stream هم بهش اضافه کنیم.
خط ۱۱: متدی مینویسیم برای هر موقع صداش بزنیم شروع میکنه به گرفتن اطلاعات از ریپو و sink‌ کردن (input) اون روی شی که از PublishSubject ساختیم.
خط ۱۶: حتما StreamController‌ خودتون رو close‌ کنید که اینجا الان PublishSubject این کار رو به عهده گرفته پس تو متد disopose کارش رو تموم میکنیم که state leak نداشته باشیم.


خب بالاخره بریم سراغ UI خودمون، فایل posts_list.dart رو تو پوشه‌ی مربوطه ایجاد کنید و محتویات زیر رو داخلش قرار بدید:

فایل posts_list.dart
فایل posts_list.dart


نکته‌ی خاصی نداره فقط محض اطلاع دوستان بگم:
- یه متد داخل PostBloc نوشتیم که اسمش fetchAllPosts بود اونو صدا بزنید تا آخرین اطلاعات رو از سرور بگیره.
- snapshot رو اجازه بدید که نال هم باشه وگرنه ارور میگیرید (خط ۱۶)
- نال بودن snapshot.data رو چک کنید (خط ۲۶)


UI
UI


نکته‌ی خیلی جذاب اینه که ما با یه StatelessWidget تونستیم اطلاعات رو بصورت داینامیک و استریم روی UI خودمون داشته باشیم کاری که StatefulWidget با کلی صرف منابع انجام میداد!

امیدوارم که استفاده‌ی لازمو از این آموزش برده باشید.

دوستدار شما مسعود سعیدی


retrofit
شاید از این پست‌ها خوشتان بیاید