# Android Fundamentals: RGB Color Mixer

# Introduction

Hi again, dear readers!

In the [last post](https://blog.rmarcos.dev/android-fundamentals-jetpack-compose) we had a brief introduction to Jetpack Compose, and [before that](https://blog.rmarcos.dev/android-fundamentals-app-components) a brief introduction to Android app components. In today's article, we'll build a color mixer app as our first project to have a brief introduction to Android app development.

In this article I'll document my walkthrough and my decisions, I encourage you, dear readers, to explore different approaches for the project and feedback, all suggestions are welcome.

# Project Description

We'll build a color mixer, we'll have three text fields that will accept hexadecimal values for red, green and blue channels respectively. It'll also contain a preview of the resulting color and a label of the color's hex code. This should look like the image below:

![RGB Color Mixer](https://github.com/Tesla-J/rgb-color-mixer/raw/main/screenshots/Screenshot_20260712_162343.png)

Of course, the app must validate inputs and shouldn't crash. To run the app, I'll use a real device because my potato would burn if I try to run the emulator, but [here](https://developer.android.com/studio/run/emulator) you'll find a guide to configure the emulator in your machine.

## Setting Up Your First Project

[Download Android Studio](https://developer.android.com/studio), run it and create a new project. From the options, keep "Empty Activity" selected and press *Next*.

![Android Studio New Project Templates](https://cdn.hashnode.com/uploads/covers/697b6bcf5656f70c5ac1be47/0bedc1c7-1e6d-4ef2-be48-bb0b2dd52c75.png align="center")

Rename "My Application" to "RGB Color Mixer", you can change other details, but be careful when changing **Minimum SDK**, the older, the better for compatibility with older devices. I'm using **Minimum SDK 24** to support more devices. Then press *Finish*, let Gradle finish the sync and you're all done.

![Android Studio Project Details](https://cdn.hashnode.com/uploads/covers/697b6bcf5656f70c5ac1be47/b706f1b4-1530-4e42-8018-ac6c3b33e1b3.png align="center")

Along the projects, we'll explore the project structure when needed, we can ignore it for now.

## Source Code

The default source code has two parts, the Activity declaration and the composable functions.

The default main activity is literally named as `MainActivity`. All Activities must be a subclass of the `Activity` class, as we can observe from the source code below, we have `ComponentActivity`, which is also a subclass of the `Activity class`. If the syntax looks confusing, check for [trailing lambdas](https://kotlinlang.org/docs/lambdas.html#passing-trailing-lambdas).

```Kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            RGBColorMixerTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}
```

The function `setContent` is responsible for setting up the layout. Inside of it there are other function calls, let's focus on the `Greeting` function, that is declared as below by default.

```kotlin
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello $name!",
        modifier = modifier
    )
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    RGBColorMixerTheme {
        Greeting("Android")
    }
}
```

We can notice the annotation `@Composable`, this indicates that it is a **composable**, There is also a second annotation, the `@Preview`.

# Development Process

## Auxiliary Functions

Before paying attention to the UI, I first created functions that would help me handle user input, validate it, properly convert it to `Color` class, and use the color to update the preview.

The `private val HEX_DIGITS = "0123456789ABCDEF"` variable was declared to help to validate hex values and convert their string representation to integer.

The function `isValidChannel` validates the user input by checking the size and the characters it contains.

```Kotlin
private fun isValidChannel(channel: String): Boolean{
    var isValid = true
    if (channel.length !in 1..2)
        return false
    channel.forEach { isValid = isValid && HEX_DIGITS.contains(it.uppercase()) }
    return isValid
}
```

To convert hexadecimal strings to `Int`, I declared an [extension function](https://kotlinlang.org/docs/extensions.html#extension-functions) for `String` class. This is just an adaptation of the famous `atoi` function (C programmers will recognize it), which converts strings to integers.

```Kotlin
private fun String.toColorChannel(): Int{
    var value = 0

    if (!isValidChannel(this))
        return value
    this.forEach {
        value = value.shl(4)
        value += HEX_DIGITS.indexOf(it.uppercase())
    }
    return value
}
```

Finally, to get a `Color` object, I create the `getColor` function. This used bitwise operations to create a valid `Int` representation of a color, that follows the `0xAARRGGBB` hexadecimal pattern. [Here](https://developer.android.com/reference/android/graphics/Color) you'll find the API reference for more details.

```Kotlin
private fun getColor(red: String, green: String, blue: String): Color {
    return Color(0xff.shl(8)
        .or(red.toColorChannel())
        .shl(8)
        .or(green.toColorChannel())
        .shl(8)
        or(blue.toColorChannel())
    )
}
```

## Resources

Resources are files and static content that our code uses, such as images, strings, etc. Always externalize resources, so that we can maintain them separately. Also provide alternative resources, at runtime Android uses the appropriate resource based on current configuration — images for different screen sizes, for example.

Resources are located at `app/src/main/res/` folder, there are different folders for different resources in it. For this project I focused on the `values/` folder, `strings.xml` to be more accurate.

This file is dedicated to string resources, instead of using string literals inside your code, just set a string resource and use `stringResource` to get it inside your code. For example, let's say we have the following string resource:

```XML
<resources>
    <string name="app_name">RGB Color Mixer</string>
    ...
</resources>
```

To access it, we can call `stringResource(R.string.app_name)` to access the string resource. Every string resource has an `Int` ID generated inside of project's `R` class. In this example `R.string.app_name` is the resource ID. 

## Declaring The UI

I first removed 'Greeting' and 'GreetingPreview' declarations and calls, then created a composable to get the user input as follows:

```Kotlin
@Composable
fun ColorTextField(
    modifier: Modifier = Modifier,
    color:String = "",
    isValidColor: Boolean = true,
    label: Int,
    placeholder: Int,
    onValueChange: (String) -> Unit = {},
){
    TextField(
        modifier = modifier.padding(4.dp),
        value = color,
        onValueChange = onValueChange,
        isError = !isValidColor,
        supportingText = {
            if (!isValidColor)
                Text(
                    text = stringResource(R.string.invalid_color),
                    color = MaterialTheme.colorScheme.error
                )
        },
        trailingIcon = {
            if (!isValidColor)
                Icon(
                    imageVector = Icons.Filled.Warning,
                    contentDescription = stringResource(R.string.error_icon),
                    tint = MaterialTheme.colorScheme.error
                )
        },
        singleLine = true,
        label = {
            Text(stringResource(label))
        },
        placeholder = {
            Text(stringResource(placeholder))
        }
    )
}
```

The `TextField` composable is brought by Jetpack Compose, other default composables used in the project are `Button` and `Text`, there are many others, but we'll explore them along future projects. To avoid repetition, I declared a customized `TextField` to reuse common details through `ColorTextField` composable.

It's worth noting that `value` and `onValueChange` must be set in `TextField`. `value` is the current `TextField` content while `onValueChange` is a lambda function that will be executed every time `value` changes. As Jetpack Compose uses a declarative approach and composables can only be updated through recomposition, `remember` API  and `MutableState<T>` objects should be used to trigger it.

To preview a color, the `Surface` composable was used, its height was defined to 1/3 of the screen height.

Modifiers allow us to augment or decorate composables. It's a best practice to have all our composable functions to accept a `modifier` parameter and pass that parameter to the first child that emits UI.

```Kotlin
@Composable
private fun ColorPreview(
    modifier: Modifier = Modifier,
    color: Color,
    label: @Composable () -> Unit = {}
){
    Column(
        modifier = modifier.fillMaxSize().padding(4.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ){
        val screenHeight = LocalConfiguration.current.screenHeightDp.dp
        Surface(
            modifier = Modifier.fillMaxWidth().height(screenHeight / 3),
            color = color
        ){}
        label()
    }
}
```

If you noticed, the code has two interesting composables: `Column` and `Row`. Including `Box` composable, they form the layout composables, making easier to design our UI.

`Column` organizes UI elements vertically, `Row` horizontally, and `Box` stacks elements one above other.

To put things together, I declared the `ColorMixerUI` composable as follows:

```Kotlin
@Preview
@Composable
fun ColorMixerUI(
    modifier: Modifier = Modifier,
){
    var red by rememberSaveable { mutableStateOf("") }
    var green by rememberSaveable { mutableStateOf("") }
    var blue by rememberSaveable { mutableStateOf("") }
    var isValidRed by rememberSaveable { mutableStateOf(true) }
    var isValidGreen by rememberSaveable { mutableStateOf(true) }
    var isValidBlue by rememberSaveable { mutableStateOf(true) }

    Column(
        modifier = modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.SpaceEvenly
    ) {
        Row(
            modifier = Modifier.fillMaxWidth().padding(4.dp),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {
            ColorTextField(
                modifier = Modifier.weight(1f),
                color = red,
                isValidColor = isValidRed,
                label = R.string.red,
                placeholder = R.string.input_red
            ){
                if (it.length < 3 ) {
                    red = it.uppercase()
                    isValidRed = isValidChannel(it)
                }
            }
            ColorTextField(
                modifier = Modifier.weight(1f),
                color = green,
                isValidColor = isValidGreen,
                label = R.string.green,
                placeholder = R.string.input_green
            ){
                if (it.length < 3 ) {
                    green = it.uppercase()
                    isValidGreen = isValidChannel(it)
                }
            }
            ColorTextField(
                modifier = Modifier.weight(1f),
                color = blue,
                isValidColor = isValidBlue,
                label = R.string.blue,
                placeholder = R.string.input_blue
            ){
                if (it.length < 3 ) {
                    blue = it.uppercase()
                    isValidBlue = isValidChannel(it)
                }
            }
        }
        ColorPreview(
            modifier = Modifier,
            color = getColor(red, green, blue),
        ){
            Text(
                modifier = Modifier
                    .padding(8.dp),
                text = "#${getColorString(red, green, blue).uppercase()}",
                fontSize = 24.sp,
                textAlign = TextAlign.Center,
            )
        }
        Button(
            modifier = Modifier.padding(vertical = 8.dp),
            onClick = {
                red = ""
                green = ""
                blue = ""
            }
        ) {
            Text(stringResource(R.string.reset))
        }
    }
}
```

## Handling State

Ignoring my questionable UI design decisions, let's focus on this part of `ColorMixerUI`:

```Kotlin
    var red by rememberSaveable { mutableStateOf("") }
    var green by rememberSaveable { mutableStateOf("") }
    var blue by rememberSaveable { mutableStateOf("") }
    var isValidRed by rememberSaveable { mutableStateOf(true) }
    var isValidGreen by rememberSaveable { mutableStateOf(true) }
    var isValidBlue by rememberSaveable { mutableStateOf(true) }
```

That is the use of the `remember` API and `MutableState<T>` to trigger recomposition and update the app's UI state. `rememberSaveable` is the variant of `remember` that survives configuration changes (eg: screen rotation).

# Final Words

This project is a brief introduction to Android development, you now have an idea on how to put things together and build basic UIs. Check the references for more details, the project repository is also there.

See you in the next post, happy coding!

# References

[RGB Color Mixer Repository](https://github.com/Tesla-J/rgb-color-mixer)

[Jetpack Compose UI App Development Toolkit](https://developer.android.com/compose)

[ComponentActivity API Reference](https://developer.android.com/reference/androidx/activity/ComponentActivity)

[Thinking In Compose](https://developer.android.com/develop/ui/compose/mental-model)

[TextField](https://developer.android.com/develop/ui/compose/text/user-input?textfield=state-based)

[Text](https://developer.android.com/develop/ui/compose/text/display-text)

[Button](https://developer.android.com/develop/ui/compose/components/button)

[Surface In Jetpack Compose](https://hey-agrawal.medium.com/surface-in-jetpack-compose-c0712d38b994)

[Compose Modifiers](https://developer.android.com/develop/ui/compose/modifiers)

[App Resources Overview](https://developer.android.com/guide/topics/resources/providing-resources)

[Compose Layout Basics](https://developer.android.com/develop/ui/compose/layouts/basics)

[State and Jetpack Compose](https://developer.android.com/develop/ui/compose/state)

EOF
