Tworzenie i inne biblioteki

W Compose możesz używać swoich ulubionych bibliotek. W tej sekcji opisujemy, jak włączyć kilka najbardziej przydatnych bibliotek.

Aktywność

Aby używać Compose w aktywności, musisz użyć ComponentActivity, czyli podklasy Activity, która udostępnia odpowiedni LifecycleOwner i komponenty dla Compose. Udostępnia ona też dodatkowe interfejsy API, które oddzielają Twój kod od zastępowania metod w klasie aktywności. Activity Compose udostępnia te interfejsy API w elementach kompozycyjnych, dzięki czemu nie trzeba już zastępować metod poza elementami kompozycyjnymi ani pobierać jawnej instancji Activity. Ponadto te interfejsy API zapewniają, że są inicjowane tylko raz, przetrwają ponowną kompozycję i zostaną prawidłowo wyczyszczone, jeśli element kompozycyjny zostanie usunięty z kompozycji.

Wynik aktywności

Interfejs rememberLauncherForActivityResult() API umożliwia uzyskanie wyniku z aktywności w elemencie kompozycyjnym:

@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"
        )
    }
}

Ten przykład pokazuje prosty GetContent() kontrakt. Kliknięcie przycisku powoduje wysłanie prośby. Lambda końcowa dla rememberLauncherForActivityResult() jest wywoływana, gdy użytkownik wybierze obraz i wróci do aktywności uruchamiającej. Spowoduje to wczytanie wybranego obrazu za pomocą funkcji rememberImagePainter() biblioteki Coil.

Jako pierwszy argument rememberLauncherForActivityResult()można użyć dowolnej podklasy ActivityResultContract. Oznacza to, że możesz użyć tej metody, aby poprosić o treści z platformy i w innych typowych wzorcach. Możesz też tworzyć własne kontrakty niestandardowe i używać ich z tą metodą.

Wysyłanie prośby o uprawnienia czasu działania

Tego samego interfejsu Activity Result API i rememberLauncherForActivityResult() opisanych powyżej można używać do wysyłania prośby o uprawnienia czasu działania za pomocą kontraktu RequestPermission w przypadku pojedynczego uprawnienia lub kontraktu RequestMultiplePermissions w przypadku wielu uprawnień.

Biblioteka Accompanist Permissions może też służyć jako warstwa nad tymi interfejsami API do mapowania bieżącego stanu przyznanych uprawnień na stan, którego może używać interfejs Compose.

Obsługa systemowego przycisku Wstecz

Aby zapewnić niestandardowe przechodzenie wstecz i zastąpić domyślne działanie systemowego przycisku Wstecz w elemencie kompozycyjnym, element ten może używać BackHandler do przechwytywania tego zdarzenia:

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

Pierwszy argument określa, czy the BackHandler jest obecnie włączony. Możesz go użyć, aby tymczasowo wyłączyć obsługę na podstawie stanu komponentu. Lambda końcowa zostanie wywołana, jeśli użytkownik wywoła zdarzenie systemowe Wstecz, a BackHandler jest obecnie włączony.

ViewModel

Jeśli używasz biblioteki Architecture Components ViewModel, możesz uzyskać dostęp do ViewModel z dowolnego elementu kompozycyjnego, wywołując funkcję viewModel(). Dodaj do pliku Gradle tę zależność:

Dynamiczny

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

Kotlin

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

Następnie możesz użyć funkcji viewModel() w kodzie.

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

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

viewModel() zwraca istniejący ViewModel lub tworzy nowy. Domyślnie zwrócony ViewModel jest ograniczony do otaczającej aktywności, fragmentu lub miejsca docelowego nawigacji i jest zachowywany tak długo, jak długo istnieje zakres.

Jeśli na przykład element kompozycyjny jest używany w aktywności, viewModel() zwraca tę samą instancję, dopóki aktywność nie zostanie zakończona lub proces nie zostanie zakończony.

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
) { /* ... */ }

Wytyczne dotyczące użytkowania

Zwykle uzyskujesz dostęp do instancji ViewModel w elementach kompozycyjnych na poziomie ekranu, czyli blisko elementu kompozycyjnego głównego wywoływanego z aktywności, fragmentu lub miejsca docelowego wykresu nawigacji. Dzieje się tak, ponieważ ViewModel są domyślnie ograniczone do tych obiektów na poziomie ekranu. Więcej informacji o cyklu życia i zakresie a ViewModel's znajdziesz tutaj.

