برنامه نویسی reactive در اندروید

راهنمای کامل برنامه نویسی Reactive در اندروید

اگر روی پروژه های اندرویدی که تعداد زیادی منابع و داده دارند کار کرده باشید، حتما با سختی های مدیریت داده و نشان دادن تغییرات روی رابط کاربری آشنا هستید. گاهی اوقات نیاز داریم تعدادی از Task ها در بک گراند اپلیکیشن انجام بشوند تا تاثیری روی رابط کاربری نداشته باشند. برای این کار میتوانیم از تسک های غیرهمزمان استفاده کنیم. برنامه نویسی Reactive یکی از راه های پیاده سازی تسک های Async است که مدیریت داده ها و اجزای مختلف را بسیار راحت تر میکند.

reactive programming

با اینکه پارادایم برنامه نویسی Reactive عمر زیادی ندارد، اما در همین مدت توجه خیلی زیادی را به خودش جذب کرده است. کتابخانه هایی که مناسب برنامه نویسی Reactive هستند برای زبان های برنامه نویسی مختلف تولید شده اند. تعدادی از این کتابخانه های مشهور RxJava، RxKotlin، RxSwift، RxJs هستند. در این مقاله از بلاگ برنامه چی کتابخانه های مناسب برای برنامه نویسی Reactive اندروید را بررسی میکنیم.

بصورت خلاصه باید بگویم که برنامه نویسی Reactive یعنی استفاده از جریان های داده ای در برنامه نویسی و واکنش به تغییرات این مدل داده ها. در این مقاله درباره تعریف برنامه نویسی Reactive، معرفی RxJava و اجزای آن و همچنین مثال های کاربردی صحبت کردیم.


تسک های Async و دردسر هایش

پلتفرم اندروید ذاتا همه کارها را غیر همزمان انجام میدهد. همه کارهای اپلیکیشن ها نیز همینطور انجام میشود. یعنی اطلاعات بصورت غیرهمزمان در اپلیکیشن شما جریان دارد، اطلاعاتی که از چند منبع مختلف می آیند. سیستم BroadCast Reciever های شما را اجرا میکند، Intent ها را ارسال میکند و با هر بار تغییر Config، رابط کاربری را دوباره از اول میسازد. کاربر به تعامل با رابط کاربری ادامه میدهد و در هر لحظه ممکن است برای درخواست های شبکه، پاسخی دریافت شود.

تسک های غیر همزمان در برنامه نویسی reactive

اما نوشتن کدهای غیر همزمان معمولا مشکلات زیادی درست میکنند. تعدادی از این مشکلات را در ادامه مشاهده میکنیم:

  • ارور های پیچیده در پردازش: اگر در چند پردازش غیر همزمان ارور هایی وجود داشته باشد، پیدا کردن اینکه کدام تسک باعث به وجود آمدن ارور شده، میتواند کار بسیار سختی باشد.
  • Callback های تو در تو: وقتی که یک اپلیکیشن پیچیده میسازیم که شامل چندین درخواست اینترنتی، تعامل با کاربر و انیمیشن است، احتمالا تعداد زیادی Callback تو در تو خواهیم داشت. وجود Callback های تو در تو میتواند پیچیدگی پروژه را بسیار زیاد کند. مثلا فرض کنید یک Calback داخل یک تسک غیرهمزمان وجود دارد. این Callback  میتواند یک تسک غیر همزمان دیگر که آن هم دارای یک Callback هست را استارت کند، و این ماجرا ادامه خواهد داشت.
  • احتمال بالا برای دریافت ارور های غیر قابل ردگیری.

برنامه نویسی Reactive میتواند روند نوشتن کدهای غیر همزمان را بسیار آسان تر کند.


برنامه نویسی Reactive اندروید چیست؟

برنامه نویسی Reactive در حقیقت یک مدل برنامه نویسی غیر همزمان بر مبنای Event هاست. یعنی هرچیزی که در این مدل میبینید، یک جریان از داده هاست که بصورت غیر همزمان کار میکند. در حقیقت برنامه نویسی Reactive یک پارادایم است که در آن همه تغییرات داده ها از طریق جریان های داده ای منتشر میشوند. این جریان ها میتوانند Observe بشوند و زمانی که یک مقدار را ارسال میکنند، یک عمل در اپلیکیشن ما انجام خواهد شد.

