آموزش RxJava با مثال

سطح آموزش:  #پیشرفته

مقدمه

توی آموزش قبلی مقدمات کار با RxJava رو یاد گرفتیم. چون RxJava با مدل فکری ما همخونی نداره توضیح بیشتر کمک زیادی به درک بهتر نمی‌کنه. تو این آموزش با بررسی مثال‌های مختلف یک مقداری عملی‌تر با RxJava سر و کله می‌زنیم تا کم‌کم به منطق فکری reactive عادت کنیم.

منبع

تمامی مثال‌ها رو از اینجا آوردم. برای رعایت کپی‌رایت و خلوت نگه داشتن پست، کل کدها رو اینجا نمیارم برای همین توصیه میکنم پروژه رو clone کنید همزمان روی گوشی یا امولاتور اندروید هم اجرا کنید تا کارکردش رو عملی ببینید.

ساختار کلی

عملیات Observe کردن حالت کلی زیر رو داره:

فرم کلی observe کردن

بخش اول Observable هست. کسی که رویدادها رو منتشر میکنه. رویداد میتونه یک عدد، کلیک روی یک button و یا دریافت اطلاعاتی از اینترنت باشه.

در ادامه scheduler به کمک ما میاد. در دو خط بعدی میگیم عملیات کدهای observable و observer روی چه threadـی انجام بشه. مقایسه کنید با چند نخی (Multithreading) در حالت عادی.

خط آخر هم Observer رو می‌سازیم و به Observable متصل می‌کنیم.

یک مثال ساده

به مثال زیر توجه کنید (مشاهده کد کامل) :

۱- با استفاده از دستور just دو تا String رو emit می‌کنیم.

۲- میگیم عملیات Observable روی io انجام شه. چرا؟ چون مادامی که کاری که انجام میدیم با UI کاری نداره بهتره توی background انجام شه. کاری مثل خوندن از سرور رو ناچاریم به این شکل انجام بدیم ولی برای باقی عملیات‌ها هم بهینه اینه که به اینصورت عمل کنیم.

۳- میگیم عملیات Observer روی UI-Thread انجام شه. چرا؟ توی کد اصلی ما یک textview رو هم آپدیت میکنیم برای همین ناچاریم عملیات رو روی ترد اصلی انجام بدیم. اما توی این حالت که فقط یک چیز رو log میکنیم میتونستیم از ترد دیگه‌ای هم استفاده کنیم.

۴- یک Observer میسازیم و میگیم توی OnNext و OnComplete و OnError چی کار کنه. همچنین میگیم این Observer به Observable متصل بشه. با متصل شدن هروقت Observable چیزی رو emit کنه OnNextـه observer اجرا میشه. هروقت مشکلی براش پیش بیاد OnError و هروقت هم کارش تموم بشه OnComplete اجرا میشه.

متدهای پرکاربرد RxJava 2

Map

یکی از پرکاربردترین متدها Map هست.

کاری که Map میکنه خیلی ساده‌ست. فرض کنید لیستی از اطلاعات رو اینترنت خوندید و حالا میخواید توی لیست نشون بدید. برای نمایش اطلاعات داخل لیست یک adapter داریم و باید آرایه‌ای از اطلاعات بهش بدید.

مسئله اینجاست که معمولا اطلاعاتی که از اینترنت میخونید رو داخل api مشخص میشه در حالیکه هر برنامه به فراخور ویژگی‌هاش استفاده مختلفی از این اطلاعات می‌کنه. ما هم همیشه برای adapter ها یک ساختار جدا می‌سازیم.

حالا میخوایم هر کودوم از اعضای لیستی که خوندیم رو به لیست جدید منتقل کنیم. حالت عادی این کار ساده‌ست. یک for each مینویسیم و تک‌تک اعضای دو لیست رو داخل هم میریزیم.

داخل RxJava هم میشه همین کارو کرد ولی در نهایت میخوایم نتیجه رو return کنیم. برای همین از متد Map استفاده می‌کنیم. متد Map یک ورودی داره یک خروجی. ورودی اطلاعاتی هست که Observable (یا تابعی که در مرحله قبل اجرا شده) برامون فراهم کرده و خروجی تبدیلی هست که ما روی این اطلاعات انجام میدیم.

اینجا Observable یک لیست از ApiUser رو از اینترنت میگیره. سپس داخل Map اون‌ها رو تبدیل به یک لیست از User میکنه که برای ما قابل درک‌تر هست.

به طور خلاصه Map آیتم‌های Observable رو میگیره و روشون یک تابعی رو اجرا میکنه. حالا اینجا این تابع کپی کردن لیست توی یک لیست دیگه بود.

Merge

