מחזור החיים של תכנים קומפוזביליים

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

סקירה כללית של מחזור החיים

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

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

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

תרשים שמציג את מחזור החיים של תוכן קומפוזבילי

איור 1. מחזור החיים של חומר קומפוזבילי ביצירה הוא מזין את יצירה מוזיקלית, הרכבה מחדש 0 פעמים או יותר, ויוצאת מהיצירה.

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

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

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

תרשים שמציג את הסידור ההיררכי של הרכיבים בקטע הקוד הקודם

איור 2. ייצוג של MyComposable ביצירה. אם נקרא 'קומפוזביליות' כמה פעמים, יצירה מוזיקלית. שרכיב בצבע אחר מצביע על כך שהוא למופע נפרד.

האנטומיה של חומר קומפוזבילי ביצירה

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

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

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

עיינו בדוגמה הבאה:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

בקטע הקוד שלמעלה, LoginScreen יקרא באופן מותנה את התוכן הקומפוזבילי LoginError, ותמיד יקרא לתוכן הקומפוזבילי LoginInput. כל אחד הקריאה כוללת אתר התקשרות ומיקום מקור ייחודיים, שבהם המהדר ישתמש כדי לזהות אותו באופן ייחודי.

תרשים שמראה איך הקוד הקודם מורכב מחדש אם דגל showError משתנה ל-True. התוכן הקומפוזבילי LoginError נוסף, אבל שאר התכנים הקומפוזביליים לא מורכבים מחדש.

איור 3. ייצוג של LoginScreen ביצירה כאשר המדינה ומתרחשת הרכבה מחדש. אם הצבע שונה, הסרטון לא הורכב מחדש.

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

הוספת מידע כדי לעזור הרכבים מחדש חכמים

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

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

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

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

איור 4. ייצוג של MoviesScreen ביצירה כאשר יצירה חדשה יתווסף לתחתית הרשימה. MovieOverview תכנים קומפוזביליים ב ניתן לעשות שימוש חוזר ביצירה. משמעות צבע זהה ב-MovieOverview היא תוכן קומפוזבילי לא קומפוזיציה.

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

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

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

איור 5. ייצוג של MoviesScreen ביצירה כאשר יצירה חדשה נוסף לרשימה. אי אפשר לעשות שימוש חוזר ב-MovieOverview תכנים קומפוזביליים ו כל תופעות הלוואי יופעלו מחדש. צבע שונה ב-MovieOverview פירושו בוצע קומפוזביליות.

באופן אידיאלי, כדאי לחשוב על הזהות של המכונה MovieOverview בתור מקושר לזהות של movie שמועברת אליו. אם נזמין מחדש את של סרטים, באופן אידיאלי נסדר מחדש את המופעים באופן דומה עץ הרכב במקום להרכיב מחדש כל תוכן קומפוזבילי של MovieOverview באמצעות למופע של סרט אחר. פיתוח נייטיב מאפשר לך לציין את זמן הריצה באילו ערכים להשתמש כדי לזהות חלק מסוים בעץ: key קומפוזבילי.

על ידי גלישת בלוק קוד עם קריאה למפתח קומפוזבילי עם אחד או יותר יועברו, הערכים האלה ישולבו כדי לשמש לזיהוי במכונה. הערך של key לא צריך להיות ייחודי בכל העולם, הוא צריך להיות ייחודי רק בין ההפעלות של תכנים קומפוזביליים באתר השיחה. בדוגמה הזו, כל movie צריך לכלול key שהוא ייחודי בין movies; זה בסדר אם הוא משתף את key עם תכנים קומפוזביליים אחרים באפליקציה.

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

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

תרשים שמראה איך הקוד הקודם מורכב מחדש אם רכיב חדש מתווסף לראש הרשימה. מאחר שהפריטים ברשימה מזוהים באמצעות מפתחות, התכונה &#39;כתיבה&#39; יודעת לא להרכיב אותם מחדש, למרות שהמיקום שלהם השתנה.

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

לחלק מהתכנים הקומפוזביליים יש תמיכה מובנית בתוכן הקומפוזבילי של key. לדוגמה, LazyColumn מקבל ציון של key מותאם אישית ב-DSL items.

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

המערכת מדלגת אם הקלט לא השתנה

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

אפשר לדלג על פונקציה קומפוזבילית אלא אם:

  • פונקציה מסוג 'החזרה' היא לא Unit
  • לפונקציה יש הערות עם @NonRestartableComposable או @NonSkippableComposable
  • פרמטר נדרש הוא מסוג לא יציב

יש מצב מהדר (compiler) ניסיוני, דילוג חזק, שמפחיתה את הדרישה האחרונה.

כדי שסוג ייחשב ליציב, עליו לעמוד בדרישות הבאות: חוזה:

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

בחוזה הזה יש כמה סוגים נפוצים וחשובים שלפיהם מהדר לכתובים יתייחס ליציבות, גם אם לא נעשה שימוש מפורש מסומן כיציב באמצעות ההערה @Stable:

  • כל סוגי הערכים הראשוניים: Boolean, Int, Long, Float, Char וכו'
  • מיתרים
  • כל סוגי הפונקציות (lambdas)

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

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

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

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

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

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

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