شروع کار با Flow در اندروید با کاتلین

سلام توی این پست قراره در مورد flow حرف بزنیم که اومده تا کلی از کارا رو آسون کنه :) منبع این پست این زیره اگه خواستید به زبان اصلی بخونیدش: https://proandroiddev.com/kotlin-flow-on-android-quick-guide-76667e872166 همچنین در آخر یه کارایی با LiveData میکنیم، پس اگه باهاش آشنا نیستید میتونید پست زیر رو بخونید: https://virgool.io/@Special_N9NE/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-livedata-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-%D8%A8%D9%87-%D8%B2%D8%A8%D8%A7%D9%86-%D8%B3%D8%A7%D8%AF%D9%87-%D8%A8%D8%A7-%DA%A9%D8%A7%D8%AA%D9%84%DB%8C%D9%86-nluzhq8gxg7n چند سالی هست که RxJava وجود داره و نقش مهمی رو توی خیلی از اپلیکیشن های اندرویدی بازی کرده و حالا Kotlin flow رقیب مهمی برای RxJava توی این عرصه هست ولی آیا Flow به اون بلوغی رسیده که جایگزین رقیبش بشه ؟ در این پست قراره چند تا کار مقدماتی انجام بدیم و اطلاعاتی رو از API به رابط کاربریمون منتقل کنیم.این Flow اصلا چیه ؟کاتلین Flow جزئی از Coroutine هست . با Coroutine میتونیم کدهامون رو به صورت نامتقارن(asynchronous) اجرا کنیم. حالا flow میاد و یه قدم بیشتر برمیداره و stream رو اضافه میکنه به داستان.معماریبریم یه نگاه به معماری بندازیم . قراره با معماری MVVM کار کنیم همراه با Repository برای لایه اطلاعاتمون.معمولا در RxJava ما از Observable در لایه دیتا استفاده میکردیم ، با ViewModel کنترلش میکردیم و با LiveData میفرستادیمش به لایه رابط کاربری یا همون UI . با Flow ما دیگه با Observable کاری نداریم بجاش از تابع های suspendهمراه با Flow استفاده میکنیم. پس معماری ما میشه یه چیزی شبیه تصویر پایین.رتروفیت برای Network Call هارتروفیت (Retrofit) یه کتابخونه خیلی خوبه که میخوایم ازش برای کار کردن با API استفاده کنیم . اگه از Flow استفاده کنید نیازی به کتابخونه دیگه ای نیست چون رتروفیت از ورژن 2.6.0 به بعد از تابع های suspend پشتیبانی میکنه پس دیگه نیازی به return کردن Observable ، Flowable و غیره نیست ، فقط کافیه object مورد نظرتون رو توی یه تابع که suspend هستش return کنید .@GET(&quotfoo&quot) suspend fun getFoo(): List<Foo>لایه شبکه همینجا تموم میشه.مخزن یا Repositoryخب بیاید در نظر بگیریم که میخوایم اطلاعاتمون رو با استفاده از Network call از repository دریافت کنیم در قالب Flow . خب ما میتونیم کد های مورد نظرمون رو توی بلاک { ... }flow بنویسیم. در این بلاک میتونیم پردازش های مورد نظر رو انجام بدیم مثلا یه درخواست API بدیم بعدش با استفاده از emit مقدار جدید رو push کنیم.البته شما میتونید از تابع هایی مثل()flowOf یا ()asFlow استفاده کنید که اینا هم میان object شما رو تبدیل به flow میکنن و مقدار رو مستقیم emit میکنن ، در کل از هر روشی که دوست دارید استفاده کنیدخب حالا که flow رو درست کردیم باید کارای map کردن رو انجام بدیم. با عملگر { ... }map. میشه این کارو انجام داد. اخرشم (...)flowOn. رو اضافه میکنیم تا روی thread درستی باشیم.class FlowRepository(private val api :FlowApiService){ fun getFoo() : Flow<List<Foo>>{ return flow{ // execute API call and map to UI object val fooList = api.getFoo() .map{ fooFromApi -> FooUI.fromResponse(fooFromApi) } emit(fooList) }.flowOn(Dispatchers.IO) // use the IO thread for this Flow } fun getFooAlternative(): Flow<List<Foo>>{ return api.getFoo() .map{ fooFromApi -> FooUI.fromResponse(fooFromApi) } .asFlow() .flowOn(Dispatchers.IO) } }خب ، حالا ما یه repository داریم که یه درخواست API میده و بعدش جواب رو map میکنه و میده به UI object .ا ViewModelتوی viewModel چند تا راه هست برای مدیریت flow ها . اول ما از روش ساده تر استفاده میکنیم که شبیه همون روشی هایی است که توی RxJava هم استفاده میکردیم ، بعدش هم از روش LiveData builder استفاده میکنیم.خب، ما یه repository داریم که یه flow برمیگردونه ولی چجوری این data رو مدیریت کنیم و بفرستیم به رابط کاربری؟ما کارمون رو با object های تغییر پذیر(mutable) و تغییرناپذیر(immutable) شروع میکنیم. کاری که میخوایم بکنیم اینه که مقادیر رو بدیم به متغیر mutable. پس اولین سوال اینه که چجوری اطلاعات رو از repository دریافت کنیم. گرفتن اطلاعات از flow خیلی آسونه ، فقط کافیه از تابع {...}collectاستفاده کنید. اون اطلاعاتی رو که map کردیم و اخرش emit کردیم رو یادتونه ؟ همون اطلاعات رو از طریق collect میشه دریافت کرد.ما فقط وقتی میتونیم از collect استفاده کنیم که یا توی یه coroutine باشیم یا توی یه تابع suspend . پس ما از viewModelScope استفاده میکنیم تا بتونیم اطلاعات flow رو دریافت کنیم.private val _foo = MutableLiveData<List<FooUI>>() val foo: LiveData<List<FooUI>> get() = _foo fun loadFoo() { viewModelScope.launch { fooRepository.getFoo() .collect { fooItems -> _foo.value = fooItems } } }خیلی خوب ، ما اطلاعات رو از API گرفتیم و دادیمش به liveData تا توی رابط کاربری اونو observe کنیم. ولی اگه این بین خطایی اتفاق افتاد چی میشه؟ در اینجاست که flow هواتونو داره! ما میتونیم یه مقدار رو emit کنیم وقتی که flow در قسمت {...} شروع به دریافت اطلاعات میکنه و اگر هم خطایی رخ بده توی {...}catch میتونیم مدیریتش کنیم.private val _foo = MutableLiveData<List<FooUI>>() val foo: LiveData<List<FooUI>> get() = _foo fun loadFoo() { viewModelScope.launch { fooRepository.getFoo() . { /* _foo.value = loading state */ } .catch { exception -> /* _foo.value = error state */ } .collect { fooItems -> _foo.value = fooItems } } }عالیه ! حالا ما اطلاعات دریافتی رو مدیریت کردیم و مقادیر مختلفی رو به UI فرستادیم.ا LiveData builderدر اینجا به جای اینکه ما دستی مقدار های liveData رو بدیم میتونیم از liveData builder استفاده کنیم. این رو به عنوان یه scope نگاه کنید ، این وقتی که یه UI شروع به observe کردن بکنه ، اجرا میشه و وقتی که هیچ UI نمونده باشه که observe کنه ، قبل تموم شدن کارش متوقف و کنسل میشه. استفاده از liveData builder آسونه ، ما دو تا راه داریم : کد هایی که میخوایم اجرا کنیم رو توی {...}liveData بنویسیم و مقدار رو emit کنیم ، یا اینکه از تابع ()asLiveData استفاده کنیم که دقیقا همون کار رو میکنه.val foo: LiveData<List<FooUI>> = liveData { fooRepository.getFoo() . { /* emit loading state */ } .catch { exception -> /* emit error state */ } .collect { fooItems -> emit(fooItems) } } val foo2: LiveData<List<FooUI>> = fooRepository.getFoo() . { /* emit loading state */ } .catch { exception -> /* emit error state */ }.asLiveData()و تمام. یه نکته ای که هست اینه که liveData builder قابلیت timeuout هم داره ، یعنی شما میتونید مقداری از زمان رو تعیین کنید که اگه پردازش کد داخل block بیشتر از این مقدار زمان طول کشید ، پردازش متوقف و کنسل بشه.و در همینجا آموزش به اتمام میرسه. ممنون که همراهی کردید اگه دوست داشتید لایک کنید و نظر خودتونو بدید درباره اش و بگید که دوست دارید پست های بعدی در مورد چی باشه :)بدرود.