چندتا Observable رو میگیره و emitهاشون رو یکی میکنه. در واقع چندتا Observable رو یکی می‌کنه. کاربردهای خیلی زیادی داره. یک استفاده‌ای که خودم میکنه اینه که فرض کنید ما داریم search می‌نویسیم. میخوایم چه کاربر روی دکمه search کلیک کرد چه تعدادی کلمه تایپ کرد کارِ search انجام بشه.

یک Observable می‌نویسیم که با کلیک روی button متن نوشته شده داخل Edittext رو emit کنه. یک Observable هم می‌نویسیم که با نوشتن متن داخل edittext اون رو emit کنه. حالا میخوایم برای هر دو فقط یک عملیات یعنی جستجو رو انجام بدیم برای همین با هم Merge می‌کنیم و به Observer میگیم subscribe کنه.

مثال دیگه‌ای که داخل همین لینک‌ که بالا هست اینه:

Observable.fromArray میاد و اشیاء داخل آرایه رو emit میکنه.

ما دو تا Observable داریم که هر کودوم یک سری رشته emit میکنن. با Merge کردنشون اگر اطلاعات emit شده رو چاپ کنیم می‌بینیم که رشته‌های هر دو آرایه چاپ میشه.

Interval و Timer

اگر بخوایم یک کار رو به طور دائم انجام بدیم با Interval این کارو می‌کنیم. مثلا میخوایم هر ۲ دقیقه موقعیت کاربر رو به سرور بفرستیم.

Timer هم یک مدت زمانی صبر میکنه و بعد یک چیزی رو emit میکنه. post delay رو که قبلا داشتیم با timer میتونیم پیاده کنیم.

این دو تا رو میشه با هم ترکیب هم کرد. مثلا بگیم ابتدا ۱ دقیقه صبر کن و بعد هر ۲ دقیقه اطلاعات رو به سرور بفرست.

این کار به شکل زیر انجام میشه.

Debounce

این اپراتور رو من خیلی دوست دارم. فرض کنید با یک سرعتی اطلاعات داره stream میشه. با debounce ما میگیم توی این فواصل زمانی به stream نگاه کن و اطلاعات رو بخون. یعنی به سرعتی که اطلاعات emit میشه کار نداره خودش هر از چند گاهی نگاه می‌کنه و اگر تو این مدت emit بوده باشه آخریش رو emit میکنه برای مرحله بعد.

با مثال منظورم کامل مشخص میشه. دقیقا توی همون مثال search که گفتم فرض کنید کاربر تند تند داره یک چیزی رو تایپ میکنه که جستجو کنه و ما هم گفتیم با تغییر هر کاراکتر یک بار عمل search انجام بشه. اما این کار چندان بهینه نیست. ممکنه کاربر برای تایپ یک نوشته بارها غلط املایی داشته باشه و بخواد پاک کنه و دوباره بنویسه. یا اینکه برای جستجو کردن مثلا “Coursio” ما ۷ تا درخواست به سرور ارسال میکنیم، به ازای هر حرف یکبار. اینجا با استفاده از debounce میتونیم بگیم emitهای observable رو نگاه کن و اونایی که مثلا ۵۰۰ میلی ثانیه اختلاف زمانی دارن رو فیلتر کن.

اینجوری کاربر اگر سریع (زیر ۵۰۰ میلی ثانیه) کلمه coursio رو تایپ کنه فقط یکبار درخواست به سرور ارسال میشه. اگر هم حروف رو تک‌تک و با فاصله زمانی تایپ کنه تک‌تک براشون جستجو انجام میشه. خیلی شبیه به جستجو در گوگل. شما اگه cour رو یادداشت کنید چند میلی‌ثانیه طول میکشه تا گوگل اقدام به پیشنهاد کلمه بکنه. اون چند میلی‌ثانیه با debounce قابل پیاده‌سازیه.

Disposable چیست؟

disposable یعنی چیزی که قابل dispose کردن هست ( چشم بسته غیب گفتم D: ). وقتی ما داریم از observable و observerها استفاده می‌کنیم منابعی رو در اختیار این‌ها قرار دادیم. برای همین باید در نهایت این منابع رو آزاد کنیم.

فرض کنید ما میخوایم اطلاعاتی رو از اینترنت بخونیم و به کاربر نمایش بدیم. اما اگر قبل از اینکه اطلاعات آماده بشه کاربر از برنامه یا activity خارج شد چی؟ یا اگه گوشی زنگ خورد یا صفحه قفل شد. در صورتی که اتصال بین observer و observable رو قطع نکنیم با آماده شدن اطلاعات observer میخواد اونا رو نمایش بده ولی چون جایی برای نمایش نیست برنامه crash میکنه. برای همین ما با استفاده از disposable ارتباط رو قطع و منابع رو آزاد ‌می‌کنیم.

Composite Disposable چیست؟

