סקירה כללית של ViewModel חלק מ-Android Jetpack.

המחלקה ViewModel היא לוגיקה עסקית או מצב ברמת המסך holder. הוא חושף את המצב לממשק המשתמש ומקיף את הלוגיקה העסקית הקשורה. היתרון העיקרי שלו הוא שהמצב נשמר במטמון ונשמר בו שינויים בתצורה. המשמעות היא שממשק המשתמש לא צריך לאחזר שוב נתונים במהלך ניווט בין פעילויות או בעקבות שינויים בהגדרות, כמו בעת סיבוב המסך.

למידע נוסף על בעלי מדינה, אפשר לעיין בהנחיות בנושא בעלי מדינה. באופן דומה, למידע נוסף על שכבת ממשק המשתמש באופן כללי, ראו שכבת ממשק המשתמש. הנחיה.

היתרונות של ViewModel

החלופה ל-ViewModel היא מחלקה פשוטה שמכילה את הנתונים שאתם מציגים בממשק המשתמש. הדבר עלול להוות בעיה כאשר עוברים בין פעילויות או יעדי ניווט. הפעולה הזו תשמיד את הנתונים האלה אם לא תאחסנו אותם. באמצעות מנגנון שמירת מצב המכונה. באמצעות ViewModel API לשמירת נתונים שפותר את הבעיה.

היתרונות העיקריים של המחלקה ViewModel הם בעיקרון:

  • הוא מאפשר לשמור על מצב ממשק המשתמש.
  • הוא מספק גישה ללוגיקה עסקית.

התמדה

התכונה ViewModel מאפשרת שמירה על עקביות גם במצב של ViewModel וגם הפעולות ש-ViewModel מפעיל. השמירה במטמון פירושה שלא כדי לאחזר שוב נתונים באמצעות שינויים נפוצים בהגדרות, כמו בסבב.

היקף

כשאתם יוצרים מופע של ViewModel, מעבירים לו אובייקט שמיישם את ViewModelStoreOwner. זה יכול להיות יעד ניווט, תרשים ניווט, פעילות, מקטע, או כל סוג אחר שמטמיע את גרפי. לאחר מכן המערכת מכסה את ה-ViewModel שלכם על מחזור החיים של ViewModelStoreOwner. הפריט נשאר בזיכרון עד ViewModelStoreOwner נעלם באופן סופי.

טווח של סיווגים הוא מחלקה ישירה או עקיפה של ממשק ViewModelStoreOwner. מחלקות המשנה הישירות הן ComponentActivity, Fragment ו-NavBackStackEntry. לרשימה מלאה של מחלקות משנה עקיפות אפשר לעיין קובץ עזר של ViewModelStoreOwner.

לאחר השמדת המקטע או הפעילות שאליהם ה-ViewModel מסתיים, העבודה האסינכרונית ממשיכה ב-ViewModel בהיקף שלה. כאן של הקבוע.

מידע נוסף זמין בקטע שבהמשך בנושא מחזור החיים של ViewModel.

הכינוי של SaveState

SavedStateHandle מאפשר לשמור נתונים לא רק באמצעות הגדרה לשינויים, אלא תוך כדי חשיבה מחדש על תהליך היצירה. כלומר, היא מאפשרת לכם לשמור על במצב תקין גם אם המשתמש סוגר את האפליקציה ופותח אותה במועד מאוחר יותר.

גישה ללוגיקה עסקית

למרות שהרוב המכריע של הלוגיקה העסקית נמצא בנתונים שכבת ה-UI, גם יכולה להכיל לוגיקה עסקית. מצב כזה יכול להיות כאשר לשלב נתונים מכמה מאגרים כדי ליצור את מצב ממשק המשתמש של המסך, סוג מסוים של נתונים לא מחייב שכבת נתונים.

ViewModel הוא המקום הנכון לטיפול בלוגיקה עסקית בשכבת ממשק המשתמש. בנוסף, ViewModel אחראי על הטיפול באירועים ומאציל אותם לגורמים אחרים בהיררכיה כאשר צריך להחיל לוגיקה עסקית כדי לשנות של נתוני האפליקציה.

Jetpack פיתוח נייטיב

