כתיבה וספריות אחרות

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

פעילות

כדי להשתמש ב'כתיבה' בפעילות, צריך להשתמש ב-ComponentActivity, מחלקה של Activity שמספקת את LifecycleOwner ואת הרכיבים המתאימים לכתיבה. הוא גם מספק ממשקי API נוספים שמפרידים בין הקוד שלכם לבין שינוי שיטות בסיווג הפעילות. Activity Compose חושף את ממשקי ה-API האלה לרכיבים הניתנים ליצירה, כך שכבר לא צריך לשנות את שיטות ה-override מחוץ לרכיבים הניתנים ליצירה או לאחזר מכונה מפורשת של Activity. בנוסף, ממשקי ה-API האלה מבטיחים שהם יופעלו רק פעם אחת, יעברו עיבוד מחדש ויתבצע ניקוי תקין אם הרכיב הניתן ליצירה יוסר מההרכב.

תוצאת פעילות

ה-API של rememberLauncherForActivityResult() מאפשר לקבל תוצאה מפעילות ב-composable:

@Composable
fun GetContentExample() {
    var imageUri by remember { mutableStateOf<Uri?>(null) }
    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
        imageUri = uri
    }
    Column {
        Button(onClick = { launcher.launch("image/*") }) {
            Text(text = "Load Image")
        }
        Image(
            painter = rememberAsyncImagePainter(imageUri),
            contentDescription = "My Image"
        )
    }
}

בדוגמה הזו מוצג חוזה GetContent() פשוט. הקשה על הלחצן מפעילה את הבקשה. ה-lambda הסופי של rememberLauncherForActivityResult() מופעל ברגע שהמשתמש בוחר תמונה וחוזר לפעילות ההפעלה. הפונקציה הזו טוענת את התמונה שנבחרה באמצעות הפונקציה rememberImagePainter() של Coil.

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

שליחת בקשה להרשאות בזמן ריצה

אפשר להשתמש באותו Activity Result API וב-rememberLauncherForActivityResult() שמתוארים למעלה כדי לבקש הרשאות בסביבת זמן הריצה באמצעות החוזה RequestPermission להרשאה אחת או החוזה RequestMultiplePermissions לכמה הרשאות.

אפשר גם להשתמש בספריית ההרשאות של Accompanist בשכבה מעל ממשקי ה-API האלה כדי למפות את המצב הנוכחי של ההרשאות שהוקצו למצב שאפשר להשתמש בו בממשק המשתמש של Compose.

טיפול בלחצן 'הקודם' במערכת

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

var backHandlingEnabled by remember { mutableStateOf(true) }
BackHandler(backHandlingEnabled) {
    // Handle back press
}

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

ViewModel

אם משתמשים בספרייה Architecture Components ViewModel, אפשר לגשת לפונקציה ViewModel מכל תוכן קומפוזבילי על ידי קריאה לפונקציה viewModel(). מוסיפים את יחסי התלות הבאים לקובץ Gradle:

Groovy

dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5'
}

Kotlin

dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5")
}

לאחר מכן תוכלו להשתמש בפונקציה viewModel() בקוד.

class MyViewModel : ViewModel() { /*...*/ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    // use viewModel here
}

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

לדוגמה, אם משתמשים ב-composable בפעילות, viewModel() מחזיר את אותה מופע עד שהפעילות מסתיימת או שהתהליך מושמד.

class MyViewModel : ViewModel() { /*...*/ }
// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    // Returns the same instance as long as the activity is alive,
    // just as if you grabbed the instance from an Activity or Fragment
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

@Composable
fun MyScreen2(
    viewModel: MyViewModel = viewModel() // Same instance as in MyScreen
) { /* ... */ }

הנחיות לשימוש

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

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

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

אם ל-ViewModel יש יחסי תלות, viewModel() מקבל פרמטר אופציונלי של ViewModelProvider.Factory.

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

מקורות נתונים

ההצעות לכתיבה כוללות תוספים שמתאימים לפתרונות הכי פופולריים של Android שמבוססים על שידורים. כל אחד מהתוספים הבאים מסופק על ידי פריט מידע שנוצר בתהליך פיתוח (Artifact) אחר:

  • LiveData.observeAsState() נכלל ב-Artifact של androidx.compose.runtime:runtime-livedata:$composeVersion.
  • Flow.collectAsState() לא מחייב יחסי תלות נוספים.
  • Observable.subscribeAsState() נכלל בפריטי מידע שנוצרו בתהליך הפיתוח (Artifact) מסוג androidx.compose.runtime:runtime-rxjava2:$composeVersion או androidx.compose.runtime:runtime-rxjava3:$composeVersion.

