StateFlow
ו-SharedFlow
הם ממשקי API לזרימה שמאפשרים לזרמים לשדר עדכוני מצב בצורה אופטימלית ולשלוח ערכים למספר צרכנים.
StateFlow
StateFlow
הוא תהליך גלוי לכולם שפולט עדכוני מצב נוכחיים וחדשים לקלפי. אפשר לקרוא את ערך המצב הנוכחי גם דרך המאפיין value
שלו. כדי לעדכן את המצב ולשלוח אותו לתהליך, מקצים ערך חדש למאפיין value
של הכיתה MutableStateFlow
.
ב-Android, StateFlow
מתאים מאוד לכיתות שצריכות לשמור על מצב משתנה שניתן לצפות בו.
בהתאם לדוגמאות מ-Kotlin flows, אפשר לחשוף את StateFlow
מ-LatestNewsViewModel
כדי ש-View
יוכל להאזין לעדכונים של מצב ממשק המשתמש, וכך מצב המסך ישרוד שינויים בהגדרות.
class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
// Backing property to avoid state updates from other classes
private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
// The UI collects from this StateFlow to get its state updates
val uiState: StateFlow<LatestNewsUiState> = _uiState
init {
viewModelScope.launch {
newsRepository.favoriteLatestNews
// Update View with the latest favorite news
// Writes to the value property of MutableStateFlow,
// adding a new element to the flow and updating all
// of its collectors
.collect { favoriteNews ->
_uiState.value = LatestNewsUiState.Success(favoriteNews)
}
}
}
}
// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
data class Success(val news: List<ArticleHeadline>): LatestNewsUiState()
data class Error(val exception: Throwable): LatestNewsUiState()
}
הכיתה שאחראית לעדכון של MutableStateFlow
היא היצרן, וכל הכיתות שאוספות מה-StateFlow
הן הצרכנים. בניגוד ל-flow קר שנוצר באמצעות ה-builder flow
, ה-StateFlow
הוא חם: איסוף מה-flow לא מפעיל קוד של יצרן. אובייקט StateFlow
תמיד פעיל ונמצא בזיכרון, והוא עומד בדרישות לאיסוף אשפה רק אם אין הפניות אחרות אליו מרמה של שורש לאיסוף אשפה.
כשצרכן חדש מתחיל לאסוף מהזרם, הוא מקבל את המצב האחרון בזרם ואת כל המצבים הבאים. אפשר למצוא את ההתנהגות הזו במחלקות אחרות שניתנות למדידה, כמו LiveData
.
ה-View
מאזין ל-StateFlow
כמו בכל תהליך אחר:
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
// Start a coroutine in the lifecycle scope
lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// Note that this happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
latestNewsViewModel.uiState.collect { uiState ->
// New value received
when (uiState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
}
}
}
}
כדי להמיר כל תהליך ל-StateFlow
, משתמשים באופרטור הביניים stateIn
.
StateFlow, Flow ו-LiveData
יש דמיון בין StateFlow
לבין LiveData
. שניהם סוגים גלויים של בעלי נתונים, ושניהם פועלים לפי דפוס דומה כשמשתמשים בהם בארכיטקטורת האפליקציות.
עם זאת, חשוב לזכור ש-StateFlow
ו-LiveData
מתנהגים באופן שונה:
- ב-
StateFlow
צריך להעביר מצב ראשוני ל-constructor, ואילו ב-LiveData
לא צריך. LiveData.observe()
מבטל את הרישום של הצרכן באופן אוטומטי כשהתצוגה עוברת למצבSTOPPED
, אבל האיסוף מ-StateFlow
או מכל תהליך אחר לא נפסק באופן אוטומטי. כדי לקבל את אותו התנהגות, צריך לאסוף את הזרימה מבלוקLifecycle.repeatOnLifecycle
.
הגברת זרימת הקור באמצעות shareIn
StateFlow
הוא זרם חם — הוא נשאר בזיכרון כל עוד הזרימה נאסף או בזמן שיש התייחסויות אחרות אליו משורש איסוף האשפה. אפשר להפעיל זרמי קרים חמים באמצעות האופרטור shareIn
.
לדוגמה, אפשר להשתמש ב-callbackFlow
שנוצר בתהליכים של Kotlin כדי לשתף את הנתונים שאוחזרו מ-Firestore בין הקולקטורים באמצעות shareIn
, במקום לדרוש מכל קולקטור ליצור תהליך חדש.
צריך להעביר את הפרטים הבאים:
CoroutineScope
שמשמש לשיתוף התהליך. ההיקף הזה צריך להיות פעיל למשך זמן רב יותר מכל לקוח אחר, כדי שהתהליך המשותף ימשיך להתקיים למשך הזמן הנדרש.- מספר הפריטים שצריך להפעיל מחדש לכל אסוף נתונים חדש.
- המדיניות בנושא התנהגות בתחילת הסשן.
class NewsRemoteDataSource(...,
private val externalScope: CoroutineScope,
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
}
בדוגמה הזו, התהליך latestNews
מפעיל מחדש את הפריט האחרון שהופץ למאסף חדש, והוא נשאר פעיל כל עוד externalScope
פעיל ויש מאספים פעילים. מדיניות ההתחלה SharingStarted.WhileSubscribed()
שומרת על הפעילות של היוצר ב-upstream כל עוד יש מנויים פעילים. יש עוד כללי מדיניות התחלה, כמו SharingStarted.Eagerly
להפעלת המפיק באופן מיידי או SharingStarted.Lazily
להפעלת השיתוף אחרי שהמנוי הראשון מופיע ולהשאיר את הזרם פעיל לנצח.
SharedFlow
הפונקציה shareIn
מחזירה SharedFlow
, תהליך חם שפולט ערכים לכל הצרכנים שאוספים ממנו. SharedFlow
הוא הכללה של StateFlow
שאפשר להגדיר בצורה רחבה.
אפשר ליצור SharedFlow
בלי להשתמש ב-shareIn
. לדוגמה, אפשר להשתמש ב-SharedFlow
כדי לשלוח סימנים לשאר האפליקציה, כך שכל התוכן יתעדכן מדי פעם באותו הזמן. בנוסף לאחזור החדשות האחרונות, כדאי גם לרענן את הקטע של פרטי המשתמש עם אוסף הנושאים המועדפים עליו. בקטע הקוד הבא, TickHandler
חושף SharedFlow
כך שכיתות אחרות יידעו מתי לרענן את התוכן שלו. בדומה ל-StateFlow
, משתמשים במאפיין תמיכה מסוג MutableSharedFlow
בכיתה כדי לשלוח פריטים לתהליך:
// Class that centralizes when the content of the app needs to be refreshed
class TickHandler(
private val externalScope: CoroutineScope,
private val tickIntervalMs: Long = 5000
) {
// Backing property to avoid flow emissions from other classes
private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
val tickFlow: SharedFlow<Event<String>> = _tickFlow
init {
externalScope.launch {
while(true) {
_tickFlow.emit(Unit)
delay(tickIntervalMs)
}
}
}
}
class NewsRepository(
...,
private val tickHandler: TickHandler,
private val externalScope: CoroutineScope
) {
init {
externalScope.launch {
// Listen for tick updates
tickHandler.tickFlow.collect {
refreshLatestNews()
}
}
}
suspend fun refreshLatestNews() { ... }
...
}
אפשר להתאים אישית את ההתנהגות של SharedFlow
בדרכים הבאות:
replay
מאפשר לשלוח מחדש מספר ערכים ששודרו בעבר למנויים חדשים.- באמצעות
onBufferOverflow
אפשר להגדיר מדיניות למקרים שבהם מאגר הנתונים הזמני מלא בפריטים לשליחה. ערך ברירת המחדל הואBufferOverflow.SUSPEND
, שגורם למבצע הקריאה להשהות. האפשרויות האחרות הןDROP_LATEST
אוDROP_OLDEST
.
ל-MutableSharedFlow
יש גם מאפיין subscriptionCount
שמכיל את מספר האוספים הפעילים, כדי שתוכלו לבצע אופטימיזציה של הלוגיקה העסקית בהתאם. MutableSharedFlow
מכיל גם את הפונקציה resetReplayCache
, אם לא רוצים להפעיל מחדש את המידע העדכני ביותר שנשלח לזרימה.
משאבי תהליך נוספים
- תהליכים ב-Kotlin ב-Android
- בדיקת תהליכים של Kotlin ב-Android
- דברים שחשוב לדעת על האופרטורים ShareIn ו-StateIn של Flow
- מעבר מ-LiveData ל-Kotlin Flow
- מקורות מידע נוספים על קורוטינים ועל זרמים ב-Kotlin