CompositeDisposable به ما این امکان رو میده که چندین Observable رو داخل یک CompositeDisposable قرار بدیم و تمام منابع رو بصورت یکجا آزاد کنیم. شاید توی یک اکتیویتی ۱۰ تا Observable داشته باشیم، برای اینکه ناچار نباشیم تک‌تک عمل dispose رو انجام بدیم از یک CompositeDisposable استفاده می‌کنیم.

یک مثال خوب برای CompositeDisposable به نقل از این پاسخ stack overflow:

همونطوری که می‌بینید استفاده ازش خیلی ساده‌ست، Observable ها رو به disposables موقع ساخت add می‌کنیم و بعد عمل disposables.clear رو داخل OnDestroy انجام میدیم.

جمع‌بندی

تعداد متدهایی که برای Observable میشه استفاده کرد خیلی زیاده و قطعا نمیشه به همشون پرداخت به علاوه که وقتی یاد بگیرید منطق کلی کار چجوریه چک کردن بقیه وقت و انرژی زیادی ازتون نمی‌گیره. برای آشنایی بیشتر با اپراتورهای پرکاربر مثال‌های اینجا رو نگاه کنید.

document خود rx هم به نظرم خیلی خوبه و تمامی متدها رو توضیح داده و با تصویر کارکردشون رو نشون داده. از این لینک می‌تونید اون ها رو هم نگاه کنید و بسته به نیازتون ازشون استفاده کنید.

برای تمرین سعی کنید چند تا از asynctask هایی که قبلا نوشتید رو با RxJava پیاده‌سازی کنید. همچنین باید بتونید بدون استفاده از handler و runnable و.. تایمر بنویسید و تاخیر ایجاد کنید.

 

9 دیدگاه برای “آموزش RxJava با مثال

  1. در مورد کلیک و هندل کردن اون و اینکه بخواییم این اطلاعاتی که در اختیار ما گرفت توسط subscribe چطوری بفرستیم یا پست کنیم؟ مثلا eventBus رو در نظر بگیرید. اگه subscribe رو جای دیگه مثلا داخل اکتیویتی ست کردیم و observable رو داخل فرگمنت توی همون اکتیویتی با اتصال این دو به همدیگه میتونیم عمل پست رو انجام بدیم؟

    1. در مورد کلیک هم میشه onNext یک observable رو داخل onClickListener یک view تعریف کرد و هم از کتابخونه RxBinding استفاده کرد که بعدا که Mvp رو بگم راجع بهش توضیحاتی میدم.
      اما برای روش اول فرض کنیم میخوایم از edittext یه متنی رو بخونیم و اون رو پست کنیم. چیزی شبیه به تکه کد زیر برای Observable خواهیم داشت:

      Observable.create(new ObservableOnSubscribe()
      public void subscribe(final ObservableEmitter emitter)
      button.setOnClickListener(new View.OnClickListener()
      public void onClick(View view)
      emitter.onNext(editText.getText().toString());

      بعد هم میتونیم هرجایی دوست داریم اطلاعات رو از سرور بخونیم. مثلا یک map استفاده کنیم که string رو به سرور میفرسته و اطلاعات رو به ما برمیگردونه.
      در مورد eventBus متوجه نشدم منظورتون چیه؟
      در مورد activity و fragment هم شما هرجایی اطلاعات رو میخواید داخل همون lifecycle باید درخواستش رو بدید که با از بین رفتن fragment/activity منابع رو هم آزاد کنید. شدن که میشه ولی یکی از مهمترین فسلفه‌های استفاده از RxJava اینه که خیلی ساده بتونیم منابع رو آزاد کنیم. ضمن اینکه تداخل لایف سایکل‌ها برای استفاده از dagger هم مشکل ایجاد میکنه.

          1. خوب ببینید همونطوری که میدونید. تو مدل MVVM ما دیگه با کستینگ کاری نداریم و فقط تو لایوت با استفاده از پرزنتور کلیک ها رو مدیریت میکنیم و متدی رو از داخل لایه ویو رو صدا میزنیم. دیگه هیچ کاری تو این فرآیند انجام نمیدیم فقط تعریف میکنیم. تو RxBinding اون طوری که من متوجه شدم برای ارسال رویداد و event به کار میره که ما بگیم کلیدی فشار داده شد مثلا. ولی تو mvvm دیگه نیازی به ارسال و پست کردن این اتفاق نیست خود mvvm اون رو مدیریت میکنه

          2. حرف شما درسته اما لزوما تمام view ها داخل layout تعریف نمیشن برای همین در mvvm هم مواردی ما ناچاریم در جایی غیر از layout کلیک رو مدیریت کنیم. البته ناچار هم نیستیم از RxBinding استفاده کنیم راه‌های دیگه هم هست ولی اگه بخوایم میتونیم استفاده کنیم و مشکل ناهماهنگی نداریم.

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *