narjes Mansoori
narjes Mansoori
خواندن ۱۰ دقیقه·۹ ماه پیش

آشنایی با مفهوم StateFlow و SharedFlow

قبل از مطالعه این پست به عنوان پیشنیاز پست قبلی رو مطالعه کنید.

خب StateFlow و SharedFlow دو کلاس در کتابخانه Kotlin Coroutines می‌باشند که به شما امکان مدیریت و جریان داده در برنامه‌های کاتلین را می‌دهند. با این حال، این دو کلاس تفاوت‌های مهمی دارند. بیایید به طور جزئی‌تر به هر کدام نگاه کنیم:

  1. StateFlow:
    • در واقع StateFlow یک جریان داده است که وضعیت (state) فعلی را نشان می‌دهد. هر بار که وضعیت تغییر می‌کند،مقدار جدید به همه مشترکین (Subscribers ها)ارسال می‌شود.
    • در واقع StateFlow یک روش push است،به این معنی که مقدار جدید به صورت فعال از تولید کننده به مشترکین ارسال می‌شود.
    • در واقع StateFlow یک Flowable است و می‌تواند یک مقدار اولیه داشته باشد.
    • برای دسترسی به مقدار فعلی StateFlow، می‌توانید از ویژگی value استفاده کنید.

مثال:
فرض کنید که یکViewModel داریم که یکStateFlow برای نمایش تعداد لایک‌ها در یک پست اینستاگرام را دارد. وقتی کاربر لایک می‌کند، تعداد لایک‌ها به صورت فعال تغییر می‌کند و به تمامی مشترکین ارسال می‌شود.

val likesStateFlow = MutableStateFlow(0) // مقدار اولیه 0 // تغییر تعداد لایک‌ها fun likePost() { likesStateFlow.value += 1 } // مشترک شدن در StateFlow viewModelScope.launch { likesStateFlow.collect { likes -> // نمایش تعداد لایک‌ها به کاربر println(&quotLikes: $likes&quot) } } // اجرای تغییر تعداد لایک‌ها likePost() // خروجی: Likes: 1 likePost() // خروجی: Likes: 2
  1. در مقابل SharedFlow به عنوان یک جریان داده مشترک عمل می‌کند که می‌تواند به چندین مشترک ارسال شود.
    • در واقع SharedFlow یک روش pull است، به این معنی که مشترکین برای دریافت مقادیر جدید از آن باید به صورت فعال از کلاس Flow.collect استفاده کنند.
    • در واقع SharedFlow می‌تواند یک مقدار اولیه داشته باشد، اما در مقابل StateFlow، وقتی یک مشترک جدید به جریان می‌پیوندد، مقدار اولیه ارسال نمی‌شود.
    • در واقع SharedFlow از استراتژی‌های مختلفی برای مدیریت جریان استفاده می‌کند، مانند استراتژی Latest, Buffer, Drop, Conflate و غیره.

مثال:
فرض کنید که دو فعالیت در برنامه‌ی شما اتفاق می‌افتد که باید از یک SharedFlow برای ارسال رویدادها استفاده کنند. در این حالت، هر بار که یک رویداد اتفاق می‌افتد، مقدار جدید به تمامی مشترکین ارسال می‌شود.

vale ventSharedFlow = MutableSharedFlow<String>() // ارسال یک رویداد fun sendEvent(event: String) { viewModelScope.launch { eventSharedFlow.emit(event) } } // مشترک شدن در SharedFlow viewModelScope.launch { eventSharedFlow.collect { event -> // نمایش رویداد به کاربر println(&quotEvent: $event&quot) } } // ارسال رویداد sendEvent(&quotClick&quot) sendEvent(&quotSwipe&quot)

خروجی:


Event: Click Event: Swipe

بنابراین، StateFlow و SharedFlow هر دو برای مدیریت و جریان داده استفاده می‌شوند، اما تفاوت‌های مهمی در روش ارسال داده و مشترک شدن در آنها وجود دارد. StateFlow وضعیت فعلی را نشان می‌دهد و به صورت فعال اطلاعات را به مشترکین ارسال می‌کند، در حالی کهSharedFlow به صورتpull عمل می‌کند و مشترکین باید به صورت فعال داده‌ها را دریافت کنند.

بیایید با مثال‌هایی از کاربردهای StateFlow وSharedFlow آشنا شویم :

  1. کاربرد StateFlow:
    فرض کنید یک برنامه داریم که یک شمارنده را نشان می‌دهد و کاربر می‌تواند با دکمه‌های افزایش و کاهش مقدار آن را تغییر دهد. در این حالت، می‌توانیم از StateFlow برای نگهداری و نمایش مقدار فعلی شمارنده استفاده کنیم.
val counterStateFlow = MutableStateFlow(0) // افزایش شمارنده fun incrementCounter() { counterStateFlow.value += 1 } // کاهش شمارنده fun decrementCounter() { counterStateFlow.value -= 1 } // مشترک شدن در StateFlow viewModelScope.launch { counterStateFlow.collect { counter -> // نمایش مقدار فعلی شمارنده به کاربر println(&quotCounter: $counter&quot) } } // افزایش شمارنده incrementCounter() // خروجی: Counter: 1 incrementCounter() // خروجی: Counter: 2 decrementCounter() // خروجی: Counter: 1