כשמשתמשים ב-Jetpack פיתוח נייטיב, ViewModel הוא האמצעי העיקרי לחשיפת ממשק המשתמש של המסך של התכנים הקומפוזביליים. באפליקציה היברידית, פעילויות ומקטעים פשוט מארחים את הפונקציות הקומפוזביליות. זהו שינוי מגישות קודמות, שבהן זה לא היה מכיוון שהוא פשוט ואינטואיטיבי כדי ליצור חלקים לשימוש חוזר בממשק משתמש עם פעילויות ולכן הם היו פעילים הרבה יותר בתור בקרי ממשק משתמש.

הדבר שהכי חשוב לזכור כשמשתמשים ב-ViewModel עם 'כתיבה' הוא שאי אפשר להגדיר היקף של ViewModel לתוכן קומפוזבילי. זה בגלל שמודל קומפוזבילי אינו ViewModelStoreOwner. שני מופעים של אותו תוכן קומפוזבילי יצירה מוזיקלית, או שתי תכנים קומפוזביליים שונים שיש להם גישה לאותו סוג ViewModel תחת אותו ViewModelStoreOwner יקבל את אותו מופע של ל-ViewModel, ולרוב זו לא ההתנהגות הצפויה.

כדי ליהנות מהיתרונות של ViewModel ב-Compose, צריך לארח כל מסך ב-Fragment או פעילות, או להשתמש בניווט בכתיבה ולהשתמש ב-ViewModels בתוכן קומפוזבילי פועל קרוב ככל האפשר ליעד הניווט. הסיבה לכך היא אפשר להגדיר את ההיקף מ-ViewModel ליעדי ניווט, לתרשימי ניווט, פעילויות ומקטעים.

מידע נוסף מופיע במדריך בנושא העלאת מצב של גודל עבור Jetpack Compose.

הטמעת ViewModel

הדוגמה הבאה היא הטמעה של ViewModel במסך שמאפשר למשתמש להטיל קוביות.

Kotlin

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // Expose screen UI state
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Handle business logic
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

Java

public class DiceUiState {
    private final Integer firstDieValue;
    private final Integer secondDieValue;
    private final int numberOfRolls;

    // ...
}

public class DiceRollViewModel extends ViewModel {

    private final MutableLiveData<DiceUiState> uiState =
        new MutableLiveData(new DiceUiState(null, null, 0));
    public LiveData<DiceUiState> getUiState() {
        return uiState;
    }

    public void rollDice() {
        Random random = new Random();
        uiState.setValue(
            new DiceUiState(
                random.nextInt(7) + 1,
                random.nextInt(7) + 1,
                uiState.getValue().getNumberOfRolls() + 1
            )
        );
    }
}

לאחר מכן אפשר לגשת ל-ViewModel מפעילות באופן הבא:

Kotlin

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Java

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
        model.getUiState().observe(this, uiState -> {
            // update UI
        });
    }
}

Jetpack פיתוח נייטיב

import androidx.lifecycle.viewmodel.compose.viewModel

// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
    viewModel: DiceRollViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // Update UI elements
}

שימוש בקורוטינים ב-ViewModel

ViewModel כולל תמיכה בקורוטינים ב-Kotlin. הוא יכול להמשיך אסינכרונית באותו אופן שבו היא נשארת במצב של ממשק המשתמש.

למידע נוסף, אפשר לעיין במאמר שימוש בקורוטינים של Kotlin עם ארכיטקטורה של Android רכיבים.

מחזור החיים של ViewModel

מחזור החיים של ViewModel קשור ישירות להיקף שלו. ViewModel נשאר בזיכרון עד לתאריך ViewModelStoreOwner שאליו היא כוללת נעלם. מצב כזה עשוי לקרות בהקשרים הבאים:

  • במקרה של פעילות, היא מסתיימת.
  • במקרה של שבר, כשהוא מתנתק.
  • במקרה של רשומת ניווט, היא תוסר מהמקבץ האחורי.

לכן, ViewModels הוא פתרון נהדר לאחסון נתונים שעדיין נשארים שינויים בתצורה.

איור 1 ממחיש את המצבים השונים של מחזור החיים של פעילות בזמן שהיא עוברת של סיבוב, ואז הוא מסתיים. באיור מוצג גם משך החיים של ViewModel לצד מחזור החיים של הפעילות המשויכת. הספציפי הזה שממחיש את המצבים של פעילות. אותם מצבים בסיסיים חלים על את מחזור החיים של מקטע.

המחשה של מחזור החיים של ViewModel במצב של שינוי פעילות.