reactive programming

با مفهوم Observable ها و Observe کردن، جلوتر آشنا میشویم. اما فعلا در نظر بگیرید جریان های داده، میتوانند توسط بعضی از قسمت های اپلیکیشن Observe یا به اصطلاح مشاهده بشوند. هر بار مقدار جدیدی در این جریان های داده ای قرار میگیرد، آن قسمت از اپلیکیشن که در حال مشاهده کردن است با خبر شده و میتواند یک عمل را انجام بدهد.

شما میتوانید از هرچیزی که دوست دارید یک جریان داده ای بسازید: تغییرات متغیر ها، Event های کلیک کردن، درخواست های http، ذخیره سازی داده، ارور ها و هر چیز دیگری که در اپلیکیشن وجود دارد. وقتی که میگوییم این جریان های داده ای بصورت غیر همزمان اجرا میشوند، یعنی هر ماژول کد روی یک رشته (Thread) جداگانه قرار میگیرد. به همین دلیل میتوانیم چند ماژول را بصورت همزمان اجرا کنیم.

یکی از مزیت های برنامه نویسی Reactive این است که هر تسک روی Thread مخصوص خودش اجرا میشود. به همین دلیل میتوانیم همه تسک ها را همزمان با هم شروع کنیم و زمانی که طول میکشد همه کارها انجام بشوند، دقیقا برابر با زمان کامل شدن طولانی ترین تسک موجود در بین آن ها است. اگر در اپلیکیشن های موبایل، تسک های وقت گیر را روی Thread های پس زمینه اجرا کنیم، رابط کاربری قفل نمیشود و تجربه کاربری بسیاری بهتری برای کاربران میسازیم.

به عنوان مثال، فرض کنید میخواهیم از این عبارت x=y+z استفاده کنیم. یعنی حاصل جمع y و z در متغیر x ریخته میشود. در برنامه نویسی Reactive هر وقت که مقادیر y یا z تغییر کنند، مقدار x هم بصورت اتوماتیک به روز رسانی میشود. بدون اینکه نیاز به اجرای دوباره کد ها داشته باشیم. برای این کار باید مقادیر y یا z را Observe کنیم.

وقتی که میخواهید اپلیکیشن خودتان را با استفاده از برنامه نویسی Reactive بسازید، روش معماری نرم افزار و نوشتن کدهای شما کاملا تغییر میکنند. همچنین اگر معماری هایی مانند MVP، MVVM و غیره را به کار ببریم، این روش بسیار قدرتمند تر میشود.

میشود گفت برنامه نویسی Reactive، یک روش برنامه نویسی است که روی جریان های داده تمرکز دارد. ایده اصلی این مدل توسعه نرم افزار، ارائه ایونت ها و داده ها در قالب جریان های مختلف است.


معرفی Reactive Extensions

Reactive Extensions که به آن ReactiveX یا بصورت خلاصه RX گفته میشود، یک کتابخانه است. این کتابخانه از قواعد برنامه نویسی Reactive پیروی میکند. مثلا میتواند برنامه های غیر همزمان و Event Base را با استفاده از Observable ها بسازد. این کتابخانه ها تعدادی متد و اینترفیس ارائه میدهند که به کدنویس ها کمک میکنند کدهای بهتری تولید بکنند.

reactive extensions

کتابخانه های Reactive Extension برای بسیاری از زبان ها موجود هستند. زبان هایی مانند سی پلاس پلاس (RxCpp)، سی شارپ (Rx.NET)، جاوا (RxJava)، کاتلین (RxKotlin)، سوییفت (RxSwift) و بسیاری از زبان های دیگر. به دلیل اینکه برنامه نویسی اندروید حوزه تخصصی ماست، ما فقط روی RxJava و RxAndroid تمرکز میکنیم.


RxJava چیست؟

