Jetpack פיתוח נייטיב מבוסס על Kotlin. במקרים מסוימים, ב-Kotlin יש ביטויים מיוחדים שמקלים על כתיבת קוד טוב ב-Compose. אם אתם חושבים במודל אחר ובשפת התכנות מתרגמת את השפה הזו לקוטלין, סביר להניח שתחמיצו חלק מהחוזק של 'כתיבה', שקשה להבין את קוד קוטלין שנכתב בצורה אידיומטית. כדי להימנע מהמלכודות האלה, מומלץ להכיר טוב יותר את הסגנון של Kotlin.
ארגומנטים של ברירת מחדל
כשכותבים פונקציה ב-Kotlin, אפשר לציין ערכים שמוגדרים כברירת מחדל לארגומנטים של הפונקציה, שייעשה בהם שימוש אם מבצע הקריאה לא מעביר את הערכים האלה באופן מפורש. התכונה הזו גורמת לצמצום לצורך בפונקציות עמוסות.
לדוגמה, נניח שרוצים לכתוב פונקציה שמשרטטת ריבוע. יכול להיות לפונקציה הזו פרמטר נדרש אחד, sideLength, שמציין את האורך של כל צלע. הפרמטר עשוי לכלול כמה פרמטרים אופציונליים, כמו עובי, EdgeColor וכו'; אם המתקשר לא מציין את הפרטים האלה, הפונקציה משתמשת בערכי ברירת מחדל. בשפות אחרות, יכול להיות שתצטרכו לכתוב כמה פונקציות:
// We don't need to do this in Kotlin! void drawSquare(int sideLength) { } void drawSquare(int sideLength, int thickness) { } void drawSquare(int sideLength, int thickness, Color edgeColor) { }
ב-Kotlin אפשר לכתוב פונקציה אחת בלבד ולציין את ערכי ברירת המחדל שלה את הארגומנטים:
fun drawSquare( sideLength: Int, thickness: Int = 2, edgeColor: Color = Color.Black ) { }
בנוסף לחיסכון בצורך לכתוב כמה פונקציות מיותרות, התכונה הזו מאפשרת לקרוא את הקוד בצורה ברורה יותר. אם המתקשר לא מציין
של ארגומנט, שמציין שהוא מוכן להשתמש בברירת המחדל
עם ערך מסוים. בנוסף, הפרמטרים עם השמות עוזרים לכם להבין מה קורה בקלות רבה יותר. אם תסתכלו בקוד ותראו בקשה להפעלת פונקציה כזו, יכול להיות שלא
ניתן לדעת מה המשמעות של הפרמטרים בלי לבדוק את הקוד drawSquare()
:
drawSquare(30, 5, Color.Red);
לעומת זאת, הקוד הזה מבצע תיעוד עצמי:
drawSquare(sideLength = 30, thickness = 5, edgeColor = Color.Red)
רוב ספריות הכתיבה משתמשות בארגומנטים שמוגדרים כברירת מחדל, ומומלץ להשתמש זהים לפונקציות הקומפוזביליות שאתם כותבים. כך תוכלו להתאים אישית את הרכיבים הניתנים לשילוב, אבל עדיין להפעיל בקלות את התנהגות ברירת המחדל. לדוגמה, אפשר ליצור רכיב טקסט פשוט כך:
Text(text = "Hello, Android!")
לקוד הזה יש את אותו אפקט כמו לקוד הבא, ארוך יותר, שבו מוגדר באופן מפורש מספר גדול יותר של הפרמטרים של Text
:
Text( text = "Hello, Android!", color = Color.Unspecified, fontSize = TextUnit.Unspecified, letterSpacing = TextUnit.Unspecified, overflow = TextOverflow.Clip )
קטע הקוד הראשון לא רק פשוט יותר וקל יותר לקריאה, אלא גם מתעד את עצמו. כשמציינים רק את הפרמטר text
, מתועדת העובדה שרוצים להשתמש בערכי ברירת המחדל לכל הפרמטרים האחרים. לעומת זאת, הקטע השני מרמז שאתם רוצים להגדיר באופן מפורש את הערכים של הפרמטרים האחרים האלה, למרות שהערכים שהגדרתם הם ערכי ברירת המחדל של הפונקציה.
פונקציות מסדר גבוה יותר וביטויי למבדה
Kotlin תומך בסדר גבוה יותר
פונקציות,
מקבלים פונקציות אחרות כפרמטרים. פיתוח הכתיבה מתבסס על הגישה הזו. לדוגמה, הפונקציה הניתנת לקישור Button
מספקת פרמטר lambda onClick
. הערך
של הפרמטר הזה הוא פונקציה, שהלחצן קורא לו כשהמשתמש לוחץ עליו:
Button( // ... onClick = myClickFunction ) // ...
פונקציות מסדר גבוה יותר מתאימות באופן טבעי לביטויי למדא, ביטויים שמתקבל מהם ערך של פונקציה. אם צריך את הפונקציה רק פעם אחת, אין צורך
להגדיר אותו במקום אחר כדי להעביר אותו לפונקציה בסדר גבוה יותר. במקום זאת, אפשר פשוט להגדיר את הפונקציה במקום באמצעות ביטוי lambda. בדוגמה הקודמת, ההנחה היא ש-myClickFunction()
מוגדר במקום אחר. אבל אם משתמשים בפונקציה הזו רק כאן, קל יותר פשוט להגדיר את הפונקציה בתוך שורת הקוד באמצעות ביטוי lambda:
Button( // ... onClick = { // do something // do something else } ) { /* ... */ }
פונקציות lambda בסוף
Kotlin מציעים תחביר מיוחד לקריאה לפונקציות מסדר גבוה יותר שהאחרונה שלהן היא הוא lambda. אם רוצים להעביר ביטוי lambda אפשר להשתמש ב-trailing lambda תחביר. במקום להציב את ביטוי הלמה בתוך הסוגריים, צריך להציב אותו אחריהם. זוהי תופעה נפוצה ב-Compose, לכן חשוב להכיר את המראה של הקוד.
לדוגמה, הפרמטר האחרון לכל הפריסות, כמו הפונקציה הניתנת ליצירה Column()
, הוא content
, פונקציה שמפיקה את רכיבי ממשק המשתמש הצאצאים. נניח שרוצים ליצור עמודה שמכילה שלושה רכיבי טקסט, וצריכים להחיל פורמט מסוים. הקוד הזה יפעל, אבל הוא מאוד מסורבל:
Column( modifier = Modifier.padding(16.dp), content = { Text("Some text") Text("Some more text") Text("Last text") } )
כי הפרמטר content
הוא הפרמטר האחרון בחתימת הפונקציה, וגם
אנחנו מעבירים את הערך שלו כביטוי למבדה, אנחנו יכולים להוציא אותו
סוגריים:
Column(modifier = Modifier.padding(16.dp)) { Text("Some text") Text("Some more text") Text("Last text") }
לשתי הדוגמאות יש בדיוק את אותה משמעות. המסגרת מסמלת את ה-lambda
ביטוי שמועבר לפרמטר content
.
למעשה, אם הפרמטר היחיד שמעבירים הוא lambda בסוף, כלומר,
אם הפרמטר הסופי הוא lambda ואתם לא מעבירים
פרמטרים — אפשר להשמיט את הסוגריים לגמרי. לדוגמה, נניח שלא צריך להעביר שינוי ל-Column
. אפשר לכתוב את הקוד, למשל
הזה:
Column { Text("Some text") Text("Some more text") Text("Last text") }
התחביר הזה נפוץ למדי ב-Compose, במיוחד לגבי רכיבי פריסה כמו Column
. הפרמטר האחרון הוא ביטוי lambda שמגדיר את הצאצאים של הרכיב, והצאצאים האלה מצוינים בסוגריים מסולסלים אחרי קריאת הפונקציה.
היקפי הרשאות ונמענים
חלק מהשיטות והמאפיינים זמינים רק בהיקף מסוים. המוגבלות היקף מאפשר לכם להציע פונקציונליות במקומות שבהם היא נחוצה, ולהימנע להשתמש בפונקציונליות הזו במקומות שבהם היא לא מתאימה.
נבחן דוגמה לשימוש ב-Compose. כשאתם קוראים ל-layout composable של Row
, פונקציית הלוגריתם של התוכן מופעלת באופן אוטומטי בתוך RowScope
.
כך אפשר לחשוף ב-Row
פונקציונליות שתקפה רק ב-Row
.
בדוגמה הבאה מוצג איך Row
חשף ערך ספציפי לשורה של המשתנה המשנה align
:
Row { Text( text = "Hello world", // This Text is inside a RowScope so it has access to // Alignment.CenterVertically but not to // Alignment.CenterHorizontally, which would be available // in a ColumnScope. modifier = Modifier.align(Alignment.CenterVertically) ) }
חלק מממשקי ה-API מקבלים lambdas שנקראות בהיקף המקלט. לפונקציות הלמה יש גישה למאפיינים ולפונקציות שמוגדרים במקום אחר, על סמך הצהרת הפרמטר:
Box( modifier = Modifier.drawBehind { // This method accepts a lambda of type DrawScope.() -> Unit // therefore in this lambda we can access properties and functions // available from DrawScope, such as the `drawRectangle` function. drawRect( /*...*/ /* ... ) } )
למידע נוסף, ראו ליטרלים של פונקציות המקבל בתיעוד של Kotlin.
נכסים שהוקצו
ב-Kotlin יש תמיכה במאפיינים שהועברו.
הנכסים האלה נקראים כאילו הם שדות, אבל הערך שלהם נקבע באופן דינמי על ידי הערכת ביטוי. אפשר לזהות את המאפיינים האלה לפי השימוש שלהם בתחביר by
:
class DelegatingClass { var name: String by nameGetterFunction() // ... }
קוד אחר יכול לגשת למאפיין באמצעות קוד כמו:
val myDC = DelegatingClass() println("The name property is: " + myDC.name)
כשהערך של println()
מתבצע, נשלחת קריאה לפונקציה nameGetterFunction()
כדי להחזיר את הערך
של המחרוזת.
הנכסים המואצלים האלה שימושיים במיוחד כשעובדים עם נכסים בגיבוי מצב:
var showDialog by remember { mutableStateOf(false) } // Updating the var automatically triggers a state change showDialog = true
פירוק מחלקות נתונים
אם מגדירים class של נתונים, אפשר לגשת בקלות לנתונים באמצעות הצהרה של ניתוח מבנה. לדוגמה, נניח שמגדירים את הכיתה Person
:
data class Person(val name: String, val age: Int)
אם יש לכם אובייקט מהסוג הזה, תוכלו לגשת לערכים שלו באמצעות קוד כמו זה:
val mary = Person(name = "Mary", age = 35) // ... val (name, age) = mary
לרוב, תוכלו לראות קוד כזה בפונקציות של Compose:
Row { val (image, title, subtitle) = createRefs() // The `createRefs` function returns a data object; // the first three components are extracted into the // image, title, and subtitle variables. // ... }
יש לכיתות הנתונים הרבה פונקציונליות שימושית נוספת. לדוגמה,
מגדיר סיווג נתונים, המהדר מגדיר באופן אוטומטי פונקציות שימושיות כמו
equals()
וגם copy()
מידע נוסף זמין בקטע נתונים
של הכיתות.
אובייקטים מסוג Singleton
ב-Kotlin קל להצהיר על מונותים, כלומר על כיתות שיש להן תמיד מופע אחד בלבד. המודולים האלה מוצהרים באמצעות מילת המפתח object
.
לעיתים קרובות נעשה שימוש באובייקטים כאלה ב-Compose. לדוגמה, MaterialTheme
מוגדר כאובייקט יחיד (singleton). המאפיינים MaterialTheme.colors
, shapes
ו-typography
מכילים את הערכים של העיצוב הנוכחי.
בוני DSL ו-DSL בטוחים לסוגים
Kotlin מאפשרים ליצור שפות ספציפיות לדומיין (DSL) בעזרת builderים בטוחים לסוג. DSL מאפשרים לבנות נתונים היררכיים מורכבים את המבנה שלהם בצורה קריאה וקריאה יותר.
Jetpack פיתוח נייטיב משתמש ב-DSL עבור ממשקי API מסוימים כמו
LazyRow
ו-LazyColumn
.
@Composable fun MessageList(messages: List<Message>) { LazyColumn { // Add a single item as a header item { Text("Message List") } // Add list of messages items(messages) { message -> Message(message) } } }
ב-Kotlin אפשר להבטיח בוני טיפים בטוחים באמצעות פונקציות לינאריות עם מקלט.
אם ניקח את Canvas
קומפוזבילי, לדוגמה, הוא לוקח כפרמטר עם פונקציה
DrawScope
בתור המקבל, onDraw: DrawScope.() -> Unit
, מה שמאפשר לקטע הקוד
קריאה לפונקציות חברוּת שמוגדרות ב-DrawScope
.
Canvas(Modifier.size(120.dp)) { // Draw grey background, drawRect function is provided by the receiver drawRect(color = Color.Gray) // Inset content by 10 pixels on the left/right sides // and 12 by the top/bottom inset(10.0f, 12.0f) { val quadrantSize = size / 2.0f // Draw a rectangle within the inset bounds drawRect( size = quadrantSize, color = Color.Red ) rotate(45.0f) { drawRect(size = quadrantSize, color = Color.Blue) } } }
מידע נוסף על בוני DSL ו-DSL ללא שגיאות בטיחות סוג זמין במסמכי התיעוד של Kotlin.
שגרת קוטלין (Kotlin)
קורוטינים מספקים תמיכה בתכנות אסינכרוני ברמת השפה ב-Kotlin. קורוטינים יכולים להשעות הפעלה בלי לחסום שרשורים. א' ממשק משתמש רספונסיבי הוא מטבעו אסינכרוני, ו-Jetpack פיתוח נייטיב פותר את הבעיה מאמצים קורוטין ברמת ה-API במקום להשתמש בקריאות חוזרות (callback).
ב-Jetpack פיתוח נייטיב יש ממשקי API שהופכים את השימוש בקורוטינים לבטוח בשכבת ממשק המשתמש.
הפונקציה rememberCoroutineScope
מחזירה CoroutineScope
שבעזרתו אפשר ליצור פונקציות רפיטיביות בטיפולי אירועים ולקרוא לממשקי API של השהיה ב-Compose. אפשר לראות את הדוגמה הבאה בעזרת
של ScrollState
API של animateScrollTo
.
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... onClick = { // Create a new coroutine that scrolls to the top of the list // and call the ViewModel to load data composableScope.launch { scrollState.animateScrollTo(0) // This is a suspend function viewModel.loadData() } } ) { /* ... */ }
שערים מריצים את בלוק הקוד ברצף כברירת מחדל. A ריצה
שקוראת לפונקציית השעיה משהה את הביצוע שלה עד
הפונקציה משעה מחזירה את הפונקציה. הדבר נכון גם אם פונקציית ההשעיה מזיזה את
ב-CoroutineDispatcher
אחר. בדוגמה הקודמת, הפונקציה loadData
לא תבוצע עד שהפונקציה להשהיה animateScrollTo
תחזיר תשובה.
כדי להריץ קוד בו-זמנית, צריך ליצור קורוטינים חדשים. בדוגמה שלמעלה, כדי לבצע במקביל את הגלילה לחלק העליון של המסך ואת טעינת הנתונים מ-viewModel
, נדרשים שני קורוטינים.
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... onClick = { // Scroll to the top and load data in parallel by creating a new // coroutine per independent work to do composableScope.launch { scrollState.animateScrollTo(0) } composableScope.launch { viewModel.loadData() } } ) { /* ... */ }
קורוטינים מקלים על השילוב של ממשקי API אסינכרוניים. בדוגמה הבאה משלבים את המשתנה המשנה pointerInput
עם ממשקי ה-API של האנימציה כדי ליצור אנימציה של מיקום רכיב כשהמשתמש מקשקש על המסך.
@Composable fun MoveBoxWhereTapped() { // Creates an `Animatable` to animate Offset and `remember` it. val animatedOffset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } Box( // The pointerInput modifier takes a suspend block of code Modifier .fillMaxSize() .pointerInput(Unit) { // Create a new CoroutineScope to be able to create new // coroutines inside a suspend function coroutineScope { while (true) { // Wait for the user to tap on the screen val offset = awaitPointerEventScope { awaitFirstDown().position } // Launch a new coroutine to asynchronously animate to // where the user tapped on the screen launch { // Animate to the pressed position animatedOffset.animateTo(offset) } } } } ) { Text("Tap anywhere", Modifier.align(Alignment.Center)) Box( Modifier .offset { // Use the animated offset as the offset of this Box IntOffset( animatedOffset.value.x.roundToInt(), animatedOffset.value.y.roundToInt() ) } .size(40.dp) .background(Color(0xff3c1361), CircleShape) ) }
מידע נוסף על קורוטינים זמין במדריך קורוטינים ב-Kotlin ב-Android.
אין המלצות כרגע.
אפשר לנסות להיכנס לחשבון Google.