ארטיפקטים האלה נרשמים כ-listener ומייצגים את הערכים בתור State. בכל פעם שמופיע ערך חדש, Compose יוצר מחדש את החלקים בממשק המשתמש שבהם state.value משמש. לדוגמה, בקוד הזה, ShowData מרכיב מחדש בכל פעם ש-exampleLiveData פולט ערך חדש.

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val dataExample = viewModel.exampleLiveData.observeAsState()

    // Because the state is read here,
    // MyScreen recomposes whenever dataExample changes.
    dataExample.value?.let {
        ShowData(dataExample)
    }
}

פעולות אסינכרוניות ב-Compose

ב-Jetpack Compose אפשר להריץ פעולות אסינכרוניות באמצעות קורוטינים מתוך הרכיבים הניתנים להחלפה.

מידע נוסף זמין בממשקי ה-API LaunchedEffect,‏ produceState ו-rememberCoroutineScope במאמר תיעוד לגבי תופעות לוואי.

רכיב הניווט מספק תמיכה באפליקציות של Jetpack פיתוח נייטיב. למידע נוסף, ראו ניווט באמצעות 'כתיבה' ו-העברת ניווט ב-Jetpack ל'פיתוח נייטיב'.

ידית

Hilt הוא הפתרון המומלץ להחדרת תלות באפליקציות ל-Android, והוא פועל בצורה חלקה עם 'כתיבה'.

הפונקציה viewModel() שצוינה בקטע ViewModel משתמשת באופן אוטומטי ב-ViewModel שנוצר על ידי Hilt באמצעות ההערה @HiltViewModel. בהמשך תמצאו מסמכים עם מידע על האינטגרציה של Hilt's ViewModel.

@HiltViewModel
class MyViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    private val repository: ExampleRepository
) : ViewModel() { /* ... */ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

Hilt ו-Navigation

Hilt משתלב גם עם ספריית Navigation Compose. מוסיפים את יחסי התלות הנוספים הבאים לקובץ Gradle:

מגניב

dependencies {
    implementation 'androidx.hilt:hilt-navigation-compose:1.2.0'
}

Kotlin

dependencies {
    implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
}

כשמשתמשים בניסוח ניווט, תמיד צריך להשתמש בפונקציה הקומפוזבילית hiltViewModel כדי לקבל מופע של ViewModel עם ההערה @HiltViewModel. אפשר להשתמש באפשרות הזו עם קטעים או פעילויות שמסומנים ב-@AndroidEntryPoint.

לדוגמה, אם ExampleScreen הוא יעד בתרשים ניווט, צריך להפעיל את hiltViewModel() כדי לקבל מופע של ExampleViewModel ברמת היעד, כפי שמתואר בקטע הקוד הבא:

// import androidx.hilt.navigation.compose.hiltViewModel

@Composable
fun MyApp() {
    val navController = rememberNavController()
    val startRoute = "example"
    NavHost(navController, startDestination = startRoute) {
        composable("example") { backStackEntry ->
            // Creates a ViewModel from the current BackStackEntry
            // Available in the androidx.hilt:hilt-navigation-compose artifact
            val viewModel = hiltViewModel<MyViewModel>()
            MyScreen(viewModel)
        }
        /* ... */
    }
}

אם אתם צריכים לאחזר את המופע של ViewModel ברמת מסלולי הניווט או תרשים הניווט, תוכלו להשתמש בפונקציה הניתנת לקיבוץ hiltViewModel ולהעביר את backStackEntry התואם כפרמטר:

// import androidx.hilt.navigation.compose.hiltViewModel
// import androidx.navigation.compose.getBackStackEntry

@Composable
fun MyApp() {
    val navController = rememberNavController()
    val startRoute = "example"
    val innerStartRoute = "exampleWithRoute"
    NavHost(navController, startDestination = startRoute) {
        navigation(startDestination = innerStartRoute, route = "Parent") {
            // ...
            composable("exampleWithRoute") { backStackEntry ->
                val parentEntry = remember(backStackEntry) {
                    navController.getBackStackEntry("Parent")
                }
                val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
                ExampleWithRouteScreen(parentViewModel)
            }
        }
    }
}

חלוקה לדפים

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

דוגמה ל-Compose APIs של ספריית ה-Paging:

@Composable
fun MyScreen(flow: Flow<PagingData<String>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it }
        ) { index ->
            val item = lazyPagingItems[index]
            Text("Item is $item")
        }
    }
}

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

מפות

אפשר להשתמש בספרייה Maps Compose כדי להציג את מפות Google באפליקציה. לפניכם דוגמה לשימוש:

@Composable
fun MapsExample() {
    val singapore = LatLng(1.35, 103.87)
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(singapore, 10f)
    }
    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState
    ) {
        Marker(
            state = remember { MarkerState(position = singapore) },
            title = "Singapore",
            snippet = "Marker in Singapore"
        )
    }
}