Staraj się nie przekazywać ViewModel instancji do innych elementów kompozycyjnych, ponieważ może to sprawić, że te elementy kompozycyjne będą trudniejsze do przetestowania i mogą zepsuć podglądy. Zamiast tego przekazuj tylko dane i funkcje, których potrzebują, jako parametry.

Możesz używać instancji ViewModel do zarządzania stanem elementów kompozycyjnych na poziomie podrzędnego ekranu, ale pamiętaj o ViewModel's cyklu życia i zakresie. Jeśli element kompozycyjny jest samodzielny, możesz użyć Hilt do wstrzyknięcia ViewModel, aby uniknąć przekazywania zależności z nadrzędnych elementów kompozycyjnych.

Jeśli Twój ViewModel ma zależności, viewModel() przyjmuje opcjonalny ViewModelProvider.Factory jako parametr.

Więcej informacji o ViewModel w Compose oraz o tym, jak instancje są używane z biblioteką Navigation Compose, aktywnościami i fragmentami, znajdziesz w dokumentacji dotyczącej współdziałania.

Strumienie danych

Compose zawiera rozszerzenia najpopularniejszych rozwiązań opartych na strumieniach w Androidzie. Każde z tych rozszerzeń jest udostępniane przez inny artefakt:

Te artefakty rejestrują się jako słuchacze i reprezentują wartości jako State. Gdy zostanie wyemitowana nowa wartość, Compose ponownie skomponuje te części interfejsu, w których używana jest ta state.value. Na przykład w tym kodzie ShowData jest ponownie komponowany za każdym razem, gdy exampleLiveData emituje nową wartość.

// 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)
    }
}

Operacje asynchroniczne w Compose

Jetpack Compose umożliwia wykonywanie operacji asynchronicznych za pomocą współprogramów w elementach kompozycyjnych.

Więcej informacji znajdziesz w dokumentacji dotyczącej efektów ubocznych, w której opisano interfejsy API LaunchedEffect, produceState i rememberCoroutineScope.

Komponent nawigacji obsługuje aplikacje Jetpack Compose. Więcej informacji znajdziesz w artykułach Nawigacja za pomocą Compose i Migracja nawigacji Jetpack do nawigacji Compose.

Hilt

Hilt to zalecane rozwiązanie do wstrzykiwania zależności w aplikacjach na Androida, które bezproblemowo współpracuje z Compose.

Funkcja viewModel() wspomniana w sekcji ViewModel automatycznie używa ViewModel, który Hilt tworzy za pomocą adnotacji @HiltViewModel. Udostepniliśmy dokumentację z informacjami o integracji ViewModel z 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 i nawigacja

Hilt integruje się też z biblioteką Navigation Compose. Dodaj do pliku Gradle te dodatkowe zależności:

Dynamiczny

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

Kotlin

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

Gdy używasz Navigation Compose, zawsze używaj funkcji kompozycyjnej hiltViewModel, aby uzyskać instancję @HiltViewModel z adnotacją ViewModel. Działa to w przypadku fragmentów lub aktywności z adnotacją @AndroidEntryPoint.

Jeśli na przykład ExampleScreen jest miejscem docelowym na wykresie nawigacji, wywołaj hiltViewModel(), aby uzyskać instancję ExampleViewModel ograniczoną do miejsca docelowego, jak pokazano w tym fragmencie kodu:

// 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)
        }
        /* ... */
    }
}

Jeśli musisz pobrać instancję ViewModel ograniczoną do tras nawigacji lub wykresu nawigacji zamiast tego, użyj funkcji typu „composable” hiltViewModel i przekaż odpowiedni backStackEntry jako parametr:

// 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

Biblioteka Paging ułatwia stopniowe wczytywanie danych i jest obsługiwana w Compose. Na stronie wersji Paging znajdziesz informacje o dodatkowej zależności paging-compose, którą należy dodać do projektu, oraz o jej wersji.

Oto przykład interfejsów API Compose biblioteki 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")
        }
    }
}

Więcej informacji o używaniu Paging w Compose znajdziesz w dokumentacji dotyczącej list i siatek.

Mapy

Aby udostępnić Mapy Google w aplikacji, możesz użyć biblioteki Maps Compose. Oto przykład użycia:

@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"
        )
    }
}