Handling Text in Jetpack compose with Android
Tags: kotlin, android, tristan
Published: 2022-11-08 18:13:25

### Introduction
- New Android tutorial every Monday and Thursday, so buckle up and lets learn some Android.


###Getting started
- While `handling text in Jetpack compose` is not a new topic, I want to introduce everyone to a more systematic approach to handling text input. Hopefully making things a little less chaotic. Our approach is going to have 4 steps to it:

**1) State**

**2) Style**

**3) Errors**

**4) Hoisting the state**


###1) State
- In this section we can start by creating a basic TextField that allows us to capture user input:

```
@Composable
fun SimpleInput(viewModel:TestViewModel = viewModel()){
    var text by remember { mutableStateOf("") }

    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )


}
```
- Lets break down the code block above and start with, `var text by remember { mutableStateOf("") }`. 

**var :** indicates this property is mutable(it can be changed)

**by :** declares a [delegate property](https://kotlinlang.org/docs/delegated-properties.html), which means the getter and setter for this property will be defined by what comes after the keyword.

**remember :** Keeps the value consistent across recompositions.

**mutableStateOf() :** returns a [MutableState](https://developer.android.com/reference/kotlin/androidx/compose/runtime/MutableState), which is a value we change and Compose will automatically observe changes made to it.

- [HERE](https://dev.to/zachklipp/remember-mutablestateof-a-cheat-sheet-10ma) is a link to a blog post doing a more indepth dive on the topics mentioned above 

**TextField :** is the composable that will be shown to the user and be used to capture user input. We have passed it 3 parameter: `1)value :` the input text to be shown in the TextField, `2)onValueChange :` The callback that is triggered when the input service updates the text. An updated text comes as a parameter of the callback. If we translate that into non tech jargon, we get, onValueChange is a parameter that takes a function which will be called each time the user changes the input inside the TextField. The function takes in a single parameter, which is the text being typed.
- If the syntax of `{ text = it }` confuses you, you need to read up on `lambda functions` of Kotlin. For now just know that the `{}` brackets count as a function and `it` represents the single parameter of the words being typed


###2) Style
- Now we can go ahead and add any types of styles we like, which is really up the individual programmer. So yours might be different than mine.
```
TextField(
        //state
        value = text,
        onValueChange = { text = it },
        //style
        singleLine = true,
        label = { Text("Label") },
        modifier = Modifier.padding(start = 0.dp,40.dp,0.dp,0.dp),
        textStyle = TextStyle(fontSize = 26.sp),
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Text
        ),
    
    )
```
- The above styles are all fairly self explanitory, however, I would recommend that you play around with the different types of 
`keyboardOptions` and notice how the keyboards can change depending on the `keyboardType`

- At this point we will have a working TextField, so feel free to run your application and make sure that everything work correctly and move on to the next step.

###3) Errors
- This step is us defining how the error is going to look. and we can do so with, `isError :` which is used to indicate when everything should turn red and `trailingIcon ` which we use to show the actual error icon. Implementing those two parameters  into our TextField looks something like this:

```
TextField(
        //state
        value = text,
        onValueChange = { text = it },
        //style
        singleLine = true,
        label = { Text("Label") },
        modifier = Modifier.padding(start = 0.dp,40.dp,0.dp,0.dp),
        textStyle = TextStyle(fontSize = 26.sp),
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Text
        ),
        //errors
        isError = true,
        trailingIcon = {
            Icon(Icons.Filled.Error, "error has occurred")
        }
    
    )

```
- For right now us setting `isError = true` is fine but in the next section we will use a conditional to determine its value.

###4) Hoisting the state
- In a real world application we want to store the state in a ViewModel and have it updated through a function we define. So create a new file and call it TestViewModel(you can call it whatever you want):

```
class TestViewModel:ViewModel() {

    private val _uiState = mutableStateOf("")
    val uiState: State<String> = _uiState


 fun updateMethod(text:String){
        _uiState.value = text
    }

```

- By us creating and using this ViewModel class we are abiding by the recommended app architecture which is found [HERE](https://developer.android.com/topic/architecture). We are currently implementing the UI layer for our app.

- If you are unfamiliar with this architecture or just ViewModels in general than you are probably confused by the two variables, `_uiState` and `uiState`.

**_uiState :** a mutable state that will be used only by methods inside of the ViewModel class

**uiState :** is a non mutable state that will be exposed and used by our UI

- All that sounds awesome, but why use two 2? It boils down to `separation of concerns` we want our UI to only focus on displaying UI and move all the state handling logic to the ViewModel, which will make for easier testing and cleaner code.

- Now lets update our TextField to use our ViewModel:

```
@Composable
fun SimpleInput(viewModel:TestViewModel = viewModel()){

    TextField(
        //state
        value = viewModel.uiState.value,
        onValueChange = { viewModel.updateMethod(it) }

//everything else stays the same
}

```
- The only thing that might seems a little weird is the default value we are providing with `viewModel:TestViewModel = viewModel()` which just creates an instance of our ViewModel class

- Now we can move on to error validation and creating a more realistic UIState

###Error validation and UI state
- Our current state is very simple and in a really project things get a lot more complicated, so lets create a state that would reflect that. Inside that same file as out ViewModel class, at the top of the file create your UI state class:
```
data class TestUiState(
    val text: String = "",
    val errorMessage: String? = null,

)
```
- If you are unfamiliar with Kotlin's data class please read the documentation, [HERE](https://kotlinlang.org/docs/data-classes.html). If you don't want to just know that data classes are used to hold data(UI data in our case) and they provide us with a lot of extra methods automatically.
- Keen eyed viewers have probably notice that do to us using the `val` keywords our `TestUiState` class is immutable, why? Well, as the documentations states:
`The key benefit of this is that immutable objects provide guarantees regarding the state of the application at an instant in time. This frees up the UI to focus on a single role: to read the state and update its UI elements accordingly. As a result, you should never modify the UI state in the UI directly unless the UI itself is the sole source of its data`. So basically immutability  gives us separation of concerns and gives our UI a guaranteed state.

###Final update to our ViewModel class:
```
class TestViewModel:ViewModel() {

    private val _uiState = mutableStateOf(TestUiState())
    val uiState: State<TestUiState> = _uiState

        fun updateMethod(text:String){
        _uiState.value = _uiState.value.copy(text = text)
    }


    fun validateText(text:String){
        if(text.isNullOrBlank()){
            _uiState.value = _uiState.value.copy(errorMessage= "Can not be blank")
        }else{
            _uiState.value = _uiState.value.copy(errorMessage= null)
        }
    }
}

```

- With the code above I want to point out the use of `copy()` method. This is one of the method provided to us automatically when using a data class. when `copy()` is used it will create a `shallow copy`(clones all the values in the object but does not create an entire new object) of our `TestUiState` and only changes the parameters specified


###Final update to our TextField:

```
@Composable
fun SimpleInput(viewModel:TestViewModel = viewModel()){
    val textState = viewModel.uiState.value.text
    val errorMessage = viewModel.uiState.value.errorMessage

    Column() {
        TextField(
            //state
            value = textState,
            onValueChange = { viewModel.updateMethod(it) },
            //style
            singleLine = true,
            label = { Text("Label") },
            modifier = Modifier.padding(start = 0.dp,40.dp,0.dp,0.dp),
            textStyle = TextStyle(fontSize = 26.sp),
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Text
            ),
            //errors
            isError = errorMessage !=null,
            trailingIcon = {
                if(errorMessage != null){
                    Icon(Icons.Filled.Error, "error has occurred")
                }
            }

        )
        Button(onClick = { viewModel.validateText(textState) }) {
            Text(text = "Validate")

        }
    }

}
```
- Besides us using a `Column` for styling and a `Button` to run the validateText() method not too much has changed. But I also want to point out the conditionals we added to the `isError` and `trailingIcon`:
```
//errors
            isError = errorMessage !=null,
            trailingIcon = {
                if(errorMessage != null){
                    Icon(Icons.Filled.Error, "error has occurred")
                }
            }
```


###Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on [Twitter](https://twitter.com/AndroidTristan).