StateFlow ו-SharedFlow הם Flow APIs
שמאפשרת לתהליכי פליטה אופטימלית של עדכוני מצב ופליטת ערכים
צרכנים.
StateFlow
StateFlow
הוא תהליך גלוי לכולם שפולט את המצב הנוכחי והחדש
ועדכונים לאספנים. אפשר גם לקרוא את ערך המצב הנוכחי עד
value
לנכס. כדי לעדכן את המצב ולשלוח אותו לזרימה, יש להקצות ערך חדש ל-
המאפיין value של
כיתה MutableStateFlow.
ב-Android, האפליקציה StateFlow מתאימה מאוד לכיתות שצריך לתחזק
מצב ניתן למדידה.
בהמשך לדוגמאות של תהליכים של קוטלין, 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 הם הצרכנים. ביטול הלייק
תהליך קר שנוצר באמצעות ה-builder של flow, StateFlow הוא חם:
האיסוף מהתהליך לא מפעיל קוד של היצרן. StateFlow
תמיד פעיל ונמצא בזיכרון, והוא הופך למתאים לאשפה
רק כשאין התייחסות אחרת לאשפה
הרמה הבסיסית (root) של האוסף.
כשצרכן חדש מתחיל לאסוף מהתהליך, הוא מקבל את
ועל המצבים הבאים: אפשר לראות את ההתנהגות הזו
בסיווגים אחרים שניתנים למדידה,
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 שנוצר בתהליכים של קוטלין בתור
לדוגמה, במקום שכל אספן ייצור תהליך חדש, אפשר לשתף
את הנתונים שאוחזרו מ-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