RxJava کتابخانه مناسب برنامه نویسی Reactive مخصوص زبان جاوا است. این کتابخانه با استفاده از الگوی Observer میتواند Event های غیر همزمان بسازد. یعنی شما میتوانید جریان های داده ای غیر همزمان درست کنید و هر Observer دیگری میتواند روی هر Thread دیگر، این داده ها را مصرف کند. این لایبرری شامل تعداد زیادی عملگر مانند map، combine، merge، filter و بسیاری دیگر است که میتوانید با استفاده از آنها جریان های داده را تا جایی که نیاز دارید سفارشی کنید.

برای یادگیری و درک بهتر عملگرها و سفارشی سازی جریان های داده، باید در محیط اپلیکیشن یا روی سمپل کدهای موجود، شروع به کار با آنها بکنید.


RxAndroid چیست؟

RxAndroid نسخه مخصوص سیستم عامل اندروید است که تعدادی کلاس اضافه تر در مقایسه با RxJava دارد. اگر بخواهیم دقیق تر بررسی کنیم، در RxAndroid یک ویژگی به نام Schedulers معرفی شد (AndroidScheduler.mainThread) که نقش اساسی در Multi-Threading اپلیکیشن های اندرویدی دارد. در حقیقت Scheduler ها تصمیم میگیرند که کدهای ما روی Thread اصلی یا در بک گراند اپلیکیشن اجرا شوند. نکته دیگر این است که به غیر از Schedulers، بقیه کلاس هایی که مورد استفاده قرار میگیرند، دقیقا همان کلاس هایی هستند که در RxJava وجود دارند.

rxjava برنامه نویسی reactive

هرچند Scheduler های زیادی وجود دارند، اما Schedulers.io و AndroidScheduler.mainThread خیلی بیشتر از بقیه در برنامه نویسی اندروید استفاده میشوند. در ادامه میتوانیم همه Scheduler های موجود و یک معرفی کوتاه از آنها را مشاهده کنیم:

  1. Schedulers.io: از این نوع برای اجرای عملیات هایی که بار زیادی روی CPU قرار نمیدهند استفاده میکنیم. کارهایی مانند انجام درخواست های شبکه، خواندن یا نوشتن فایل روی حافظه، ذخیره در دیتابیس و غیره.
  2. AndroidSchedulers.mainThread: این متد دسترسی به Thread اصلی (یا همان UI Thread) را به ما میدهد. معمولا کارهایی مثل به روز رسانی رابط کاربری و تعاملات کاربر در این Thread اتفاق می افتد. دقت داشته باشید که نباید هیچ عملیات سنگینی در این Thread انجام بدهیم. زیرا این کار باعث میشود اپلیکیشن فریز شده یا پیام Application Not Responding روی صفحه نمایش داده بشود.
  3. Schedulers.newThread: اگر از این متد استفاده کنیم، هر بار که یک تسک Schedule (برنامه ریزی برای اجرا در آینده) میشود، یک Thread جدید برای آن ساخته خواهد شد. پیشنهاد میکنم فقط برای عملیات هایی که مدت زمان زیادی طول میکشند از این روش استفاده کنید. دقت کنید، Thread هایی که با استفاده از متد newThread ساخته میشوند، نمیتوانند برای کارهای بعدی دوباره مورد استفاده قرار بگیرند.
  4. Schedulers.computation: از این مدل Scheduler میتوانیم برای پردازش های سنگین CPU استفاده کنیم. یعنی عملیات هایی مانند پردازش داده های زیاد، پردازش Bitmap و غیره. تعداد Thread هایی که توسط این متد ساخته میشوند، به تعداد هسته های پردازشی CPU دستگاه بستگی دارند.
  5. Schedulers.single: این Scheduler همه تسک هایی که درون آن اضافه شده اند را به ترتیب اجرا میکند. هر جا نیاز داشتیم تعدادی از عملیات ها پشت سر هم انجام شوند، میتوانیم از این متد استفاده کنیم.
  6. Schedulers.immediate: این متد همه تسک هایی که درون خودش دارد را بصورت همزمان، در همان لحظه انجام میدهد. البته برای این کار UI Thread بلاک خواهد شد.
  7. Schedulers.trampoline: این متد همه تسک های درون خودش را به ترتیب First in – First out انجام میدهد. یعنی اولین تسک اضافه شده، اولین تسک اجرا شده هم خواهد بود (برای درک بهتر باید بگم دقیقا مثل صف نونوایی که هرکی اول بیاد، زودتر نون میگیره). با استفاده از این Scheduler، همه تسک ها به ترتیب در پس زمینه اجرا خواهند شد، با این تفاوت که فقط و فقط یک Thread در پس زمینه ساخته میشود.
  8. Schedulers.from: این متد به ما اجازه میدهد تعداد Thread های ساخته شده برای اجرای کدها را محدود کنیم. زمانی که Thread Pool پر شده باشد (یعنی نتوانیم Thread جدید بسازیم) همه تسک ها در صف قرار میگیرند تا اجرا بشوند.

