Jetpack פיתוח נייטיב מבוסס על Kotlin. במקרים מסוימים, Kotlin מספקים ניבים שמקלים על כתיבת קוד כתיבה טוב. אם אתם חושבים במודל אחר ובשפת התכנות מתרגמת את השפה הזו לקוטלין, סביר להניח שתחמיצו חלק מהחוזק של 'כתיבה', שקשה להבין את קוד קוטלין שנכתב בצורה אידיומטית. לצבור יותר היכרות עם הסגנון של 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()
מוגדר במקום אחר. אבל אם משתמשים רק
כאן פשוט יותר להגדיר את הפונקציה בתוך השורה
ביטוי:
Button( // ... onClick = { // do something // do something else } ) { /* ... */ }
טראקים נגררים lambda
Kotlin מציעים תחביר מיוחד לקריאה לפונקציות מסדר גבוה יותר שהאחרונה שלהן היא הוא lambda. אם רוצים להעביר ביטוי lambda אפשר להשתמש ב-trailing lambda תחביר. במקום להוסיף את ביטוי lambda בתוך הסוגריים, לאחר מכן. זהו מצב נפוץ בניסוח האוטומטי, לכן צריך להכיר במראה של הקוד.
לדוגמה, הפרמטר האחרון לכל הפריסות, כמו
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") }
התחביר הזה נפוץ למדי במצב 'כתיבה', במיוחד באלמנטים של פריסה כמו
Column
הפרמטר האחרון הוא ביטוי lambda שמגדיר את הפרמטרים
ילדים, והילדים האלה מסומנים בסוגריים מסולסלים אחרי הפעלת הפונקציה.
היקפי הרשאות ומקלטים
חלק מהשיטות והמאפיינים זמינים רק בהיקף מסוים. המוגבלות היקף מאפשר לכם להציע פונקציונליות במקומות שבהם היא נחוצה, ולהימנע להשתמש בפונקציונליות הזו במקומות שבהם היא לא מתאימה.
נבחן דוגמה לשימוש ב'כתיבה'. כשקוראים לפריסה של Row
קומפוזבילי, התוכן lambda מופעל באופן אוטומטי בתוך 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 שנקראים בהיקף המקלט. נורות ה-lambda האלה יכולים לגשת למאפיינים ולפונקציות שמוגדרים במקום אחר, בהתבסס על הצהרת פרמטרים:
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
לרוב, קוד כזה יופיע בפונקציות של הכתיבה:
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, קל להצהיר על Singletons, מחלקות שתמיד יש להן אחד
רק מופע אחד. הסינגלונים האלה מוצהרים באמצעות מילת המפתח object
.
לעיתים קרובות משתמשים באובייקטים כאלה ב-Compose. לדוגמה,
MaterialTheme
הוא
מוגדר כאובייקט סינגלטון; MaterialTheme.colors
, shapes
המאפיינים של typography
מכילים את הערכים של העיצוב הנוכחי.
בנאים ופרוטוקולי 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 ב: המסמכים של קוטלין.
שגרת קוטלין (Kotlin)
Coroutines מציע תמיכה בתכנות אסינכרוני ברמת השפה Kotlin. קורוטינים יכולים להשעות הפעלה בלי לחסום שרשורים. א' ממשק משתמש רספונסיבי הוא מטבעו אסינכרוני, ו-Jetpack פיתוח נייטיב פותר את הבעיה מאמצים קורוטין ברמת ה-API במקום להשתמש בקריאות חוזרות (callback).
ב-Jetpack פיתוח נייטיב יש ממשקי API שהופכים את השימוש בקורוטינים לבטוח בשכבת ממשק המשתמש.
rememberCoroutineScope
הפונקציה מחזירה CoroutineScope
שבעזרתה אפשר ליצור קורוטינים בגורמים מטפלים באירועים ובקריאה
כתיבת ממשקי API של השעיה. אפשר לראות את הדוגמה הבאה בעזרת
של 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) ) }
למידע נוסף על קורוטין, אפשר לעיין המדריך בנושא קורוטין של קוטלין ב-Android.
מומלץ עבורך
- הערה: טקסט הקישור מוצג כאשר JavaScript מושבת
- רכיבים ופריסות של חומר לימוד
- תופעות לוואי ב-Compose
- העקרונות הבסיסיים של הפריסה