פתרון של בעיות ביציבות

כשמתמודדים עם סיווג לא יציב שגורם לביצועים צריך לוודא שהיא יציבה. במסמך הזה מפורטות כמה שיטות יכולים להשתמש בו כדי לעשות את זה.

הפעלת דילוגים חזקים

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

מידע נוסף זמין במאמר דילוג חזק.

הגדרת הכיתה כ'לא ניתנת לשינוי'

אפשר גם לנסות לגרום לכיתה לא יציבה להיות בלתי ניתנת לשינוי לחלוטין.

  • לא ניתן לשינוי: מציין סוג שבו הערך של נכס לעולם לא יכול להיות קבוע אחרי בניית מופע מהסוג הזה, וכל השיטות שקופים באופן מטעה.
    • צריך לוודא שכל המאפיינים של הכיתה הם גם val ולא var, ומסוגים שלא ניתנים לשינוי.
    • סוגים ראשוניים כמו String, Int ו-Float תמיד לא ניתנים לשינוי.
    • אם זה בלתי אפשרי, צריך להשתמש במצב 'כתיבה' בשביל כל תכונה הניתנת לשינוי.
  • יציב: מציין סוג שניתן לשנות. זמן הריצה של 'פיתוח נייטיב' לא נודע אם וכאשר סוג כלשהו הוא נכסים ציבוריים או method ההתנהגות שלהם תניב תוצאות שונות מהפעלה קודמת.

אוספים בלתי ניתנים לשינוי

אחת הסיבות הנפוצות לכך שכיתה לא יציבה מבחינת 'פיתוח נייטיב' היא קולקציות. כפי שצוין בדף אבחון בעיות ביציבות, בכלי 'מהדר לכתוב' לא יכול להיות בטוח לחלוטין שאוספים כמו List, Map ו-Set שלא ניתן לשנות באמת, ולכן מסמן אותן כלא יציבות.

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

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

unstable class Snack {
  
  unstable val tags: Set<String>
  
}

אפשר להפוך את tags ליציב באמצעות אוסף שלא ניתן לשינוי. שינוי בכיתה מסוג tags עד ImmutableSet<String>:

data class Snack{
    
    val tags: ImmutableSet<String> = persistentSetOf()
    
}

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

הוספת הערות באמצעות Stable או Immutable

דרך אפשרית לפתרון בעיות ביציבות היא להוסיף הערות למחלקות לא יציבות עם @Stable או עם @Immutable.

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

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

קטע הקוד הבא הוא דוגמה מינימלית לסיווג נתונים שמסומן בתור: לא ניתן לשינוי:

@Immutable
data class Snack(

)

בין אם משתמשים בהערה @Immutable או @Stable, המהדר לכתיבה מסמן את המחלקה Snack כיציבה.

כיתות עם הערות באוספים

כדאי לקחת בחשבון תוכן קומפוזבילי שכולל פרמטר מסוג List<Snack>:

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  
  unstable snacks: List<Snack>
  
)

גם אם מוסיפים הערות ל-Snack באמצעות @Immutable, המהדר לכתיבה עדיין יסמן הפרמטר snacks ב-HighlightedSnacks כלא יציב.

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

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

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

קובץ התצורה

אם אין לך בעיה לעמוד לחוזה היציבות ב-codebase שלך, אפשר: אפשר להביע הסכמה לכך שהקולקציות של Kotlin יהיו יציבות על ידי הוספת kotlin.collections.* אל קובץ תצורת היציבות.

אוסף בלתי משתנה

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

@Composable
private fun HighlightedSnacks(
    
    snacks: ImmutableList<Snack>,
    
)

Wrapper

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

@Immutable
data class SnackCollection(
   val snacks: List<Snack>
)

לאחר מכן אפשר להשתמש בו בתור סוג הפרמטר בתוכן הקומפוזבילי.

@Composable
private fun HighlightedSnacks(
    index: Int,
    snacks: SnackCollection,
    onSnackClick: (Long) -> Unit,
    modifier: Modifier = Modifier
)

הפתרון

לאחר ביצוע אחת מהגישות האלה, המהדר של Composer מסמן את HighlightedSnacks קומפוזבילי גם כskippable וגם כ-restartable.

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  stable snacks: ImmutableList<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

במהלך הכתיבה מחדש, התכונה 'כתיבה' יכולה לדלג על HighlightedSnacks אם הקלט השתנה.

קובץ תצורת יציבות

החל מ-Compose Compiler גרסה 1.5.5, קובץ תצורה של מחלקות ל- ניתן לספק יציבות בזמן הקומפילציה. כך אפשר להביא בחשבון כיתות שאין לך שליטה עליהן, כמו כיתות בספרייה רגילה כמו LocalDateTime, כיציבות.

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

// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider kotlin collections stable
kotlin.collections.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>

כדי להפעיל את התכונה הזו, יש להעביר את הנתיב של קובץ התצורה אל חלון הכתיבה אפשרויות מהדר (compiler) .

מגניב

kotlinOptions {
    freeCompilerArgs += [
            "-P",
            "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
                    project.absolutePath + "/compose_compiler_config.conf"
    ]
}

Kotlin

kotlinOptions {
  freeCompilerArgs += listOf(
      "-P",
      "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
      "${project.absolutePath}/compose_compiler_config.conf"
  )
}

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

מספר מודולים

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

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

הפתרון

כדי לפתור את הבעיה, אפשר להשתמש באחת מהדרכים הבאות:

  1. מוסיפים את המחלקות לקובץ התצורה של קומפילר.
  2. מפעילים את המהדר לכתיבה במודולים של שכבות הנתונים, או מתייגים את המחלקות. עם @Stable או @Immutable במקרים הרלוונטיים.
    • לשם כך, צריך להוסיף תלות בכתיבה לשכבת הנתונים. אבל, לפעמים מדובר רק בתלות בסביבת זמן הריצה של Compose ולא Compose-UI
  3. במודול ממשק המשתמש, כוללים את המחלקות של שכבת הנתונים ב-wrapper שספציפי לממשק המשתמש הסוגים.

אותה בעיה מתרחשת גם כשמשתמשים בספריות חיצוניות אם הן לא משתמשות הרכב מהדר (compiler)

לא כל תוכן קומפוזבילי הוא תוכן שניתן לדלג עליו

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

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

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

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