معرفی اجزای RxJava

در کل کتابخانه RxJava دو مفهوم بسیار مهم وجود دارد: Observable و Observer. علاوه بر اینها مفاهیم دیگری مانند Schedulers، Operators و Subscription هستند که در ادامه همه آنها را با هم بررسی میکنیم.


Observable

دو سنگ بنای اصلی برنامه نویسی Reactive، مفاهیم Observable و Subscriber ها هستند. بصورت ساده میشود گفت Observable ها منبع داده و Subscriber ها استفاده کننده های داده میباشند. تعریف این مفهوم را میتوانیم از اسم آن هم متوجه بشویم. Observable یعنی چیزی که میتواند مورد مشاهده و بررسی قرار بگیرد. جریان های داده ای که در برنامه نویسی Reactive وجود دارند، از نوع Observable هستند. یعنی این جریان ها میتوانند توسط دیگر اجزای اپلیکیشن به اصطلاح “مشاهده” یا Observe بشوند.

observable در برنامه نویسی reactive

با توجه به مواردی که گفته شد، باید گفت کلاس Observable یکی از اصلی ترین کلاس های RxJava است. اینترفیس آن شامل این متد ها میشود: onNext، onComplete، onError. کلاس Observable دو حالت کلی دارد: Complete Successfully برای وقتی که کار با موفقیت انجام شده و Observable متوقف میشود. دومین حالت Error Stopage است که در زمان به وجود آمدن مشکل اتفاق می افتد.

کار اصلی این کلاس تولید جریان داده ها در اپلیکیشن است. یعنی در برنامه نویسی Reactive، کلاس Observable تصمیم میگیرد چه زمانی داده ها را ارائه بدهد. وظیفه ما این است که به این تغییرات گوشی بدهیم و عکس العمل مناسب برای آنها داشته باشیم.

به عمل متصل شدن Observer و Observable به اصطلاح Subscription گفته میشود. دقت داشته باشید که برای یک Observable میتواند چندین Observer وجود داشته باشد.


Observer و Subscriber

چه تفاوت ها و شباهت هایی بین Subscriber و Observer وجود دارد؟ Observer چیزی است که همه تغییرات Observable را زیر نظر دارد. یعنی همه داده هایی که Observable در جریان داده قرار میدهد، توسط Observer دریافت میشوند. اما Subscriber شامل این اجزا میشود: Observer + متد unsubscribe + متد isUnsubscribed. یعنی Subscriber هم Observer و هم متد های اینترفیس Subscription را Implement میکند.

public abstract class Subscriber<> implements Observer<>, Subscription

بنابراین، Subscriber نه تنها میتواند روی Observable ثبت نام کند، بلکه میتواند unsubscribe هم انجام بدهد.

نکته مهمی که باید به یاد داشته باشیم این است که از درخواست های غیر همزمان باید Unsubscribe کنیم. Rx به ما اجازه میدهد به راحتی از Observable ها Unsubscribe کنیم. یعنی وقتی که روی یک Observable عمل Subscribe را انجام میدهیم، یک آبجکت از Subscription به ما برگردانده میشود که شامل متد unsubscribe است. در حقیقت این کار یک عملیات زنجیره ای را استارت میزند.

یعنی وقتی که متد unsubscribe صدا زده میشود، همه عملگر ها یکی یکی شروع به Unsubscribe شدن میکنند. این کار باعث جلوگیری از Memory Leak میشود. میتوانید متد unsubscribe را در مرحله onDestroy قرار بدهید.

