Compose とその他のライブラリ

Compose でお好みのライブラリを使用できます。このセクションでは、有用なライブラリを組み込む方法について説明します。

アクティビティ

アクティビティで Compose を使用するには、ComponentActivity を使用する必要があります。これは、Compose に適切な LifecycleOwner とコンポーネントを提供する Activity のサブクラスです。また、アクティビティ クラスのメソッドのオーバーライドからコードを分離する追加の API も提供します。Activity Compose は、これらの API をコンポーザブルに公開します。これにより、コンポーザブル外のメソッドをオーバーライドしたり、明示的な Activity インスタンスを取得したりする必要がなくなりました。さらにこれらの API は、一度初期化されると再コンポーズで保持され、コンポーザブルがコンポジションから削除された場合、適切にクリーンアップを行うようにします。

アクティビティの結果

rememberLauncherForActivityResult() API を使用すると、コンポーザブル内のアクティビティの結果を取得できます。

@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() の末尾のラムダが呼び出されます。これにより、Coil の rememberImagePainter() 関数を使用して、選択した画像が読み込まれます。

ActivityResultContract のすべてのサブクラスを、rememberLauncherForActivityResult() の最初の引数として使用できます。つまり、この方法を使用して、フレームワークや他の一般的なパターンでコンテンツをリクエストできます。また、独自のカスタム コントラクトを作成して、この方法で使用することもできます。

実行時の権限をリクエストする

前述の Activity Result API と rememberLauncherForActivityResult() は、1 つの権限に対する RequestPermission コントラクト、または複数の権限に対する RequestMultiplePermissions コントラクトを使って実行時の権限のリクエストに使用できます。

これらの API の上位レイヤで Accompanist Permissions ライブラリを使用して、権限に対して現在許可されている状態を Compose UI で使用できる状態にマッピングすることもできます。

システムの [戻る] ボタンの処理

コンポーザブル内からカスタムの「戻る」ナビゲーションを提供し、システムの [戻る] ボタンのデフォルトの動作をオーバーライドするには、コンポーザブルで BackHandler を使用してイベントをインターセプトします。

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

最初の引数は、BackHandler が現在有効かどうかを制御します。この引数を使用すると、コンポーネントの状態に基づいてハンドラを一時的に無効にできます。ユーザーがシステムの「戻る」イベントをトリガーし、BackHandler が有効になっている場合、末尾のラムダが呼び出されます。

ViewModel

アーキテクチャ コンポーネントの ViewModel ライブラリを使用している場合、viewModel() 関数を呼び出すことで、任意のコンポーザブルから 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 に依存関係がある場合、viewModel() はオプションの ViewModelProvider.Factory をパラメータとして受け取ります。

Compose の ViewModel と、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 が使用される UI 部分を再コンポーズします。たとえば、このコードでは、exampleLiveData が新しい値を出力するたびに ShowData が再コンポーズされます。

// 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 では、コンポーザブル内からコルーチンを使用して非同期オペレーションを実行できます。

詳しくは、副作用に関するドキュメントLaunchedEffect API、produceState API、rememberCoroutineScope API をご覧ください。

Navigation コンポーネントは、Jetpack Compose アプリをサポートしています。詳しくは、Compose を使用したナビゲーションJetpack Navigation を Navigation Compose に移行するをご覧ください。

Hilt

Hilt は、Android アプリで依存性を注入するための推奨されるソリューションであり、Compose とシームレスに連携します。

ViewModel セクションに記載されている viewModel() 関数は、Hilt が @HiltViewModel アノテーションを使用して構築する ViewModel を自動的に使用します。Hilt の 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 ファイルに次のように依存関係を追加します。

Groovy

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

Kotlin

dependencies {
    implementation("androidx.hilt:hilt-navigation-compose:1.0.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

Paging ライブラリを使用すると、データを段階的に読み込むことが容易になります。これは Compose でサポートされています。Paging リリースのページには、プロジェクトとそのバージョンに追加する必要がある特別な paging-compose 依存関係の情報が記載されています。

Paging ライブラリの Compose API の例を次に示します。

@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 で Paging を使用する方法について詳しくは、リストとグリッドのドキュメントをご覧ください。

マップ

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 = MarkerState(position = singapore),
            title = "Singapore",
            snippet = "Marker in Singapore"
        )
    }
}