2021年6月21日月曜日

Jetpack Compose で Runtime Permission をリクエストする

onStart() でパーミッションがあるかどうかチェックして、無い場合はパーミッションをリクエストする処理です。

rememberLauncherForActivityResult() を使います。 enum class PermissionState { Checking, Granted, Denied, } @Composable private fun NeedPermissionScreen() { var state by remember { mutableStateOf(PermissionState.Checking) } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { state = if (it) PermissionState.Granted else PermissionState.Denied } val permission = Manifest.permission.CAMERA val context = LocalContext.current val lifecycleObserver = remember { LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { val result = context.checkSelfPermission(permission) if (result != PackageManager.PERMISSION_GRANTED) { state = PermissionState.Checking launcher.launch(permission) } else { state = PermissionState.Granted } } } } val lifecycle = LocalLifecycleOwner.current.lifecycle DisposableEffect(lifecycle) { lifecycle.addObserver(lifecycleObserver) onDispose { lifecycle.removeObserver(lifecycleObserver) } } when (state) { PermissionState.Checking -> { } PermissionState.Granted -> { // TODO パーミッションが必要な機能を使う画面 } PermissionState.Denied -> { // TODO 拒否された時の画面 } } }

2021年6月10日木曜日

Compose の Text を長押しで文字選択できるようにしたい

SelectionContainer を使います。 SelectionContainer { Text("This text is selectable") }
選択ハンドルなどの色は MaterialTheme.colors.primary が使われます。

残念ながら SelectionContainer には直接色を指定するパラメータは用意されていません。

ピンポイントで色を指定したいなら MaterialTheme を使って primary を上書きします。 val original = MaterialTheme.colors val textColor = original.primary MaterialTheme(colors = original.copy(primary = original.secondary)) { SelectionContainer { Text("This text is selectable", color = textColor) } }




2021年6月1日火曜日

Kotlin の sealed interface が必要になる例

(本当は Compose のコード例(State/MutableState)にしたかったんだけど、まだ Jetpack Compose は Kotlin 1.5 に対応してないので sealed interface 使えないんだよね...)


ViewModel で持ってる LiveData を Activity で observe してるとする。 class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel.state.observe(this) { when (it) { is State.Data -> { println(it.value) } State.Loading -> { } } } } } class MainViewModel : ViewModel() { private val _state = MutableLiveData<State>() val state: LiveData<State> get() = _state fun doSomething() { val state = _state.value if (state is State.Data) { state.mutableValue = Random.nextInt() } } } sealed class State { object Loading : State() data class Data(val id: String) : State() { // 変更できるのは MainViewModel からだけにしたいが、 // private にすると MainViewModel からも見えなくなる var mutableValue: Int = -1 val value: Int get() = mutableValue } } ↑ State.Data が持つ mutableValue は MainViewModel からのみ変更できるようにしたい。

mutableValue に private は MainViewModel から見えなくなるのでダメ。 private var mutableValue: Int = -1 これもダメ。 private sealed class State { private data class Data(val id: String) : State() { Data を top level にすると private をつけても MainViewModel から見えるけど、MainActivity から見えなくなるのでダメ。 sealed class State object Loading : State() // MainViewModel から mutableValue は見えるが // Data が MainActivity からは見えなくなる private data class Data(val id: String) : State() { var mutableValue: Int = -1 val value: Int get() = mutableValue }

ということで sealed interface を使います。 class MainViewModel : ViewModel() { private val _state = MutableLiveData<State>() val state: LiveData<State> get() = _state fun doSomething() { val state = _state.value if (state is DataImpl) { state.mutableValue = Random.nextInt() } } } sealed interface State object Loading : State sealed interface Data : State { val value: Int } private class DataImpl(val id: Int) : Data { // private class なので変更できるのは同じファイルからだけ var mutableValue: Int = id override val value: Int get() = mutableValue }