RxJava VS. LiveData

RxJava VS. LiveDataمسلما برنامه نویسی واکنش‌گرا یا برنامه نویسی Reactive یکی از اصول مهمیه که شما به عنوان یک توسعه دهنده اپِ موبایل باید بلد باشید ، حتما (یا احتمالا (یا شایدم اصلا)) با RxJava آشنایی دارید ، ما با RxJava (یا RxAndroid یا RxKotlin ، هر دوشون بر پایه RxJava هستند) می‌تونیم خیلی کارا بکنیم ، می‌تونیم اونو وصل کنیم به سرویس های API مون ، می‌تونیم باهاش برنامه های Event Base (برنامه هایی که نسبت به یک عمل قراره واکنش بدند) بنویسم ، می‌تونیم تو معماری MVVM استفاده کنیم و هزاران استفاده دیگه (البته هزاران که خالی بندیه) .اما LiveData چطور ؟ با وجود LiveData آیا ما لازمه بازم با RxJava کار کنیم ؟ جواب یک کلمه هستش : "بله" ، شما به هر حال به عنوان یه توسعه دهنده باید RxJava رو بلد باشید ، خیلی از پروژه های قدیمی هستند که بر پایه RxJava نوشته شدند و ممکنه شما توی شرکتی بری که نیاز باشه از اونا پشتیبانی کنی ، به اضافه اینکه خیلی از کارایی که با RxJava میشه کرد رو نمیشه با LiveData کرد .تو این مقاله می‌خوایم بررسی کنیم ببینم تکلیفمون چیه و چطوری باید از این دو تا در کنار هم استفاده کنیم .خب بذارید یه توضیح خیلی خیلی خیلی کوتاه در مورد خود LiveData بدم :بر طبق مستندات گوگل ، LiveData یه کلاس بر پایه دیزان پترنِ Observable هستش ولی برعکس حالت عادی که ما داریم LiveData به چرخه حیات (lifecycle) مجهزه ، برعکس RxJava نیازی به استفاده از Disposable ها برای آزادسازی مموری نیست ، نیازی نیست مثل RxJava ما خودمون بیایم بهش حالی کنیم که دیتا تغییر کرده و خودش به محض تغییرات دیتامون عکس العمل نشون میده .شما باید LiveData تون رو در کلاس ViewModel داشته باشید و بعدا اونو توی قسمت View برای بروزرسانی صفحه یا هر کار دیگه ای استفاده کنید ، یه کد ساده که مال خود داکِ گوگله رو با هم ببینیم :پیاده سازی در ViewModelاستفاده در Activity یا Viewهمون طوری که می‌بینید ما با استفاده از تغییراتِ LiveData میتونیم نسبت به وضعیت Aware یا آگاه بشیم و مطابق با اون واکنش نشون بدیم (یه نکته هم بگم ، اون کلمه lazy یعنی تا موقعی که شی ما صدا نشه نمیایم بهش حافظه اختصاص بدیم)اگه بخوایم یه لیست از مزایا و معیاب LiveData نسبت به Rx بگیم می‌تونیم به اینا اشاره کنیم :ما تو LiveData عملیات رو در MainThread انجام میدیم نه Background که این می‌تونه نسبتا بد باشه ، در حالی که تو Rx میتونیم با یه کانفیگ ساده مشخص کنیم که عملیات پردازش داده توی Background اجرا بشه (عیب)ما تو LiveData چون نسبت به چرخه حیات آگاه هستیم (lifecycle-aware) به همین خاطر خودش تشخیص میده که کی باید واکنش انجام بشه و لازم نیست مثل Rx بهش بفهمونیم (مزیت)ما خیلی از اپراتور های Rx رو اینجا نداریم ! البته اینو بگم اپراتورهایی مثل map و ... اصلا برا Rx نیستن بلکه Rx میاد صرفا ازشون استفاده میکنه ، بعضی وقتا هم انواع خاصی از LiveData وجود دارن که میتونن جایگزین اپراتورهای خاصی بشن ولی خب بازم این یک نقطه ضفعه ! (عیب)مموری لیک در LiveData پیش نمیاد (مزیت)در Rx همه چیز یک جریان داده است (Stream) ولی در LiveData این طور نیست (عیب)همه چیز در LiveData حالت synchronous رو داره (آقا این مبحث سنکرون بودن لزوما ربطی به Multi Thread بودن برنامه نداره ، سنکرون منظور اینه که شما برای اجرای تسک A باید صبر کنی قبلیش تموم بشه ولی تو آسنکرون اینطوری نیست ، حالا Thread چیه این وسط ؟ برای پیاده سازی آسنکرون میتونن از چند تا Thread استفاده کنن ولی بازم میشه تو یک Thread قضیه آسنکرون باشه (که البته پیاده سازی جالبی نیست) ، این یک اشتباهه که خیلی‌ها در فهم این مطلب دارن) (عیب)ما اون قضیه های و ... رو که در Rx داشتیم در LiveData نداریم (عیب)خب الان ما مزایا و معایب رو لیست کردیم ، الان چیکار کنیم ؟ Rx رو بندازیم دور یا LiveData رو پرت کنیم تو سطح آشغال ؟ "هیچکدوم" ! جفتشو ترکیب میکنیم !!!! الان میخوایم یه اپی رو بنویسم که جفتش رو با هم به کار ببریم (کدی که برای این قضیه نوشتم رو آخر پست براتون میذارم) هدف اینه که یه لیست از حیوانات رو از سرور بگیریم و نشون بدیم ، معماری این اپ رو با MVVM جلو می‌بریم ، اولین گام تعریف کلاس دیتامون هست :data class Animal ( val name : String?, val taxonomy: Taxonomy?, val location: String?, val speed : Speed?, val diet : String?, //@SerializedName("lifespan") optional if you use correct spell val lifespan : String?, val image: String?)بعد از اون باید کلاس ViewModel رو ایجاد کنیم :class ListViewModel(appliaction : Application) : AndroidViewModel(appliaction)توی این کلاس ما سه پارامتر از LiveData استفاده می‌کنیم و جنس اون LiveData رو هم مشخص می‌کنیم ، یکی داده ها ، یکی برای ارور و یکی هم برای loading ، دو پارامتر دیگه یکی disposable هست که برای ترکیب Rx نیاز داریم تا بتونیم بعدا Rx رو clear کنیم ، یکی هم که باید api مون رو به برنامه با Dagger تزریق (Inject) کنیم (اگه شما با Dagger آشنایی نداری اصلا مهم نیست ، با همین چند تا تیکه کد می‌تونید بفهمید اینجا چیکار داریم می‌کنیم و بعدا خودتون تو اندروید استدیو اپ مشابه رو بزنید) :val animals by lazy { MutableLiveData<List<Animal>>() }val loadError by lazy { MutableLiveData<Boolean>() }val loading by lazy { MutableLiveData<Boolean>() }privateval disposable = CompositeDisposable()@Injectlateinit var apiService : AnimalApiServiceواضحه که ما در View نسبت به مقادیری که این پارامتر ها دارن قراره واکنش نشون بدیم و UI رو بروز کنیم خب کاری که من می‌خوام بکنم اینه ، میخوایم حیواناتی از لیست رو داشته باشیم که تو اسمشون حتما حرف "o" وجود داشته باشه ، پس باید از اپراتور filter استفاده کنیم :disposable.add(apiService.getAnimals(key) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .map { it.filter { it.name!!.toLowerCase().contains("o") } } .subscribe( { loading.value = false loadError.value = false animals.value = it }, { it.printStackTrace() loadError.value = true loading.value = false } ))بقیه کار به راحتی توی لایه View قابل پیاده سازیه ، ما اونجا اول باید یک شی از viewModel داشته باشیم :private lateinit var viewModel : ListViewModelبعدش باید بیاییم متناسب با کارایی که می‌خوایم بکنیم یک سری Observer تعریف کنیم و بعدش عملیات هامون رو بنویسم ، در یکی باید لیست تشکیل بشه ، در یکی باید علامت Loading بیاد (پروگرس‌بار) و در یکی هم باید خطا نمایش داده بشه (این قضیه it?let اینه که میاد بررسی می‌کنه اگه it مخالف null باشه کد پایینش رو اجرا می‌کنه ، اجرای این کد سوای اینکه منوط به null نبودن it هست به animalListDataObserver هم بستگی داره و فقط وقتی اجرا میشه که ما animalListDataObserver رو تغییر بدی) :private var animalListDataObersver = Observer<List<Animal>>(){ it?.let{ list_animals.visibility = View.VISIBLE adapterList.updateAnimalList(it) }}private var loadingObersver = Observer<Boolean>(){ loadingView.visibility = if(it) View.VISIBLE else View.GONE list_animals.visibility = if(it) View.GONE else list_animals.visibility txt_error.visibility = if(it) View.GONE else list_animals.visibility}private var errorObersver = Observer<Boolean>(){ txt_error.visibility = if(it) View.VISIBLE else View.GONE}حالا توی onCreate (البته اینجا من تو onViewCreated نوشتم چون فرگمنته) باید پارامتر های viewModel رو به این Observer هایی که تعریف کردیم وصل کنیم :viewModel = ViewModelProviders.of(this).get(ListViewModel::class.java)viewModel.animals.observe(this,animalListDataObersver)viewModel.loading.observe(this,loadingObersver)viewModel.loadError.observe(this,errorObersver)viewModel.refresh()تهش می‌بینیم که همچین خروجی داریم و همشون کاراکتر "o" رو دارند :خروجی نهاییخب اینم از این ، دوستان اگر سوال ، انتقاد یا ایراد علمی نسبت به این مقاله دارید حتما در کامنت منو مطلع کنید ، موفق باشید ! https://gitlab.com/drflakelorenzgerman/mvvmsample Code Sample (MVVM + RxJava + LiveData +Dagger)