همه Subscription ها میتوانند در CompositeSubscription ذخیره شوند. یعنی میتوانید یک اکتیویتی یا فرگمنت پایه بسازیم که شامل CompositeSubscription باشد. با اینکار میتوانید همه Subscription ها را برای استفاده های بعدی ذخیره کنید و این آبجکت بصورت خودکار پاکسازی میشود.


عملگرهای RxJava

عملگر ها سر راه جریان داده قرار میگیرند و کار خود را انجام میدهند. یعنی وقتی داده از Observable ارسال میشود، قبل از اینکه به دست Observer برسد، از فیلتر Operator رد شده و تغییراتی که نیاز است روی آن اعمال میشود. در ادامه با Operator های RxJava آشنا میشویم:

عملگر flatMap

فرض کنید جریان داده ای از List<User> داریم. این جریان داده یک List از کلاس User را به ما برمیگرداند که از سرور دریافت میشود (این لیست رو به عنوان مثال برای همه عملگر های این نوشته قراره به کار ببریم).

usersObservable.subscribe { users -&gt; println("Users: $users") }

اجزای این لیست عبارتند از:

Users: [Bob, Rob, Ben, Stan]

حالا اگر بخواهیم کاری کنیم که این جریان، به جریانی از کلاس User تبدیل شود (یعنی دیگه List نباشه)، میتوانیم از عملگر flatMap استفاده کنیم. کدهای زیر را مشاهده کنید:

usersObservable
.flatMap { users -&gt; Observable.from(users) }
.subscribe { user -&gt; println(user) }

نتیجه استفاده از این عملگر بصورت زیر خواهد بود:

Bob
Rob
Ben
Stan

عملگر filter

فرض کنید در مثال قبل میخواهیم کاربرانی که اول اسم آنها با حرف خاصی شروع میشود را انتخاب کنیم. عملگر filter میتواند این کار را برای ما انجام بدهد.

usersObservable
.flatMap { users -&gt; Observable.from(users) }
.filter { it.name.startsWith("b", ignoreCase = true) }
.subscribe { user -&gt; println(user) }

کدهای زیر نتیجه زیر را نشان خواهند داد:

Bob
Ben

عملگر take

با استفاده از عملگر take میتوانیم تعدادی از المان های مورد نظر خودمان را انتخاب کنیم. برای مثال اولین داده ورودی:

usersObservable
.flatMap { users -&gt; Observable.from(users) }
.filter { it.name.startsWith("в", ignoreCase = true) }
.take(1)
.subscribe { user -&gt; println(user) }

نتیجه:

Bob

و برای انتخاب آخرین مورد:

usersObservable
…
.takeLast(1)
.subscribe { user -&gt; println(user) }

که نتیجه آن به شکل زیر میشود:

Stan

البته دقت داشته باشید برای اینکه takeLast کار بکند، باید جریان داده کامل شده باشد. به زبان ساده تر یعنی متد onComplete باید صدا زده شده باشد.

عملگر map

فرض کنید میخواهیم داده های یک جریان را از یک نوع به نوع دیگری تبدیل کنیم. مثلا فرض کنید میخواهیم تعداد حروف موجود در نام های کاربری را پیدا کنیم.

usersObservable
.flatMap { users -&gt; Observable.from(users) }
.map { it.name.length }
.subscribe { println(it) }

نتیجه:

3
3
3
4

عملگر distinct

این عملگر برای حذف موارد کپی شده در جریان داده مورد استفاده قرار میگیرد.

usersObservable
.flatMap { users -&gt; Observable.from(users) }
.map { it.name.length }
.distinct()
.subscribe { println(it) }

نتیجه:

3
4

شروع به کار با برنامه نویسی Reactive

شروع برنامه نویسی reactive

خب باید سراغ کار با کتابخانه RxJava در برنامه نویسی اندروید و پیاده سازی برنامه نویسی Reactive در این زمینه برویم. برای شروع باید Dependency های RxJava و RxAndroid را به فایل build.gradle پروژه خودمان اضافه کنیم:

// RxJava
implementation 'io.reactivex.rxjava2:rxjava:2.1.9'

// RxAndroid
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'

 قدم اول – ساخت Observable

اولین قدم برای برنامه نویسی Reactive این است که یک Observable بسازیم تا داده ها را ارسال کنیم. در مثال زیر یک Observable ساختیم که تعدادی نام حیوان را ارسال میکند. در این مثال، عملگر just برای محدود کردن تعداد نام هایی که ارسال میشود، استفاده شده است.

