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 コントラクトを使って実行時の権限のリクエストに使用できます。

Accompanist 権限ライブラリを使用して、これらの API より上のレイヤを使用して、権限の現在の付与状態を Compose UI が使用できる状態にマッピングすることもできます。

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

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

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

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

ViewModel

アーキテクチャ コンポーネントの 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() は、アクティビティが終了するまで、またはプロセスが強制終了されるまで、同じインスタンスを返します。

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 インスタンスには、画面レベルのコンポーザブルでアクセスします。これは、アクティビティ、フラグメント、または Navigation グラフのデスティネーションから呼び出されるルート コンポーザブルに近いものを指します。これは、ViewModel がデフォルトで、これらの画面レベルオブジェクトにスコープされているためです。ViewModelライフサイクルとスコープについて詳しくは、こちらをご覧ください。

ViewModel インスタンスを他のコンポーザブルに渡さないようにしてください。そうしないと、コンポーザブルのテストが難しくなり、プレビューが破損する可能性があります。代わりに、必要なデータと関数のみをパラメータとして渡すようにしてください。

ViewModel インスタンスを使用してサブ画面レベルのコンポーザブルの状態を管理できますが、ViewModelライフサイクルとスコープに注意してください。コンポーザブルが自己完結型の場合は、親コンポーザブルから依存関係を渡す必要がないように、Hilt を使用して ViewModel を挿入することを検討してください。

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.2.0'
}

Kotlin

dependencies {
    implementation("androidx.hilt:hilt-navigation-compose:1.2.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 = remember { MarkerState(position = singapore) },
            title = "Singapore",
            snippet = "Marker in Singapore"
        )
    }
}