در این مثال، هر بار که مقدار شمارنده تغییر می‌کند، مقدار جدید به صورت فعال به تمامی مشترکین ارسال می‌شود و مشترکین مقدار فعلی را دریافت می‌کنند.

  1. کاربرد SharedFlow:
    فرض کنید یک برنامه داریم که بهصورت زنده نرخ قلب کاربر را نمایش می‌دهد. در این حالت، می‌توانیم از SharedFlow برای ارسال و دریافت نرخ قلب استفاده کنیم.
val heartRateSharedFlow = MutableSharedFlow<Int>() // ثبت نرخ قلب جدید fun recordHeartRate(heartRate: Int) { viewModelScope.launch { heartRateSharedFlow.emit(heartRate) } } // مشترک شدن در SharedFlow viewModelScope.launch { heartRateSharedFlow.collect { heartRate -> // نمایش نرخ قلب به کاربر println(&quotHeart Rate: $heartRate&quot) } } // ثبت نرخ قلب جدید recordHeartRate(75) // خروجی: Heart Rate: 75 recordHeartRate(80) // خروجی: Heart Rate: 80

در این مثال، هر بار که نرخ قلب جدید ثبت می‌شود، مقدار جدید به تمامی مشترکین ارسال می‌شود و مشترکین مقدار جدید را دریافت می‌کنند.

استفاده از StateFlow یا SharedFlow بستگی به نوع کاربرد و نیازهای خاص شما دارد. در ادامه، مثال‌هایی را بررسی می‌کنیم و توضیح می‌دهیم که کدام یک از این دو راه حل مناسب‌تر است:

  1. استفاده از StateFlow:
    معمولاً StateFlow برای مواردی مناسب است که مقدار فعلی داده مهم است و نیاز به ردیابی تغییرات فوری داریم. به عنوان مثال، فرض کنید در یک برنامه لیستی از پیام‌ها را نمایش می‌دهیم و کاربر می‌تواند پیام‌ها را حذف کند. در این حالت، می‌توانیم از StateFlow برای نگهداری و نمایش لیست پیام‌ها استفاده کنیم.
val messagesStateFlow = MutableStateFlow(listOf<String>()) // حذف پیام fun deleteMessage(message: String) { messagesStateFlow.value = messagesStateFlow.value - message } // مشترک شدن در StateFlow viewModelScope.launch { messagesStateFlow.collect { messages -> // نمایش لیست پیام‌ها به کاربر println(&quotMessages: $messages&quot) } } // حذف پیام deleteMessage(&quotHello&quot) // خروجی: Messages: [ ] deleteMessage(&quotHi&quot) // خروجی: Messages: [ ]

در این مثال، هر بار که یک پیام حذف می‌شود، لیست پیام‌ها به صورت فعال به تمامی مشترکین ارسال می‌شود و مشترکین لیست پیام‌های به‌روزشده را دریافت می‌کنند.

  1. استفاده از SharedFlow:
    معمولاً SharedFlow برای مواردی مناسب است که نیاز به انتشار یک رویداد یا اطلاعات در سراسر برنامه داریم. به عنوان مثال، فرض کنید یک برنامه داریم که اعلان‌ها را نمایش می‌دهد و هر بار که یک اعلان جدید دریافت می‌شود، باید آن را به تمامی صفحات برنامه ارسال کنیم. در این حالت، می‌توانیم از SharedFlow برای ارسال اعلان‌ها استفاده کنیم.
val notificationSharedFlow = MutableSharedFlow<String>() // دریافت اعلان جدید fun receiveNotification(notification: String) { viewModelScope.launch { notificationSharedFlow.emit(notification) } } // مشترک شدن در SharedFlow viewModelScope.launch { notificationSharedFlow.collect { notification -> // نمایش اعلان به کاربر println(&quotNew Notification: $notification&quot) } } // دریافت اعلان جدید receiveNotification(&quotNew message!&quot) // خروجی: New Notification: New message! receiveNotification(&quotReminder!&quot) // خروجی: 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 است، به این معنی که نیاز به یک مقدار اولیه دارد و این مقدار را به طور فوری به مشترکین ارسال می‌کند.

  • فقط مقدار آخرین مقدار شناخته شده را ارسال می‌کند.
  • دارای خاصیت value است که می‌توانیم مقدار فعلی را بررسی کنیم. همچنین یک تاریخچه با یک مقدار را نگهداری می‌کند که می‌توانیم آن را مستقیماً بدون نیاز به جمع‌آوری دریافت کنیم.
  • مقادیر تکراری متوالی را ارسال نمی‌کند. فقط زمانی مقدار را ارسال می‌کند که از مقدار قبلی متمایز باشد.
  • شباهتی به LiveData دارد به جز آگاهی از LifeCycle . برای اضافه کردن آگاهی از LifeCycle به آن، باید از scope repeatOnLifecycle با StateFlow استفاده شود، در این صورت مانند LiveData عمل خواهد کرد.

خب SharedFlow:

  • یک Hot Flow است.
  • نیازی به یک مقدار اولیه ندارد، بنابراین به طور پیش فرض هیچ مقداری را ارسال نمی‌کند.
  • می‌توان با استفاده از اپراتور replay تنظیم کرد که چندین مقدار قبلی را ارسال کند.
  • خاصیت value را ندارد.
  • تمامی مقادیر را ارسال می‌کند و اهمیتی به تمایز از مقدار قبلی نمی‌دهد. بنابراین مقادیر تکراری متوالی را نیز ارسال می‌کند.
  • شباهتی به LiveData ندارد.


امیدوارم مفید واقع شده باشد.😊

stateflow sharedflowکروتینcoroutinecoroutinesflow
Android Developer
شاید از این پست‌ها خوشتان بیاید