Accompanist のユースケース

**2023/6/30 v0.31.4-beta までの変更内容を記事にも反映させました**
**2022/11/28 v0.27.1 までの変更内容を記事にも反映させました**
**2022/4/29 v0.23.1、0.24.7-alpha までの変更内容を記事にも反映させました**
**2021/8/5 v0.16.0 で追加されたライブラリを追加しました**

Jetpack Compose を使うプロジェクトでは使うことになるであろう Accompanist とは何なのか、Accompanist は何ができるのかを書いていこうと思います。(v0.31.4-beta 時点)

## Accompanist とは?

Accompanist は、Jetpack Compose で一部の API を使いやすくしたり、Android View にある機能を Compose でも使えるようにしたライブラリのグループです。
将来的には Jetpack の方でより良い形で実装された後に Accompanist からは非推奨になります。

Accompanist は複数あるライブラリのグループで、以下のライブラリがあります。

プロジェクトで使用している Jetpack Compose のバージョンで利用するライブラリのバージョンが異なるので確認しましょう。　　
https://google.github.io/accompanist/#compose-versions


| Library | package |
|----|----|
|System UI Controller|com.google.accompanist:accompanist-systemuicontroller:&lt;version&gt;|
|AppCompat Theme Adapter|com.google.accompanist:accompanist-appcompat-theme:&lt;version&gt;|　
|Pager Indicator|com.google.accompanist:accompanist-pager-indicators:&lt;version&gt;|
|Permissions|com.google.accompanist:accompanist-permissions:&lt;version&gt;|
|Placeholder|com.google.accompanist:accompanist-placeholder:&lt;version&gt;<br />com.google.accompanist:accompanist-placeholder-material:&lt;version&gt;|
|Flow Layouts(soon to be deprecated)|com.google.accompanist:accompanist-flowlayout:&lt;version&gt;|
|Navigation Animation(soon to be deprecated)|com.google.accompanist:accompanist-navigation-animation:&lt;version&gt;|
|Navigation Material|com.google.accompanist:accompanist-navigation-material:&lt;version&gt;|
|Drawable Painter|com.google.accompanist:accompanist-drawablepainter:&lt;version&gt;|
|WebView|com.google.accompanist:accompanist-webview:&lt;version&gt;|
|Adaptive|com.google.accompanist:accompanist-adaptive:&lt;version&gt;|
|Pager(deprecated)|com.google.accompanist:accompanist-pager:&lt;version&gt;|
|Inset(deprecated)|com.google.accompanist:accompanist-insets:&lt;version&gt;<br />com.google.accompanist:accompanist-insets-ui:&lt;version&gt;|  
|Swipe to Refresh(deprecated)|com.google.accompanist:accompanist-swiperefresh:&lt;version&gt;|

これらの中から必要なライブラリだけ選択してプロジェクトで使用することができます。

## Accompanist のライブラリのユースケース

### [System UI Controller](https://google.github.io/accompanist/systemuicontroller/)
- StatusBar や NavigationBar を制御しやすくしたライブラリ
- StatusBar や NavigationBar の色や表示・非表示を変更できる
- Android 8.1 を境目に StatusBar の色が自動で適用される
![api-scrim.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/72e07809-0de0-04c3-0622-6c3cb0f4987b.png)

```kotlin
val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight

SideEffect {
    // Update all of the system bar colors to be transparent, and use
    // dark icons if we're in light theme
    systemUiController.setSystemBarsColor(
        color = Color.Transparent,
        darkIcons = useDarkIcons
    )

    // setStatusBarsColor() and setNavigationBarsColor() also exist
}
```