בדרך כלל מבקשים ViewModel בפעם הראשונה שהמערכת קוראת ל-method onCreate() של אובייקט הפעילות. המערכת עשויה להתקשר onCreate() כמה פעמים במהלך עצם קיומה של פעילות, כמו כמו כשמסובבים את מסך המכשיר. ViewModel קיים החל מתאריך לבקש ViewModel עד שהפעילות תסתיים ותושמד.

ניקוי יחסי התלות של ViewModel

ה-ViewModel קורא לשיטה onCleared כאשר ViewModelStoreOwner משמידים אותו במהלך מחזור החיים שלו. כך אפשר לנקות כל מקום עבודה או יחסי תלות שתואמים למחזור החיים של ViewModel.

הדוגמה הבאה מציגה חלופה ל-viewModelScope. viewModelScope הוא CoroutineScope מובנה עוקב באופן אוטומטי אחרי מחזור החיים של ViewModel. ה-ViewModel משתמש בו כדי להפעיל פעולות עסקיות. אם רוצים להשתמש במקום זאת בהיקף מותאם אישית של viewModelScope לבדיקה קלה יותר, ה-ViewModel יכול לקבל CoroutineScope כתלות ב-constructor שלה. כאשר ViewModelStoreOwner מנקה את ה-ViewModel בסוף מחזור החיים שלו, ViewModel גם מבטל את CoroutineScope.

class MyViewModel(
    private val coroutineScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {

    // Other ViewModel logic ...

    override fun onCleared() {
        coroutineScope.cancel()
    }
}

מגרסה 2.5 ואילך במחזור החיים, אפשר להעביר Closeable אחת או יותר אובייקטים ל-constructor של ViewModel שנסגרים אוטומטית, המופע של ViewModel הוסר.

class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}

class MyViewModel(
    private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
    // Other ViewModel logic ...
}

שיטות מומלצות

בהמשך מפורטות כמה שיטות מומלצות עיקריות שכדאי לפעול לפיהן במהלך ההטמעה ViewModel:

  • בגלל ההיקפים שלהם, השתמשו ב-ViewModels בתור פרטי ההטמעה של שומר מצב ברמת המסך. אין להשתמש בהם כבעלי מצב של ממשק משתמש לשימוש חוזר רכיבים כמו קבוצות צ'יפים או טפסים. אחרת, מקבלים במופע של ViewModel נעשה שימושים שונים באותו רכיב של ממשק המשתמש במסגרת ViewModelStoreOwner, אלא אם משתמשים במפתח מודל מפורש של תצוגה לכל צ'יפ.
  • פרטי ההטמעה של ממשק המשתמש לא ידועים ל-ViewModel. שמירת השמות השיטות ש-ViewModel API חושף וה-methods של שדות המצב של ממשק המשתמש כלליות ככל האפשר. כך, ה-ViewModel יכול לתת מענה לכל סוג ממשק משתמש: טלפון נייד, מתקפל, טאבלט או אפילו Chromebook
  • מאחר שהמודל ימשיך להיות ארוך יותר מ-ViewModelStoreOwner, מודלים של ViewModel לא יכולה להכיל הפניות לממשקי API שקשורים למחזור החיים, כמו Context או Resources כדי למנוע דליפות זיכרון.
  • אסור להעביר מודלים של ViewModel למחלקות אחרות, לפונקציות אחרות או לרכיבים אחרים של ממשק המשתמש. מאחר שהפלטפורמה מנהלת אותם, כדאי לשמור אותם בקרבת מקום כן. קרוב לפונקציה הקומפוזבילית ברמת הפעילות, מקטע או מסך. כך רכיבים ברמה נמוכה יותר יכולים לגשת ליותר נתונים ולוגיקה מאשר הדרושים להם.

מידע נוסף

ככל שהנתונים נעשים יותר מורכבים, כדאי ליצור כיתה נפרדת כדי לטעון את הנתונים. המטרה של ViewModel היא לבצע אנונימיזציה של הנתונים עבור בקר בממשק משתמש שמאפשר לנתונים לשרוד שינויים בתצורה. למידע על טעינה, שמירה וניהול של נתונים בשינויים שונים בתצורה, מצבים שמורים של ממשק המשתמש.

במדריך לארכיטקטורת אפליקציות של Android הצעה ליצור מחלקה של מאגר כדי לטפל בפונקציות האלה.

מקורות מידע נוספים

כדי לקבל מידע נוסף על הכיתה ViewModel, אפשר לעיין במאמרים הבאים: המשאבים.

מסמכים

דוגמיות