Scrittura e altre librerie

Puoi utilizzare le tue librerie preferite in Compose. Questa sezione descrive come incorporare alcune delle librerie più utili.

Attività

Per utilizzare Compose in un'attività, devi utilizzare ComponentActivity, una sottoclasse di Activity che fornisce a Compose i componenti e LifecycleOwner appropriati. Inoltre, fornisce API aggiuntive che disaccoppiano il codice dall'override dei metodi nella classe dell'attività. Activity Compose espone queste API ai componibili in modo che non sia più necessario eseguire l'override dei metodi al di fuori dei componibili o recuperare un'istanza Activity esplicita. Inoltre, queste API garantiscono che vengano inizializzate una sola volta, sopravvivano alla ricomposizione e vengano eliminate correttamente se il composable viene rimosso dalla composizione.

Risultato dell'attività

L'API rememberLauncherForActivityResult() ti consente di ottenere un risultato da un'attività nel tuo 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"
        )
    }
}

Questo esempio mostra un semplice contratto GetContent(). Toccando il pulsante, viene avviata la richiesta. La funzione lambda finale per rememberLauncherForActivityResult() viene richiamata quando l'utente seleziona un'immagine e torna all'attività di avvio. In questo modo, l'immagine selezionata viene caricata utilizzando la funzione rememberImagePainter() di Coil.

Qualsiasi sottoclasse di ActivityResultContract può essere utilizzata come primo argomento di rememberLauncherForActivityResult(). Ciò significa che puoi utilizzare questa tecnica per richiedere contenuti dal framework e in altri pattern comuni. Puoi anche creare i tuoi contratti personalizzati e utilizzarli con questa tecnica.

Richiesta di autorizzazioni di runtime

Per richiedere le autorizzazioni di runtime, puoi utilizzare la stessa API Activity Result e lo stesso rememberLauncherForActivityResult() spiegati sopra, utilizzando il contratto RequestPermission per una singola autorizzazione o il contratto RequestMultiplePermissions per più autorizzazioni.

La libreria Accompanist Permissions può essere utilizzata anche come livello sopra queste API per mappare lo stato attuale delle autorizzazioni concesse in uno stato che la tua UI di Compose può utilizzare.

Gestione del pulsante Indietro del sistema

Per fornire una navigazione a ritroso personalizzata e sostituire il comportamento predefinito del pulsante Indietro di sistema dall'interno del tuo composable, quest'ultimo può utilizzare un BackHandler per intercettare l'evento:

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

Il primo argomento controlla se BackHandler è attualmente abilitato. Puoi utilizzare questo argomento per disabilitare temporaneamente il gestore in base allo stato del componente. La funzione lambda finale verrà richiamata se l'utente attiva un evento Indietro di sistema e BackHandler è attualmente abilitato.

ViewModel

Se utilizzi la libreria ViewModel dei componenti dell'architettura, puoi accedere a un ViewModel da qualsiasi composable chiamando la funzione viewModel(). Aggiungi la seguente dipendenza al file Gradle:

Alla moda

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

Kotlin

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

Puoi quindi utilizzare la funzione viewModel() nel tuo codice.

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

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

viewModel() restituisce un ViewModel esistente o ne crea uno nuovo. Per impostazione predefinita, l'oggetto ViewModel restituito ha come ambito l'attività, il frammento o la destinazione di navigazione che lo contiene e viene conservato finché l'ambito è attivo.

Ad esempio, se il composable viene utilizzato in un'attività, viewModel() restituisce la stessa istanza finché l'attività non viene completata o il processo non viene terminato.

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

Linee guida sull'utilizzo

In genere, accedi alle istanze di ViewModel nei componibili a livello di schermata, ovvero vicino a un componibile principale chiamato da un'attività, un frammento o una destinazione di un grafico di navigazione. Questo perché, per impostazione predefinita, l'ambito delle ViewModel è limitato a questi oggetti a livello di schermata. Scopri di più sul ciclo di vita e sull'ambito di un ViewModel qui.

Cerca di evitare di passare istanze ViewModel ad altri componibili, in quanto ciò può rendere più difficile il test di questi componibili e può interrompere le anteprime. Passa invece solo i dati e le funzioni di cui hanno bisogno come parametri.

Puoi utilizzare le istanze ViewModel per gestire lo stato dei componibili a livello di sottoschermo, ma tieni presente il ciclo di vita e l'ambito di ViewModel. Se il composable è autonomo, ti consigliamo di utilizzare Hilt per inserire ViewModel ed evitare di dover passare le dipendenze dai composable principali.

Se ViewModel ha dipendenze, viewModel() accetta un parametro ViewModelProvider.Factory facoltativo.

Per saperne di più su ViewModel in Compose e su come vengono utilizzate le istanze con la libreria Navigation Compose o con attività e frammenti, consulta la documentazione sull'interoperabilità.

Stream di dati

Compose include estensioni per le soluzioni basate su stream più diffuse di Android. Ognuna di queste estensioni è fornita da un artefatto diverso:

Questi artefatti si registrano come listener e rappresentano i valori come State. Ogni volta che viene emesso un nuovo valore, Compose ricompone le parti della UI in cui viene utilizzato state.value. Ad esempio, in questo codice, ShowData si ricompone ogni volta che exampleLiveData emette un nuovo valore.

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

Operazioni asincrone in Compose

Jetpack Compose ti consente di eseguire operazioni asincrone utilizzando le coroutine all'interno dei tuoi componibili.

Per saperne di più, consulta le API LaunchedEffect, produceState e rememberCoroutineScope nella documentazione sugli effetti collaterali.

Il componente Navigation fornisce supporto per le applicazioni Jetpack Compose. Per saperne di più, consulta Navigazione con Compose e Esegui la migrazione di Jetpack Navigation a Navigation Compose.

Hilt

Hilt è la soluzione consigliata per l'inserimento delle dipendenze nelle app per Android e funziona perfettamente con Compose.

La funzione viewModel() menzionata nella sezione ViewModel utilizza automaticamente il ViewModel creato da Hilt con l'annotazione @HiltViewModel. Abbiamo fornito la documentazione con informazioni sull'integrazione di ViewModel di 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 e navigazione

Hilt si integra anche con la libreria Navigation Compose. Aggiungi le seguenti dipendenze aggiuntive al file Gradle:

Alla moda

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

Kotlin

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

Quando utilizzi Navigation Compose, utilizza sempre la funzione componibile hiltViewModel per ottenere un'istanza di ViewModel con annotazione @HiltViewModel. Questa funzionalità è compatibile con frammenti o attività annotati con @AndroidEntryPoint.

Ad esempio, se ExampleScreen è una destinazione in un grafico di navigazione, chiama hiltViewModel() per ottenere un'istanza di ExampleViewModel con ambito per la destinazione, come mostrato nello snippet di codice riportato di seguito:

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

Se invece devi recuperare l'istanza di un ViewModel con ambito limitato ai percorsi di navigazione o al grafico di navigazione, utilizza la funzione componibile hiltViewModel e passa il backStackEntry corrispondente come parametro:

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

La libreria Paging semplifica il caricamento graduale dei dati ed è supportata in Compose. La pagina della release di Paging contiene informazioni sulla dipendenza paging-compose aggiuntiva che deve essere aggiunta al progetto e sulla relativa versione.

Ecco un esempio delle API Compose della libreria 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")
        }
    }
}

Per saperne di più sull'utilizzo di Paging in Compose, consulta la documentazione su elenchi e griglie.

Maps

Puoi utilizzare la libreria Maps Compose per fornire Google Maps nella tua app. Ecco un esempio di utilizzo:

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