Jetpack Compose でログインフォームを作る

## はじめに

Jetpack Compose で入力フォームを作成する方法を記載します。画面に配置する要素は3つで、メールアドレス入力欄、パスワード入力欄、ボタンです。

Empty Compose Activity から作り始めた場合、おそらく追加のパッケージは必要ありません。

## 実装

まずはメールアドレス入力欄の実装です。

```kotlin
@Composable
fun EmailTextField(
  value: String,
  modifier: Modifier = Modifier,
  onValueChange: (String) -> Unit,
) {
  val focusManager = LocalFocusManager.current
  OutlinedTextField(
    value = value,
    onValueChange = onValueChange,
    modifier = modifier,
    keyboardOptions = KeyboardOptions(
      keyboardType = KeyboardType.Email,
      imeAction = ImeAction.Next,
    ),
    keyboardActions = KeyboardActions(
      onNext = {
        focusManager.moveFocus(FocusDirection.Down)
      }
    ),
    label = { Text("メールアドレス") },
  )
}
```

`keyboardType = KeyboardType.Email` とすることで、キーボードがメールアドレスの入力用になります。

`ImeAction.Next` はキーボードの右下にあるボタンの形式を表します。`keyboardOptions` の `imeAction = ImeAction.XXX` と `keyboardActions` の `onXXX` は密接に関係しており、たとえば `imeAction = ImeAction.Next` と設定すると `onNext` で渡した関数が呼ばれます。

`focusManager.moveFocus(FocusDirection.Down)` により次のフォーカス先にフォーカスを移すことができます。

次はパスワード入力欄の実装です。

```kotlin
@Composable
fun PasswordTextField(
  value: String,
  modifier: Modifier = Modifier,
  onValueChange: (String) -> Unit,
  submit: () -> Unit,
) {
  val focusManager = LocalFocusManager.current
  OutlinedTextField(
    value = value,
    onValueChange = onValueChange,
    modifier = modifier,
    keyboardOptions = KeyboardOptions(
      keyboardType = KeyboardType.Password,
      imeAction = ImeAction.Done,
    ),
    keyboardActions = KeyboardActions(
      onDone = {
        focusManager.clearFocus()
        submit()
      }
    ),
    visualTransformation = PasswordVisualTransformation(),
    label = { Text("パスワード") },
  )
}
```

今回はキーボードで決定を押すと submit 処理が走ってほしいため、submit 関数を受け取るようにしています。

メールアドレス入力欄とほぼ同じですが、`visualTransformation = PasswordVisualTransformation()` という見慣れない記述があります。これは、パスワードを非表示にする変換処理を登録しています。

最後にボタンの実装です。

```kotlin
@Composable
fun LoginButton(
  ajax: Boolean,
  modifier: Modifier = Modifier,
  submit: () -> Unit,
) {
  Button(onClick = submit, enabled = !ajax, modifier = modifier) {
    if (ajax) {
      CircularProgressIndicator(
        strokeWidth = 2.dp,
        modifier = Modifier.size(24.dp)
      )
    } else {
      Text("ログイン")
    }
  }
}
```

処理中はボタンイベントを無効にしたいため `enabled = !ajax` としています。

これらの要素を取り入れたフォームの実装は次のようになります。

```kotlin
@Composable
fun LoginForm() {
  var ajax by remember { mutableStateOf(false) }
  var email by remember { mutableStateOf("") }
  var password by remember { mutableStateOf("") }
  val scope = rememberCoroutineScope()
  val submit = {
    scope.launch(Dispatchers.IO) {
      ajax = true
      // ここにログイン処理を記述する
      ajax = false
    }
    Unit
  }
  Column(
    modifier = Modifier
      .fillMaxSize()
      .padding(16.dp)
  ) {
    EmailTextField(email, Modifier.fillMaxWidth()) { email = it }
    Spacer(Modifier.height(4.dp))
    PasswordTextField(password, Modifier.fillMaxWidth(), { password = it }, submit)
    Spacer(Modifier.height(12.dp))
    LoginButton(ajax, Modifier.fillMaxWidth().height(56.dp), submit)
  }
}
```

入力文字列などの情報はフォームに持たせています。こうすることで、ログイン処理を行うときに入力文字列を利用することができます。

3つの各要素では `modifier` を引数として受け取っていました。そのように実装することで、呼び出し側で細かなスタイルの調節ができるようになります。

## 終わりに

メールアドレス入力欄からパスワード入力欄にフォーカスを移動させるとき、はじめは方法が思い浮かばずに悩んでいましたが、`LocalFocusManager.current` を使うことでフォーカスを次に移す処理が簡単に実装できます。参考にしていただけると嬉しいです。