قبل از مطالعه این پست به عنوان پیشنیاز پست قبلی رو مطالعه کنید.
خب StateFlow و SharedFlow دو کلاس در کتابخانه Kotlin Coroutines میباشند که به شما امکان مدیریت و جریان داده در برنامههای کاتلین را میدهند. با این حال، این دو کلاس تفاوتهای مهمی دارند. بیایید به طور جزئیتر به هر کدام نگاه کنیم:
مثال:
فرض کنید که یکViewModel داریم که یکStateFlow برای نمایش تعداد لایکها در یک پست اینستاگرام را دارد. وقتی کاربر لایک میکند، تعداد لایکها به صورت فعال تغییر میکند و به تمامی مشترکین ارسال میشود.
val likesStateFlow = MutableStateFlow(0) // مقدار اولیه 0 // تغییر تعداد لایکها fun likePost() { likesStateFlow.value += 1 } // مشترک شدن در StateFlow viewModelScope.launch { likesStateFlow.collect { likes -> // نمایش تعداد لایکها به کاربر println("Likes: $likes") } } // اجرای تغییر تعداد لایکها likePost() // خروجی: Likes: 1 likePost() // خروجی: Likes: 2
مثال:
فرض کنید که دو فعالیت در برنامهی شما اتفاق میافتد که باید از یک SharedFlow برای ارسال رویدادها استفاده کنند. در این حالت، هر بار که یک رویداد اتفاق میافتد، مقدار جدید به تمامی مشترکین ارسال میشود.
vale ventSharedFlow = MutableSharedFlow<String>() // ارسال یک رویداد fun sendEvent(event: String) { viewModelScope.launch { eventSharedFlow.emit(event) } } // مشترک شدن در SharedFlow viewModelScope.launch { eventSharedFlow.collect { event -> // نمایش رویداد به کاربر println("Event: $event") } } // ارسال رویداد sendEvent("Click") sendEvent("Swipe")
خروجی:
Event: Click Event: Swipe
بنابراین، StateFlow و SharedFlow هر دو برای مدیریت و جریان داده استفاده میشوند، اما تفاوتهای مهمی در روش ارسال داده و مشترک شدن در آنها وجود دارد. StateFlow وضعیت فعلی را نشان میدهد و به صورت فعال اطلاعات را به مشترکین ارسال میکند، در حالی کهSharedFlow به صورتpull عمل میکند و مشترکین باید به صورت فعال دادهها را دریافت کنند.
بیایید با مثالهایی از کاربردهای StateFlow وSharedFlow آشنا شویم :
val counterStateFlow = MutableStateFlow(0) // افزایش شمارنده fun incrementCounter() { counterStateFlow.value += 1 } // کاهش شمارنده fun decrementCounter() { counterStateFlow.value -= 1 } // مشترک شدن در StateFlow viewModelScope.launch { counterStateFlow.collect { counter -> // نمایش مقدار فعلی شمارنده به کاربر println("Counter: $counter") } } // افزایش شمارنده incrementCounter() // خروجی: Counter: 1 incrementCounter() // خروجی: Counter: 2 decrementCounter() // خروجی: Counter: 1
در این مثال، هر بار که مقدار شمارنده تغییر میکند، مقدار جدید به صورت فعال به تمامی مشترکین ارسال میشود و مشترکین مقدار فعلی را دریافت میکنند.
val heartRateSharedFlow = MutableSharedFlow<Int>() // ثبت نرخ قلب جدید fun recordHeartRate(heartRate: Int) { viewModelScope.launch { heartRateSharedFlow.emit(heartRate) } } // مشترک شدن در SharedFlow viewModelScope.launch { heartRateSharedFlow.collect { heartRate -> // نمایش نرخ قلب به کاربر println("Heart Rate: $heartRate") } } // ثبت نرخ قلب جدید recordHeartRate(75) // خروجی: Heart Rate: 75 recordHeartRate(80) // خروجی: Heart Rate: 80
در این مثال، هر بار که نرخ قلب جدید ثبت میشود، مقدار جدید به تمامی مشترکین ارسال میشود و مشترکین مقدار جدید را دریافت میکنند.
استفاده از StateFlow یا SharedFlow بستگی به نوع کاربرد و نیازهای خاص شما دارد. در ادامه، مثالهایی را بررسی میکنیم و توضیح میدهیم که کدام یک از این دو راه حل مناسبتر است:
val messagesStateFlow = MutableStateFlow(listOf<String>()) // حذف پیام fun deleteMessage(message: String) { messagesStateFlow.value = messagesStateFlow.value - message } // مشترک شدن در StateFlow viewModelScope.launch { messagesStateFlow.collect { messages -> // نمایش لیست پیامها به کاربر println("Messages: $messages") } } // حذف پیام deleteMessage("Hello") // خروجی: Messages: [ ] deleteMessage("Hi") // خروجی: Messages: [ ]
در این مثال، هر بار که یک پیام حذف میشود، لیست پیامها به صورت فعال به تمامی مشترکین ارسال میشود و مشترکین لیست پیامهای بهروزشده را دریافت میکنند.
val notificationSharedFlow = MutableSharedFlow<String>() // دریافت اعلان جدید fun receiveNotification(notification: String) { viewModelScope.launch { notificationSharedFlow.emit(notification) } } // مشترک شدن در SharedFlow viewModelScope.launch { notificationSharedFlow.collect { notification -> // نمایش اعلان به کاربر println("New Notification: $notification") } } // دریافت اعلان جدید receiveNotification("New message!") // خروجی: New Notification: New message! receiveNotification("Reminder!") // خروجی: New Notification: Reminder!
در این مثال، هر بار که یک اعلان جدید دریافت میشود، اعلان به تمامی مشترکین ارسال میشود و مشترکین اعلان جدید را دریافتند.
خب حالا باز یک مثال ملموس تر در پروژه های اندرویدی میخواییم بزنیم:
در مثال اول، سناریویی توضیح داده شده است که در آن لیستی از کاربران از شبکه دریافت و در رابط کاربری نمایش داده میشود. در ViewModel از StateFlow استفاده میشود. این StateFlow با نام usersStateFlow ایجاد شده و مقدار اولیه آن به صورت UiState.Loading تنظیم میشود. در Activity نیز یک collector برای این StateFlow وجود دارد.
وقتی که Activity باز میشود، فعالیت به collect مشترک میشود و مقدار اولیه یعنی وضعیت Loading از StateFlow جمعآوری میشود.
سپس، ViewModel اطلاعات را از شبکه دریافت میکند و آنها را به usersStateFlow ارسال میکند.
usersStateFlow.value = UiState.Success(usersFromNetwork)
سپس، collector در Activity دادههای کاربران را دریافت و در رابط کاربری نمایش میدهد.
در صورتی که تغییر جهت دستگاه رخ دهد و ViewModel باقی بماند، collector موجود در Activity دوباره به collect مشترک میشود و دادههای موجود در usersStateFlow که لیست کاربران دریافت شده از شبکه است را جمعآوری میکند.
مزیت استفاده از StateFlow در اینجا این است که نیازی به درخواست جدید از شبکه نیست و دادههای قبلی در ViewModel باقی میمانند.
سپس به بررسی استفاده از SharedFlow میپردازیم. در مثال دوم، سناریویی توضیح داده شده است که در آن در صورتی که یک وظیفه با شکست مواجه شود، باید Snackbar نمایش داده شود. در ViewModel از SharedFlow استفاده میشود. این SharedFlow با نام showSnackbarSharedFlow ایجاد شده و یک collector برای آن در Activity وجود دارد.
وقتی که Activity باز میشود، فعالیت به collect مشترک میشود، اما در اینجا چیزی جمعآوری نمیشود زیرا از SharedFlow استفاده شده است.
سپس، ViewModel کار را شروع کرده و در صورتی که با شکست مواجه شود، مقدار true را به showSnackbarSharedFlow ارسال میکند.
showSnackbarSharedFlow.emit(true)
سپس، collector در Activity مقدار true را دریافت و Snackbar را نمایش میدهد.
در صورتی که تغییر جهت دستگاه رخ دهد و ViewModel باقی بماند، collector موجود در Activity دوباره به collect مشترک میشود، اما در اینجا چیزی جمعآوری نمیشود زیرا SharedFlow آخرین مقدار را نگهداری نمیکند و همین امر مطلوب است زیرا ما نباید Snackbar را در تغییر جهت دستگاه مجدداً نمایش دهیم.
مزیت استفاده از SharedFlow در اینجا این است که Snackbar را مجدداً نمایش نمیدهد همانطور که مورد نیاز است.
حالا به استفاده از StateFlow به جای SharedFlow میپردازیم. در مثال سوم، در ViewModel از StateFlow استفاده میشود. این StateFlow با نام showSnackbarStateFlow ایجاد شده و مقدار اولیه آن به صورت false تنظیم میشود. در Activity نیز یک collector برای این StateFlow وجود دارد.
وقتی که Activity باز میشود، فعالیت به collect مشترک میشود و مقدار اولیه یعنی false از StateFlow جمعآوری میشود.
سپس، ViewModel کار را شروع کرده و در صورتی که با شکست مواجه شود، مقدار true را به showSnackbarStateFlow ارسال میکند.
showSnackbarStateFlow.value = true
سپس، collector در Activity مقدار true را دریافت و Snackbar را نمایش میدهد.
در صورتی که تغییر جهت دستگاه رخ دهد و ViewModel باقی بماند، collector موجود در Activity دوباره به collect مشترک میشود و مقدار true که آخرین مقدار است را جمعآوری میکند. این باعث میشود Snackbar مجدداً نمایش داده شود و این امر مطلوب نیست.
معایب استفاده از StateFlow در اینجا این است که Snackbar را مجدداً نمایش میدهد که لازم نیست.
بنابراین، در این مورد باید از SharedFlow به جای StateFlow استفاده کنیم.
نکته : عملگر replay
در SharedFlow
کنترل میکند که آیا یک SharedFlow
میتواند مقادیر قبلی را برای مشترکین جدیدی که به آن متصل میشوند، ارسال کند یا خیر. با استفاده از این عملگر، میتوانید تعیین کنید که چند مقدار قبلی برای مشتریان جدید بازیابی شود.
مثال زیر نشان میدهد چگونه با استفاده از عملگر replay
میتوان مقادیر قبلی را برای مشترکین جدید بازیابی کرد:
suspend fun main() {
val sharedFlow = MutableSharedFlow<Int>(replay = 2) // بازیابی 2 مقدار قبلی
// مشترک 1
val job1 = sharedFlow.collect { println("مشترک 1: $it") }
// مقادیر را ارسال میکنیم
sharedFlow.emit(1)
sharedFlow.emit(2)
sharedFlow.emit(3)
delay(1000) // تأخیر برای جلوگیری از مشاهده مقادیر تا اینجا توسط مشترک دیگر
// مشترک 2
val job2 = sharedFlow.collect { println("مشترک 2: $it") }
// لغو مشترکین
job1.cancel()
job2.cancel()
}
خروجی:
output:
مشترک 1: 1
مشترک 1: 2
مشترک 1: 3
مشترک 2: 2
مشترک 2: 3
در این مثال، با استفاده از replay = 2
، مقادیر قبلی دو مقدار ارسالی (1
و 2
) به مشترک جدید (مشترک 2
) بازیابی میشوند. این به مشترک جدید اجازه میدهد تا مقادیر قبلی را دریافت کرده و سپس از آخرین مقدار (3
) شروع به دریافت مقادیر کند.
حالا که درک خوبی از StateFlow و SharedFlow داریم، میتوانیم به راحتی تصمیم بگیریم که در هر مورد از کدام یک استفاده کنیم.
نتیجه گیری :
بر اساس مطالب فوق، تفاوتهای بین StateFlow و SharedFlow به شرح زیر است:
خب StateFlow یک Hot Flow است، به این معنی که نیاز به یک مقدار اولیه دارد و این مقدار را به طور فوری به مشترکین ارسال میکند.
خب SharedFlow:
امیدوارم مفید واقع شده باشد.😊