CompositionLocal
הוא כלי לכוונון עדין
שמעבירים נתונים דרך היצירה באופן מרומז. הנושאים בדף הזה
כאן אפשר ללמוד בפירוט על CompositionLocal
, איך ליצור תיאור משלך
CompositionLocal
, ולדעת אם CompositionLocal
הוא פתרון טוב
של התרחיש לדוגמה המבוקש.
גאים להציג: CompositionLocal
בדרך כלל, במצב 'כתיבה', הנתונים יורדים דרך עץ UI כפרמטרים לכל פונקציה קומפוזבילית. כך אפשר ליצור תוכן קומפוזבילי או יחסי תלות מפורשים. עם זאת, התהליך הזה יכול להיות מסורבל עבור נתונים נפוצים ונפוצים מאוד, למשל צבעים או סגנונות של סוגים. כדאי לעיין בנושאים הבאים דוגמה:
@Composable fun MyApp() { // Theme information tends to be defined near the root of the application val colors = colors() } // Some composable deep in the hierarchy @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, color = colors.onPrimary // ← need to access colors here ) }
כדי לתמוך בכך שלא יהיה צורך להעביר את הצבעים כתלות מפורשת של פרמטר
את רוב התכנים הקומפוזביליים, כתיבת הצעות כתיבה CompositionLocal
מאפשרת
כדי ליצור אובייקטים בעלי שם ברמת עץ שיכולים לשמש כדרך מרומזת
עוברים דרך עץ ממשק המשתמש.
רכיבי CompositionLocal
בדרך כלל מסופקים עם ערך בצומת מסוים
של עץ ממשק המשתמש. הצאצאים הקומפוזביליים שלו יכולים להשתמש בערך הזה בלי
הצהרה על CompositionLocal
כפרמטר בפונקציה הקומפוזבילית.
CompositionLocal
הוא היישום המרכזי של העיצוב Material.
MaterialTheme
הוא
אובייקט שמספק שלוש מופעים של CompositionLocal
– צבעים, טיפוגרפיה
והצורות - כך שאפשר לאחזר אותן מאוחר יותר בכל חלק צאצא של
יצירה מוזיקלית. באופן ספציפי, אלו הם LocalColors
, LocalShapes
LocalTypography
נכסים שניתן לגשת אליהם דרך MaterialTheme
colors
, shapes
ו-typography
.
@Composable fun MyApp() { // Provides a Theme whose values are propagated down its `content` MaterialTheme { // New values for colors, typography, and shapes are available // in MaterialTheme's content lambda. // ... content here ... } } // Some composable deep in the hierarchy of MaterialTheme @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, // `primary` is obtained from MaterialTheme's // LocalColors CompositionLocal color = MaterialTheme.colors.primary ) }
מופע CompositionLocal
בהיקף של חלק מהיצירה מוזיקלית, כך
יכול לספק ערכים שונים ברמות שונות של העץ. הערך current
של CompositionLocal
תואם לערך הקרוב ביותר שסופק על ידי
בישות האב בחלק הזה של היצירה.
כדי לציין ערך חדש ל-CompositionLocal
, צריך להשתמש בפונקציה
CompositionLocalProvider
ו-provides
הפונקציה הקבועה שמשייכת מפתח CompositionLocal
אל value
.
lambda אחד (content
) מתוך CompositionLocalProvider
יקבל
כשניגשים למאפיין current
של CompositionLocal
. כאשר
סופק ערך חדש, התכונה 'פיתוח נייטיב' מורכבת מחדש מחלקים ביצירה שקוראים
CompositionLocal
.
לדוגמה, CompositionLocal
של LocalContentAlpha
מכיל את אלפא התוכן המועדף שמשמש לטקסט וגם
איקונוגרפיה במטרה להדגיש או להסיר הדגשה של חלקים שונים בממשק המשתמש. ב
בדוגמה הבאה, CompositionLocalProvider
משמש כדי לתת
לערכים שונים של היצירה המוזיקלית.
@Composable fun CompositionLocalExample() { MaterialTheme { // MaterialTheme sets ContentAlpha.high as default Column { Text("Uses MaterialTheme's provided alpha") CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { Text("Medium value provided for LocalContentAlpha") Text("This Text also uses the medium value") CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) { DescendantExample() } } } } } @Composable fun DescendantExample() { // CompositionLocalProviders also work across composable functions Text("This Text uses the disabled alpha now") }
איור 1. תצוגה מקדימה של התוכן הקומפוזבילי CompositionLocalExample
.
בכל הדוגמאות שלמעלה, המופעים CompositionLocal
היו בשימוש פנימי
לפי חומרים קומפוזביליים. כדי לגשת לערך הנוכחי של CompositionLocal
,
להשתמש ב-current
לנכס. בדוגמה הבאה, הערך הנוכחי Context
של LocalContext
CompositionLocal
שנמצא בשימוש נפוץ באפליקציות ל-Android משמש כדי לקבוע את פורמט
הטקסט:
@Composable fun FruitText(fruitSize: Int) { // Get `resources` from the current value of LocalContext val resources = LocalContext.current.resources val fruitText = remember(resources, fruitSize) { resources.getQuantityString(R.plurals.fruit_title, fruitSize) } Text(text = fruitText) }
המערכת יוצרת CompositionLocal
משלך
CompositionLocal
הוא כלי להעברת נתונים למטה דרך היצירה
בצורה מרומזת.
אות מפתח נוסף לשימוש ב-CompositionLocal
הוא כשהפרמטר
חיתוך ושכבות ביניים של הטמעה לא צריכות להיות מודעים
הוא קיים, כי מודעות לשכבות הביניים האלה תגביל את
של התוכן הקומפוזבילי. לדוגמה, שליחת שאילתות לגבי הרשאות ב-Android
בכיסוי של CompositionLocal
. כלי קומפוזבילי בכלי לבחירת מדיה
יכולים להוסיף פונקציונליות חדשה כדי לגשת לתוכן המוגן בזכויות יוצרים
בלי לשנות את ה-API שלו ובלי לדרוש מהמתקשרים מהכלי לבחירת מדיה
להיות מודעים להקשר הנוסף הזה שבו משתמשים מהסביבה.
עם זאת, CompositionLocal
הוא לא תמיד הפתרון הטוב ביותר. רביעי
לא מומלץ להשתמש יתר ב-CompositionLocal
כי יש כמה חסרונות:
CompositionLocal
מקשה על ההיגיון בהתנהגות של תוכן קומפוזבילי. בתור
הם יוצרים יחסי תלות מרומזים, קוראים של תכנים קומפוזביליים שמשתמשים בהם
כדי לוודא שערך לכל CompositionLocal
מתקיים.
בנוסף, ייתכן שאין מקור ברור של אמיתות לגבי התלות הזאת,
יכולים להשתנות בכל חלק ביצירה. לכן, צריך לנפות באגים באפליקציה כאשר
יכולה להיות מאתגרת יותר, כי תצטרכו לנווט
יצירה מוזיקלית כדי לראות איפה צוין הערך current
. כלים כמו Finder
משתמשים בסביבת הפיתוח המשולבת (IDE) או בכלי לבדיקת פריסת הכתיבה מספקים מספיק מידע כדי
לצמצם את הבעיה.
ההחלטה אם להשתמש ב-CompositionLocal
יש תנאים מסוימים שיכולים להפוך את CompositionLocal
לפתרון טוב
במיוחד למקרה כזה:
ל-CompositionLocal
צריך להיות ערך ברירת מחדל טוב. אם לא מוגדרת ברירת מחדל
עליך להבטיח שלמפתח יהיה קשה מאוד
במצבים שבהם לא ניתן ערך עבור CompositionLocal
.
אם לא מציינים ערך ברירת מחדל, זה עלול לגרום לבעיות ולתסכול במהלך היצירה
או צופים בתצוגה מקדימה של תוכן קומפוזבילי שמשתמש ב-CompositionLocal
תמיד
נדרש לציין זאת באופן מפורש.
יש להימנע משימוש ב-CompositionLocal
עבור מושגים שלא נחשבים ברמת עץ או
היררכיית משנה בהיקף. CompositionLocal
הגיוני כשאפשר
יכול להיות בשימוש על ידי כל הצאצאים, ולא על ידי כמה מהם.
אם התרחיש לדוגמה שלכם לא עומד בדרישות האלה, כדאי לבדוק את
קטע חלופות שכדאי לבדוק לפני שיוצרים
CompositionLocal
.
דוגמה לשיטה לא מומלצת היא יצירה של CompositionLocal
שכולל את
ViewModel
של מסך מסוים, כך שכל התכנים הקומפוזביליים במסך הזה
מקבלים הפניה אל ViewModel
כדי לבצע לוגיקה. שיטה זו לא מומלצת
בגלל שלא כל התכנים הקומפוזביליים שמתחת לעץ ממשק משתמש מסוים צריכים לדעת
ViewModel
השיטה המומלצת היא להעביר לתכנים קומפוזביליים רק את המידע
שהם צריכים לפי הדפוס המצביע למטה והאירועים זורמים למעלה. הגישה הזו תשפר את התכנים הקומפוזביליים
לשימוש חוזר וקל יותר לבדיקה.
יצירת CompositionLocal
צריך ליצור שני ממשקי API כדי ליצור CompositionLocal
:
compositionLocalOf
: שינוי הערך שצוין במהלך הרכבה מחדש לא תקף רק את התוכן שכתוב בוcurrent
עם ערך מסוים.staticCompositionLocalOf
: שלא כמוcompositionLocalOf
, הקריאות שלstaticCompositionLocalOf
אינן במעקב באמצעות 'כתיבה'. שינוי הערך גורם להצגה המלאה שלcontent
lambda שבהCompositionLocal
מסופק להרכבת מחדש, במקום רק המקומות שבהם יש לקרוא את הערךcurrent
ביצירה.
אם לא סביר שהערך שצוין ל-CompositionLocal
ישתנה או
לא ישתנה אף פעם. יש להשתמש במדד staticCompositionLocalOf
כדי ליהנות מיתרונות הביצועים.
לדוגמה, מערכת העיצוב של אפליקציה עשויה להיות מקובעות באופן שבו תכנים קומפוזביליים
מוגבהים באמצעות צל של רכיב ממשק המשתמש. מאחר שהמודל
הגבהים של האפליקציה צריכות להתפשט בעץ ממשק המשתמש, אנחנו משתמשים
CompositionLocal
מאחר שהערך של CompositionLocal
נגזר באופן מותנה
בהתאם לעיצוב של המערכת, אנחנו משתמשים ב-API של compositionLocalOf
:
// LocalElevations.kt file data class Elevations(val card: Dp = 0.dp, val default: Dp = 0.dp) // Define a CompositionLocal global object with a default // This instance can be accessed by all composables in the app val LocalElevations = compositionLocalOf { Elevations() }
מספקים ערכים ל-CompositionLocal
הCompositionLocalProvider
תוכן קומפוזבילי מקשר בין ערכים ל-CompositionLocal
מופעים של הערך שצוין
ההיררכיה. כדי לציין ערך חדש ל-CompositionLocal
, משתמשים בפונקציה
provides
הפונקציה infix שמשייכת מפתח CompositionLocal
ל-value
באופן הבא:
// MyActivity.kt file class MyActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // Calculate elevations based on the system theme val elevations = if (isSystemInDarkTheme()) { Elevations(card = 1.dp, default = 1.dp) } else { Elevations(card = 0.dp, default = 0.dp) } // Bind elevation as the value for LocalElevations CompositionLocalProvider(LocalElevations provides elevations) { // ... Content goes here ... // This part of Composition will see the `elevations` instance // when accessing LocalElevations.current } } } }
צריכת CompositionLocal
הפונקציה CompositionLocal.current
מחזירה את הערך שסופק על ידי CompositionLocalProvider
הקרוב ביותר שמספק ערך לאותו CompositionLocal
:
@Composable fun SomeComposable() { // Access the globally defined LocalElevations variable to get the // current Elevations in this part of the Composition Card(elevation = LocalElevations.current.card) { // Content } }
אפשרויות אחרות שכדאי לבדוק
CompositionLocal
יכול להיות פתרון מוגזם לתרחישים לדוגמה מסוימים. אם
לא עומד בקריטריונים שמפורטים בקטע ההחלטה אם להשתמש
בקטע CompositionLocal, יכול להיות שפתרון אחר יהיה טוב יותר.
שמתאים לתרחיש לדוגמה שלכם.
העברה של פרמטרים מפורשים
גילוי מפורש לגבי יחסי תלות של תוכן קומפוזבילי הוא הרגל טוב. ההמלצות שלנו שאתם מעבירים תכנים קומפוזביליים רק את מה שהם צריכים. כדי לעודד הפרדה ושימוש חוזר בחומרים קומפוזביליים, כל תוכן קומפוזבילי צריך להכיל את הכמות הקטנה ביותר של עד כמה שניתן.
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel.data) } // Don't pass the whole object! Just what the descendant needs. // Also, don't pass the ViewModel as an implicit dependency using // a CompositionLocal. @Composable fun MyDescendant(myViewModel: MyViewModel) { /* ... */ } // Pass only what the descendant needs @Composable fun MyDescendant(data: DataToDisplay) { // Display data }
היפוך הבקרה
דרך נוספת להימנע מהעברת יחסי תלות מיותרים לתוכן קומפוזבילי היא באמצעות היפוך שליטה. במקום שצאצא יקבל תלות מפעילים לוגיקה כלשהי, ההורה עושה את זה.
בדוגמה הבאה אפשר לראות שצאצא צריך להפעיל את הבקשה כדי: לטעון נתונים מסוימים:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
בהתאם למקרה, ייתכן שבאחריות של MyDescendant
תהיה אחריות רבה. כמו כן,
העברת MyViewModel
כי התלות הופכת את MyDescendant
לשימוש חוזר פחות,
עכשיו הם ביחד. כדאי לשקול חלופה שלא עוברת את
של התלות בצאצא ושימוש בעקרונות ההיפוך של הבקרה
שגורם לישות האב שאחראית לביצוע הלוגיקה:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
גישה זו יכולה להתאים יותר לתרחישי שימוש מסוימים, מכיוון שהיא פותרת את צאצא מאבותיו המיידיות. תכנים קומפוזביליים אבותיים נוטים להיות מורכבים יותר, כדי ליצור תכנים קומפוזביליים גמישים יותר ברמה נמוכה.
באופן דומה, ניתן להשתמש ב-@Composable
בתוכן lambdas באותו אופן כדי לקבל
אותם יתרונות:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
מומלץ עבורך
- הערה: טקסט הקישור מוצג כאשר JavaScript מושבת
- המבנה של עיצוב בחלונית 'כתיבה'
- שימוש בתצוגות מפורטות בהצעות לכתיבה
- Kotlin ל-Jetpack פיתוח נייטיב