### [AppCompat Theme Adapter](https://google.github.io/accompanist/appcompat-theme/)
- AppCompat の XML Theme を Jetpack Compose の Theme に変換するライブラリ
- Material Component の XML Theme を変換するライブラリは [MDC-Android Compose Theme Adapter](https://github.com/material-components/material-components-android-compose-theme-adapter) が提供されている
- AppCompat の Theme に定義されている色が以下のように MaterialTheme color　に割り当てられる

| MaterialTheme color | AppCompat attribute |
|---------------------|---------------------|
| primary             | colorPrimary |
| primaryVariant      | colorPrimaryDark |
| onPrimary           | Calculated black/white |
| secondary           | colorAccent |
| secondaryVariant    | colorAccent |
| onSecondary         | Calculated black/white |
| surface             | Default |
| onSurface           | android:textColorPrimary, else calculated black/white |
| background          | android:colorBackground |
| onBackground        | android:textColorPrimary, else calculated black/white |
| error               | colorError |
| onError             | Calculated black/white |



### [Permissions](https://google.github.io/accompanist/permissions/)
- Permission を Jetpack Compose で扱いやすくしたライブラリ

```kotlin
@Composable
fun FeatureThatRequiresCameraPermission(
    navigateToSettingsScreen: () -> Unit
) {
    // Track if the user doesn't want to see the rationale any more.
    var doNotShowRationale by rememberSaveable { mutableStateOf(false) }

    val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
    PermissionRequired(
        permissionState = cameraPermissionState,
        permissionNotGrantedContent = {
            if (doNotShowRationale) {
                Text("Feature not available")
            } else {
                Column {
                    Text("The camera is important for this app. Please grant the permission.")
                    Spacer(modifier = Modifier.height(8.dp))
                    Row {
                        Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
                            Text("Ok!")
                        }
                        Spacer(Modifier.width(8.dp))
                        Button(onClick = { doNotShowRationale = true }) {
                            Text("Nope")
                        }
                    }
                }
            }
        },
        permissionNotAvailableContent = {
            PermissionDenied(navigateToSettingsScreen)
        }
    ) {
        Text("Camera permission Granted")
    }
}

```


### [Placeholder](https://google.github.io/accompanist/placeholder/)
- コンテンツが読み込み中の [Placeholder](https://material.io/design/communication/launch-screen.html#placeholder-ui) を表示できるライブラリ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/64a18b9d-56d0-0e0e-b863-88af5485e98a.png" width="200px" />

```kotlin
Text(
    text = "Content to display after content has loaded",
    modifier = Modifier
        .padding(16.dp)
        .placeholder(visible = true)
)
```


### [Flow Layouts](https://google.github.io/accompanist/flowlayout/)
- Jetpack Compose 1.4.0 から Flow Layouts の機能が [foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation?hl=en) のライブラリに含まれるようになったが、1.4.0 時点では foundation の方で機能が足りない部分があるために移行できない可能性がある。
  - 1.5.0 では置き換え可能な状態になるので、そのタイミングで Deprecated になる予定。
<br />

- Jetpack Compose で Flexbox のようなレイアウトができるライブラリ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/bf47bf34-ccda-6373-5041-991cefafa02b.gif" width="200px" />


```kotlin
FlowRow {
    // row contents
}

FlowColumn {
    // column contents
}
```

### [Navigation Animation](https://google.github.io/accompanist/navigation-animation/)
- Jetpack Compose 1.5.0 + androidx.navigation.compose 2.7.0-alpha01 から Navigation Animation の機能が公式にサポートされるので、Deprecated になる予定。
<br />


- [Navigation Compose](https://developer.android.com/jetpack/compose/navigation) での遷移アニメーションを実装しやすくしたライブラリ

```kotlin
val navController = rememberAnimatedNavController()
AnimatedNavHost(navController, startDestination = "Blue") {
    composable(
        "Blue",
        enterTransition = { initial, _ ->
            when (initial.destination.route) {
                "Red" ->
                    slideInHorizontally(initialOffsetX = { 1000 }, animationSpec = tween(700))
                else -> null
            }
        },
        exitTransition = { _, target ->
            when (target.destination.route) {
                "Red" ->
                    slideOutHorizontally(targetOffsetX = { -1000 }, animationSpec = tween(700))
                else -> null
            }
        },
        popEnterTransition = { initial, _ ->
            when (initial.destination.route) {
                "Red" ->
                    slideInHorizontally(initialOffsetX = { -1000 }, animationSpec = tween(700))
                else -> null
            }
        },
        popExitTransition = { _, target ->
            when (target.destination.route) {
                "Red" ->
                    slideOutHorizontally(targetOffsetX = { 1000 }, animationSpec = tween(700))
                else -> null
            }
        }
    ) { BlueScreen(navController) }
    composable(
        "Red",
        ...
    )
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/83011e21-a82c-0979-bbcc-112dc767e31e.gif" width="200px" />


### [Navigation Material](https://google.github.io/accompanist/navigation-material/)

- [Navigation Compose](https://developer.android.com/jetpack/compose/navigation) で BottomSheet への遷移を実装できるライブラリ

```kotlin
val navController = rememberNavController()
val bottomSheetNavigator = rememberBottomSheetNavigator()
navController.navigatorProvider += bottomSheetNavigator
ModalBottomSheetLayout(bottomSheetNavigator) {
    NavHost(navController, Destinations.Home) {
       composable(route = "home") {
           ...
       }
       bottomSheet(route = "sheet") {
           Text("This is a cool bottom sheet!")
       }
    }
}
```

### [Drawable Painter](https://google.github.io/accompanist/drawablepainter/)

- Android の Drawable リソースを Painter として利用できるライブラリ

```kotlin
val drawable = AppCompatResources.getDrawable(LocalContext.current, R.drawable.rectangle)

Image(
    painter = rememberDrawablePainter(drawable = drawable),
    contentDescription = "content description",
)
```


### [WebView](https://google.github.io/accompanist/web/)

- `WebView` を Jetpack Compose でも使えるようにしたラッパーライブラリ  

```kotlin
val state = rememberWebViewState("https://example.com")

WebView(
    state,
    onCreated = { it.settings.javaScriptEnabled = true },
)
```

### [Adaptive](https://google.github.io/accompanist/adaptive/)

- Foldable や大きい画面対応のユーティリティ系の実装を提供するライブラリ  


```kotlin
val displayFeatures = calculateDisplayFeatures(this)

TwoPane(
    first = {
        Card {
            Box(
                contentAlignment = Alignment.Center,
                modifier = Modifier.fillMaxSize()
            ) {
                Text("First")
            }
        }
    },
    second = {
        Card {
            Box(
                contentAlignment = Alignment.Center,
                modifier = Modifier.fillMaxSize()
            ) {
                Text("Second")
            }
        }
    },
    strategy = TwoPaneStrategy { density, layoutDirection, layoutCoordinates ->
        if (layoutCoordinates.size.height >= layoutCoordinates.size.width) {
            VerticalTwoPaneStrategy(splitFraction = 0.75f)
        } else {
            HorizontalTwoPaneStrategy(splitFraction = 0.75f)
        }.calculateSplitResult(density, layoutDirection, layoutCoordinates)
    },
    displayFeatures = displayFeatures,
)
```

|Phone|Foldable|
|:--:|:--:|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/6dd4783b-d393-0b14-c35b-7d4a7601cbb3.png" width="200px" /> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/5f522673-6a77-c8db-b26f-58a127fa7739.png" width="200px" /> |





## Deprecated になったライブラリ

### [Pager](https://google.github.io/accompanist/pager/)
- ⚠️ Jetpack Compose 1.4.0 から Pager の機能が [foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation?hl=en) のライブラリに含まれるようになったので、こちらのライブラリは Deprecated になった
  - Deprecated に合わせて[マイグレーションガイド](https://google.github.io/accompanist/pager/#migration)も用意されている
  - ただし、Pager Indicator は Deprecated にはなっていない
<br />


- Android View の `ViewPager` のようなレイアウトができるライブラリ
- 横向きだけでなく縦向きの表示や無限にスワイプもできる
- Tab 連携やインジケータのサポートまでされている

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/3f28d9e9-f6a5-feb0-92ee-9449077c31bd.png" width="200px" />

```kotlin
val pagerState = rememberPagerState(pageCount = 10)

HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}
```

### [Inset](https://google.github.io/accompanist/insets/)

- ⚠️ Jetpack Compose 1.2.0 から Inset の機能が [foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation?hl=en) のライブラリに含まれるようになったので、こちらのライブラリは Deprecated になった
  - Deprecated に合わせて[マイグレーションガイド](https://google.github.io/accompanist/insets/)も用意されている
<br />

- WindowInsets を Jetpack Compose で扱いやすくしたライブラリで Edge to Edge 対応で使用することになる
- また、Experimental ではあるものの IME Animation も対応できる

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/2d589f7b-ff3b-a474-08c3-a893face31e1.png" width="200px" />

```kotlin
Scaffold(
    topBar = {
        // We use TopAppBar from accompanist-insets-ui which allows us to provide
        // content padding matching the system bars insets.
        TopAppBar(
            title = { Text(stringResource(R.string.insets_title_list)) },
            backgroundColor = MaterialTheme.colors.surface.copy(alpha = 0.9f),
            contentPadding = rememberInsetsPaddingValues(
                LocalWindowInsets.current.statusBars,
                applyBottom = false,
            ),
        )
    },
    bottomBar = {
        // We add a spacer as a bottom bar, which is the same height as
        // the navigation bar
        Spacer(Modifier.navigationBarsHeight().fillMaxWidth())
    },
) { contentPadding ->
    // We apply the contentPadding passed to us from the Scaffold
    Box(Modifier.padding(contentPadding)) {
        // content
    }
}
```

### [Swipe to Refresh](https://google.github.io/accompanist/swiperefresh/)

- ⚠️ Jetpack Compose 1.3.0 から Swipe to Refresh の機能が [material](https://developer.android.com/jetpack/androidx/releases/compose-material?hl=en) のライブラリに含まれるようになったので、こちらのライブラリは Deprecated になった
  - Deprecated に合わせて[マイグレーションガイド](https://google.github.io/accompanist/swiperefresh/#migration)も用意されていたり、[移行の記事](https://qiita.com/Nabe1216/items/eba345e59fae092ed821)を別途書いています

<br />


- Android View の `SwipeRefreshLayout` のような Pull to Refresh ができるライブラリ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/a115e846-351e-58f2-27aa-ea07e1ed2e54.png" width="200px" />

```kotlin
val viewModel: MyViewModel = viewModel()
val isRefreshing by viewModel.isRefreshing.collectAsState()

SwipeRefresh(
    state = rememberSwipeRefreshState(isRefreshing),
    onRefresh = { viewModel.refresh() },
) {
    LazyColumn {
        items(30) { index ->
            // TODO: list items
        }
    }
}
```


### ImageLoader
- coil を使用した画像読み込みも元々は Accompanist にあったが 
coil のライブラリで提供されるようになった
    - https://coil-kt.github.io/coil/compose/

```kotlin
Image(
    painter = rememberImagePainter("https://www.example.com/image.jpg"),
    contentDescription = null,
    modifier = Modifier.size(128.dp)
)
```

## リンク
- https://github.com/google/accompanist
- https://google.github.io/accompanist/