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

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

פעילות

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

תוצאת הפעילות

‫rememberLauncherForActivityResult() API מאפשר לקבל תוצאה מפעילות ב-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(). לחיצה על הכפתור מפעילה את הבקשה. הלמדא הנגררת של rememberLauncherForActivityResult() מופעלת כשהמשתמש בוחר תמונה וחוזר לפעילות ההפעלה. התמונה שנבחרה נטענת באמצעות הפונקציה rememberImagePainter() של Coil.

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

בקשת הרשאות בתחילת ההפעלה

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

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

טיפול בכפתור "הקודם" של המערכת

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

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

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

ViewModel

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

מגניב

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

Kotlin

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

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

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

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

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

לדוגמה, אם משתמשים בפונקציה קומפוזבילית בפעילות, הפונקציה 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) שנקרא מפעילות, מקטע או יעד של תרשים Navigation. הסיבה לכך היא שכברירת מחדל, ViewModel מוגדרים לאובייקטים האלה ברמת המסך. כאן אפשר לקרוא מידע נוסף על מחזור החיים וההיקף של ViewModel.

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

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

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

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

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

‫Compose כולל תוספים לפתרונות הפופולריים ביותר שמבוססים על סטרימינג ב-Android. כל אחד מהתוספים האלה מסופק על ידי פריט מידע שונה:

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

הארטיפקטים האלה נרשמים כמאזין ומייצגים את הערכים כ-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, אתם יכולים לבצע פעולות אסינכרוניות באמצעות פונקציות Coroutine מתוך הרכיבים הקומפוזביליים.

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

רכיב Navigation מספק תמיכה באפליקציות Jetpack Compose. מידע נוסף זמין במאמרים ניווט באמצעות Compose והעברת נתונים מ-Jetpack Navigation ל-Navigation Compose.

Hilt

‫Hilt הוא הפתרון המומלץ להזרקת תלויות באפליקציות ל-Android, והוא פועל בצורה חלקה עם Compose.

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

@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.4.0'
}

Kotlin

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

כשמשתמשים ב-Navigation Compose, תמיד צריך להשתמש בפונקציה הקומפוזבילית hiltViewModel כדי לקבל מופע של @HiltViewModel עם הערות ViewModel. האפשרות הזו פועלת עם מקטעים או פעילויות שמסומנים ב-@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. בדף הגרסה של Paging מופיע מידע על התלות הנוספת ב-paging-compose שצריך להוסיף לפרויקט, ועל הגרסה שלה.

דוגמה לממשקי ה-API של Compose בספריית 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")
        }
    }
}

במסמכי התיעוד של רשימות ורשתות מוסבר איך משתמשים ב-Paging ב-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"
        )
    }
}