שגרות ההמשך של Kotlin מספקות API שמאפשר לכם לכתוב קוד אסינכרוני. בעזרת Kotlin coroutines, אפשר להגדיר CoroutineScope, וכך לנהל את המועדים שבהם ה-coroutines צריכים לפעול. כל פעולה אסינכרונית פועלת בהיקף מסוים.
רכיבים שמודעים למחזור החיים מספקים תמיכה ברמה גבוהה ב-coroutines עבור היקפים לוגיים באפליקציה. במאמר הזה מוסבר איך להשתמש ב-coroutines ביעילות עם רכיבים שמודעים למחזור החיים.
הוספת יחסי תלות
היקפי הקורוטינות המובנים שמתוארים בנושא הזה כלולים ב-Lifecycle API. כשמשתמשים בהיקפי ההרשאות האלה, חשוב להוסיף את יחסי התלות המתאימים.
- כדי להשתמש בכלי ViewModel ב-Compose, צריך להשתמש ב-
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"). - כדי להשתמש בכלי השירות של מחזור החיים ב-Compose, משתמשים ב-
implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version").
היקפי coroutine עם מודעות למחזור החיים
Compose וספריות מחזור החיים מספקות את ההיקפים המובנים הבאים שאפשר להשתמש בהם באפליקציה.
ViewModelScope
ה-ViewModelScope מוגדר לכל ViewModel באפליקציה. כל שגרת המשך (coroutine) שמופעלת בהיקף הזה מבוטלת באופן אוטומטי אם ה-ViewModel מנוקה. קורוטינות שימושיות כאן כשצריך לבצע עבודה רק אם ViewModel פעיל. לדוגמה, אם אתם מחשבים נתונים מסוימים עבור פריסה, כדאי להגדיר את היקף העבודה ל-ViewModel, כך שאם ViewModel ינוקה, העבודה תבוטל אוטומטית כדי למנוע צריכת משאבים.
אפשר לגשת אל CoroutineScope של ViewModel דרך המאפיין viewModelScope של ViewModel, כמו בדוגמה הבאה:
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
בתרחישי שימוש מתקדמים יותר, אפשר להעביר CoroutineScope מותאם אישית ישירות לבונה של ViewModel כדי להחליף את viewModelScope שמוגדר כברירת מחדל. הגישה הזו מאפשרת יותר שליטה וגמישות, במיוחד במקרים הבאים:
בדיקה: אפשר להוסיף
TestScope, וכך קל יותר לשלוט בתזמון ולאמת את ההתנהגות של שגרות המשך (coroutine) בבדיקות יחידה (unit testing).הגדרה בהתאמה אישית: אפשר להגדיר את ההיקף באמצעות
CoroutineDispatcherספציפי (כמוDispatchers.Defaultלחישובים כבדים) אוCoroutineExceptionHandlerמותאם אישית, עוד לפני שה-ViewModel מתחיל את העבודה שלו.
היקפים שקשורים ליצירה
תופעות לוואי כמו אנימציות, קריאות לרשת או טיימרים צריכות להיות מוגבלות למחזור החיים של הרכיב הניתן להרכבה. כך, כשאלמנט שאפשר להרכיב יוצא מהמסך (יוצא מהקומפוזיציה), כל הקורוטינות שפועלות מבוטלות אוטומטית כדי למנוע דליפות זיכרון.
Compose מספקת את LaunchedEffect API כדי לטפל בהיקף של קומפוזיציה באופן הצהרתי.
LaunchedEffect יוצר CoroutineScope שמאפשר להריץ פונקציות השהיה. ההיקף קשור למחזור החיים של הקומפוזיציה של הרכיב הקומפוזבילי, ולא למחזור החיים של פעילות המארח.
- Enter: שגרת ההמשך מתחילה כשהפונקציה הקומפוזבילית נכנסת לקומפוזיציה.
- יציאה: שגרת ההמשך (coroutine) מבוטלת כשהקומפוזבילי יוצא מהקומפוזיציה.
- הפעלה מחדש: אם אחד מהמפתחות שמועברים אל
LaunchedEffectמשתנה, שגרת ההמשך הקיימת מבוטלת ומופעלת שגרת המשך חדשה.
בדוגמה הבאה אפשר לראות איך משתמשים ב-LaunchedEffect כדי ליצור אנימציה של פעימה. הקורוטינה קשורה לנוכחות של הרכיב הקומפוזבילי בקומפוזיציה ומגיבה לשינויים בהגדרות:
// Allow the pulse rate to be configured, so it can be sped up if the user is running // out of time var pulseRateMs by remember { mutableLongStateOf(3000L) } val alpha = remember { Animatable(1f) } LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes while (isActive) { delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user alpha.animateTo(0f) alpha.animateTo(1f) } }
מידע נוסף על LaunchedEffect זמין במאמר בנושא תופעות לוואי ב-Compose.
איסוף נתונים בתהליך שמתחשב במחזור החיים
כדי לאסוף בבטחה נתונים מ-Flow ב-Jetpack פיתוח נייטיב, צריך להשתמש ב-API collectAsStateWithLifecycle. הפונקציה הזו ממירה אובייקט Flow לאובייקט Compose State ומנהלת באופן אוטומטי את המינוי למחזור החיים. כברירת מחדל, האיסוף מתחיל כשמחזור החיים הוא STARTED ומסתיים כשמחזור החיים הוא STOPPED. כדי לשנות את התנהגות ברירת המחדל הזו, מעבירים את הפרמטר minActiveState עם שיטת מחזור החיים הרצויה, כמו Lifecycle.State.RESUMED.
בדוגמה הבאה אפשר לראות איך אוספים את StateFlow של ViewModel בקומפוזיציה:
@Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val messages by conversationViewModel.messages.collectAsStateWithLifecycle() ConversationScreen( messages = messages, onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) } ) } @Composable private fun ConversationScreen( messages: List<Message>, onSendMessage: (Message) -> Unit ) { MessagesList(messages, onSendMessage) /* ... */ }
איסוף מקביל של כמה תהליכים
במצב כתיבה, אפשר לאסוף כמה תהליכים במקביל על ידי הצהרה על כמה משתני מצב. מכיוון ש-collectAsStateWithLifecycle מנהל את ההיקף הבסיסי שלו, איסוף מקביל מטופל באופן אוטומטי:
@Composable
fun DashboardScreen(viewModel: DashboardViewModel = viewModel()) {
// Both flows are collected safely in parallel and will emit updates when either changes, the composables will recompose
val userData by viewModel.userFlow.collectAsStateWithLifecycle()
val feedData by viewModel.feedFlow.collectAsStateWithLifecycle()
// ...
}
חישוב ערכים באופן אסינכרוני באמצעות Flows
כדי לחשב ערכים באופן אסינכרוני, משתמשים בפונקציה StateFlow עם האופרטור stateIn.
בדוגמה הבאה נעשה שימוש ב-Flow רגיל שהומר ל-StateFlow. הפרמטר WhileSubscribed(5000) שומר על המינוי פעיל למשך חמש שניות אחרי שממשק המשתמש נעלם, כדי לטפל בשינויים בהגדרות.
val uiState: StateFlow<Result> = flow {
emit(repository.fetchData())
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = Result.Loading
)
משתמשים ב-collectAsStateWithLifecycle כדי להמיר את הערכים שנאספו ל-Compose
State, כדי שממשק המשתמש יוכל להתעדכן באופן תגובתי בכל פעם שהנתונים משתנים.
מידע נוסף על מצב זמין במאמר מצב ו-Jetpack פיתוח נייטיב.
מקורות מידע נוספים
צפייה בתוכן
דוגמאות
מומלץ בשבילך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- טיפול במחזורי חיים באמצעות רכיבים שמודעים למחזור החיים
- טעינה והצגה של נתונים עם חלוקה לדפים