チップを計算するアプリを作ってみる。

## 参考にした場所
引用：[ＵＩと状態](https://developer.android.com/courses/pathways/android-basics-compose-unit-2-pathway-3?hl=ja#codelab-https://developer.android.com/codelabs/basic-android-kotlin-compose-using-state)


## スターター コードを取得を入手する。

```github:github
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git
$ cd basic-android-kotlin-compose-training-tip-calculator
$ git checkout starter

```


入手したら展開して起動します。


チップアプリで用いる単語をstring.xmlに登録します。

res -> values -> string.xml

```kotlin:string.xml
<resources>
   <string name="app_name">Tip Time</string>
   <string name="calculate_tip">Calculate Tip</string>
   <string name="bill_amount">Bill Amount</string>
   <string name="tip_amount">Tip Amount: %s</string>
</resources>
```


## テキストフィールドを作る。

スターターコードはある程度書いてあるので、その次からやっていきます。

テキストフィールドコンポーザブルを作ります。

```kotlin:MainActivity.kt
@Composable
fun EditNumberField(modifier: Modifier=Modifier){
    TextField(value = "", onValueChange = {},modifier=modifier)

}

```

これをTipTimeLayout()に入れます。

```kotlin:MainActivity.kt
    Text(…)
    EditNumberField(modifier = Modifier
        .padding(bottom = 36.dp)
        .fillMaxWidth())
    Text(…)

```



## Composeで状態を使用する。

状態とは時間と共に変化する可能性があるものを指します。
テキストフィールドの状態を最初０にします。

```kotlin:MainActivity.kt
@Composable
fun EditNumberField(modifier: Modifier=Modifier){
    val amountInput = "0"
    TextField(value = amountInput , onValueChange = {},modifier=modifier)
}

```

## 構成

変化する可能性のある値を監視するためにmutableStateOf()関数を使います。

また警告が出るために無視します。

```kotlin:MainActivity.kt
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf

@SuppressLint("UnrememberedMutableState")
@Composable
fun EditNumberField(modifier: Modifier=Modifier){
    var amountInput = mutableStateOf("0")
    TextField(value = amountInput.value,
        onValueChange = {amountInput.value = it},
        modifier=modifier)
}
```

onValueChange コールバックはテキストボックスへの入力が変更されるとトリガーされます。

この状態でアプリを起動して、テキストフィールドに入力しても文字は保存されません



## remember 関数を使用して状態を保存する

再コンポーズにより、コンポーズ可能なメソッドが何度も呼び出される場合があります。コンポーザブルは、保存されていない場合、再コンポーズ中に状態をリセットします。

そのため、保存する値がある場合、一時的に格納する必要があります。
remember関数を使います。


```kotlin:MainActivity.kt
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
   var amountInput by remember { mutableStateOf("") }
   TextField(
       value = amountInput,
       onValueChange = { amountInput = it },
       modifier = modifier
   )
}
```


これで保存されるようになりました。

## 外観を変更する

#### テキスト ボックスにラベルを追加する

追加するのはラベル、下線、キーボード タイプの制限です。


```kotlin:MainActivity.kt
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType


@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
    var amountInput by remember { mutableStateOf("") }
    TextField(
        value = amountInput,
        onValueChange = { amountInput = it },
        singleLine = true,
        label = { Text(stringResource(R.string.bill_amount)) },
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
        modifier = modifier
    )
}
```

## チップの金額を表示する

スターター コードの一部として提供されている private calculateTip()を用います。

テキストフィールドに入力された値はStringなのでDoubleに変更します。

使うのは toDoubleOrNull 関数です。

```kotlin:MainActivity.kt
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {

    var amountInput by remember { mutableStateOf("") }
    val amount = amountInput.toDoubleOrNull() ?: 0.0
    val tip = calculateTip(amount)


    TextField(
        value = amountInput,
        onValueChange = { amountInput = it },
        modifier = modifier,
        singleLine = true,
        label = { Text(text = stringResource(id = R.string.bill_amount))},
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
    )
}

```

次にここで計算されたtipを表示する必要があります。
このコンポーザブルの値を別のコンポーザブルから巻き上げ、ホイスティングします。
入力したと同時に行う、つまりステートレスにもします。


## 状態ホイスティング

状態を他のコンポーザブルで利用できるするためにvalue パラメータと onValueChange パラメータを追加して状態をホイスティングします。

```kotlin:MainActivity.kt

@Composable
fun EditNumberField(
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
) {
//...

TextField(
   value = value,
   onValueChange = onValueChange,
   //...
)

```


そしてremember で保存した状態を EditNumberField() 関数から TipTimeLayout() 関数に移動します。

```kotlin:MainActivity.kt

@Composable
fun TipTimeLayout() {
   var amountInput by remember { mutableStateOf("") }

   val amount = amountInput.toDoubleOrNull() ?: 0.0
   val tip = calculateTip(amount)

   Column(
       //...
   ) {
       //...
   }
}

```

状態ホイスティングにより、テキストフィールドに入力された値が、すべてつながるようになりました。

状態ホイスティングを行うために、コンポーズ可能な関数 EditNumberField() に amountInput 値と、ユーザーの入力によって amountInput 値を更新するラムダ コールバックの 2 つの引数を渡しました。

完成がこちらです

```kotlin:MainActivity.kt

@Composable
fun TipTimeLayout() {
   var amountInput by remember { mutableStateOf("") }
   val amount = amountInput.toDoubleOrNull() ?: 0.0
   val tip = calculateTip(amount)

   Column(
       modifier = Modifier
            .statusBarsPadding()
            .padding(horizontal = 40.dp)
            .verticalScroll(rememberScrollState())
            .safeDrawingPadding(),
       horizontalAlignment = Alignment.CenterHorizontally,
       verticalArrangement = Arrangement.Center
   ) {
       Text(
           text = stringResource(R.string.calculate_tip),
           modifier = Modifier
               .padding(bottom = 16.dp, top = 40.dp)
               .align(alignment = Alignment.Start)
       )
       EditNumberField(
           value = amountInput,
           onValueChange = { amountInput = it },
           modifier = Modifier
               .padding(bottom = 32.dp)
               .fillMaxWidth()
       )
       Text(
           text = stringResource(R.string.tip_amount, tip),
           style = MaterialTheme.typography.displaySmall
       )
       Spacer(modifier = Modifier.height(150.dp))
   }
}

@Composable
fun EditNumberField(
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
) {
   TextField(
       value = value,
       onValueChange = onValueChange,
       singleLine = true,
       label = { Text(stringResource(R.string.bill_amount)) },
       keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
       modifier = modifier
   )
}

```