Observable&lt;&gt; animalsObservable = Observable.just("Ant", "Bee", "Cat", "Dog", "Fox");

قدم دوم – ساخت Observer

حالا باید یک Observer بسازیم که به Observable ما گوش بدهد. در Observer متدهای اینترفیس زیر وجود دارند که با استفاده از آنها میتوانیم وضعیت Observable را متوجه بشویم.

  • متد onSubscriber: این متد وقتی صدا زده میشود که یک Observer به Observable مورد نظر Subscriber کند.
  • متد onNext: این متد وقتی صدا زده میشود که Observable شروع به ارسال داده میکند.
  • متد onError: هروقت هر خطایی اتفاق بیفتد، این متد صدا زده خواهد شد.
  • متد onComplete: وقتی که Observable همه داده ها را به صورت کامل ارسال کرد، این متد صدا زده میشود.
Observer&lt;&gt; animalsObserver = getAnimalsObserver();

private Observer&lt;String&gt; getAnimalsObserver() {
return new Observer&lt;String&gt;;() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe");
}
       @Override
        public void onNext(String s) {
            Log.d(TAG, "Name: " + s);
        }

        @Override
        public void onError(Throwable e) {
            Log.e(TAG, "onError: " + e.getMessage());
        }

        @Override
        public void onComplete() {
            Log.d(TAG, "All items are emitted!");
        }
    };
}

قدم سوم – Subscribe کردن

حالا باید عمل Subscribe کردن Observer روی Observable را انجام بدهیم. بعد از این کار است که دریافت داده ها در Observer شروع میشود. در این مرحله دو متد جدید دیگر را مشاهده میکنیم:

  1. متد subscribeOn با Schedulers.io: این متد به Observable دستور میدهد که همه تسک های خودش را را روی یک Thread پس زمینه انجام بدهد.
  2. متد observeOn با AndroidSchedulers.mainThread: این متد به Observer دستور میدهد که داده ها را در UI Thread دریافت کند. بنابراین میتوانید عکس العمل های مناسب برای UI را تعریف کنید.
animalsObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(animalsObserver);

قدم چهارم – اجرای برنامه

اگر این نرم افزار را اجرا کنید، خروجی زیر را میتوانید در LogCat ببینید:

onSubscribe
Name: Ant
Name: Bee
Name: Cat
Name: Dog
Name: Fox
All items are emitted!

تمام شد، شما اولین برنامه RxJava خودتان را با موفقیت ساختید و اجرا کردید. در مقاله های بعدی بیشتر درباره Schedulers و Observer ها صحبت میکنیم. اما برای شروع برنامه نویسی Reactive تا همین حد کافی است.


چرا باید از برنامه نویسی Reactive اندروید استفاده کنیم؟

با توجه به همه مواردی که تا اینجا بررسی کردیم، میتوان گفت که استفاده از برنامه نویسی Reactive میتواند در موارد زیادی مفید باشد. برای مثال:

  • تعاملات کاربر مانند کلیک کردن، Gesture و بقیه موارد.
  • Event های سیستم مانند GPS، Gyroscope و غیره.
  • پردازش داده های ورودی غیر همزمان (تعامل با سرور).
  • و بقیه سناریو هایی که نیاز به جریان داده ای دارند.

سوالی دارید؟

در این مقاله همه مواردی که برای برنامه نویسی Reactive در اندروید میتوانیم انجام بدهیم را با هم بررسی کردیم و روش کار با آن را یاد گرفتیم. اگر هنوز سوالی در ذهن شما وجود دارد یا میتوانید نکته دیگری به این نوشته اضافه کنید، با نوشتن آن در قسمت نظرات (همین پایین) این مقاله آموزشی را کامل تر کنید.


منابع بیشتر برای مطالعه

میتوانید از منابع زیر برای مطالعه بیشتر درباره برنامه نویسی Reactive استفاده کنید:

2 دیدگاه دربارهٔ «راهنمای کامل برنامه نویسی Reactive در اندروید»

دیدگاه‌ خود را بنویسید

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

اسکرول به بالا