Things that I've learned doing a coding challenge in Kotlin (Android)

eronalves1996

EronAlves1996

Posted on November 6, 2023

Things that I've learned doing a coding challenge in Kotlin (Android)

In the past days, I taked a coding challenge, that, in summary, required making a Tic Tac Toe game.

The requirements involves, optionally, target mobile platforms, using Kotlin, Swift or Flutter, but it can be maded using a minimal TUI.

My main language is Java, most of my projects and my study targets Java, and profissionally I worked most with React and Typescript. The logical choice here could be going for TUI with Java or Typescript, but I opt in target mobile platform, and learn Jetpack Compose and Android Development in the proccess.

Here is what I've learned:

The Programming Model

The modern Android development uses Jetpack Compose library for developing UI's. How is it?

It's mainly similar to React model of making UI.

The main artifact that you produce to compose a UI is a Composable. Composables are functions marked with @Composable annotation, that can return an Unit (that's somewhat similar to void) or can return some value, but the majority of composables returns just Unit.

But how is it similar to React if React return objects (behind JSX)?

The DSL is really clear in this aspect. Composables are functions that paint the UI, where you use composition, crafting the UI with other Composables.

Here's an example of a dummy Composable:

@Composable
fun FormSection(
    @StringRes title: Int,
    modifier: Modifier = Modifier,
    children: @Composable (() -> Unit) = {},
) {
    Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
        Text(
            text = stringResource(title),
            fontWeight = FontWeight.Bold,
            fontSize = TextUnit(20.0F, TextUnitType.Sp),
            textAlign = TextAlign.Center
        )
        Spacer(modifier = Modifier.height(5.dp))
        children()
    }
}
Enter fullscreen mode Exit fullscreen mode

This composable is dummy because it don't holds any state or effect in it. Here, it accepts another function, similar to the children declared in React to accept another composable.

"Hooks"

In Jetpack Compose, we don't have any type of hooks, but we have constructs that acts similarly to hooks.

Explicitly, the programming model is builded around observables. Observables are based on the Observer pattern, were we have a set of subscribers, that consists of callbacks. When the Observable receives some update (or some value), it gonna notify every subscriber, by invoking the stored callbacks.

The Jetpack Compose's useState behaves exactly like that. We use delegation to remember function, and the callback function it receives returns a mutableStateOf<T>.

var someState by remember { mutableStateOf(0) }
Enter fullscreen mode Exit fullscreen mode

But, an important thing to see here is that, the main subscriber of this observer is the Composable. When the observer receives some value, the Composable is invoked again in a process called recomposition. Just like React's reconciliation.

Another interesting thing are LaunchedEffects. They are really the useEffect of Jetpack Compose. They are really fit to run some asynchronous rotine, like a Coroutine Timeout to close some type of dialog:

LaunchedEffect(key1 = "Popup") {
  // this is a suspend function call 
  viewModel.makeRobotPlay()
}
Enter fullscreen mode Exit fullscreen mode

The best part of this is, unlike React, LaunchedEffects can run in any part of Composable lifecycle. So we can wrap this in some conditional to run only in a certain condition:

 if (openRobotPlayingTime) {
   LaunchedEffect(key1 = "Popup") {
     viewModel.makeRobotPlay()
   }
   AlertDialog(
     onDismissRequest = { /*TODO*/ }, 
     confirmButton = { /*TODO*/ }, 
     text = {
       Column(
         verticalArrangement = Arrangement.spacedBy(10.dp)
       ) {
         Text(
           text = stringResource(R.string.robot_time_playing_popup_title),
           style = MaterialTheme.typography.titleLarge
         )
         Text(
           text = stringResource(R.string.robot_time_playing_popup_content))
      }
   })
}
Enter fullscreen mode Exit fullscreen mode

Every composable here can be invoked in any order.

Coroutines

Coroutines are core part of Kotlin programming, so here it haves a central utility.

Normally you can use coroutines for every async need, but remember that coroutines are colored functions (just like Promises). So, remember that coroutine only runs in:

  • A CoroutineScope launch callback
  • A LaunchedEffect
  • Another suspend function

To tackle this in a functional and synchronous programming model, Jetpack Compose give us Reactive Programming based upon observables.
I learned the hard way that some observables should not be used on some type of coroutine scope (generally associated on data persistence).
These observables can be used to manage state accross application.

View Model

The view model is where we centralize the application state to manage more complex states in simple data objects. Then, we gonna wrap these objects in StateFlow and make progressive updates on these objects. Every function and composable listening on the StateFlow gonna be notified and updated.

Is really that simple on three steps:

  1. Create a data class that will represent the state of application
data class GameState(
    val player1Name: String? = null,
    val player2Name: String? = null,
    val isRobotEnabled: Boolean = false,
    val tableSize: Int = 3,
    val gameTable: Array<Array<CellStates>>? = null,
    val playerTime: PlayerTime = PlayerTime.Player1,
    val winner: Winner = Winner.NoWinner
)
Enter fullscreen mode Exit fullscreen mode
  1. Create your view model, based on this data class. The object should be wrapped on a StateFlow observable and should be exposed with .asStateFlow() function.
class GlobalStateViewModel(db: AppDatabase) : ViewModel() {
    private val _uiState = MutableStateFlow(GameState())
    val uiState = _uiState.asStateFlow()
}
Enter fullscreen mode Exit fullscreen mode
  1. Subscribe to the view model in Composable using delegation:
@Composable
fun GameScreen(
    viewModel: GlobalStateViewModel, modifier: Modifier = Modifier,
    onNewGame: () -> Unit
) {
    val globalUiState by viewModel.uiState.collectAsState()
}
Enter fullscreen mode Exit fullscreen mode

To update the data on this viewModel, just call the method update on the _uiState. The update should be exposed through a viewModel method:

fun makeUpdate{
  _uiState.update {
    it.copy(
     // update the components of the data class here
    )  
  }
}
Enter fullscreen mode Exit fullscreen mode

Data Access

Data access can be maded with Room Library.
I describe this library as Spring Data without steroids.
We use annotated classes and interfaces to model the data access and data entities of app persistence level.

@Entity 
data class User(@PrimaryKey(autoGenerated = true) val id: Long?, @ColumnInfo(name="name) val name: String?)

@Dao 
interface UserDao {
  @Insert 
  fun create(user: User): Long?
}
Enter fullscreen mode Exit fullscreen mode

Finally, we then define our database with an abstract class that gonna expose the creation of our Dao.

@Database(version = 1, entities=[User::class])
abstract class AppDatabase :  RoomDatabase() {
  abstract fun userDao(): UserDao
}
Enter fullscreen mode Exit fullscreen mode

And that's it.
Just instantiate the database in your code and you are ready to use it.

 val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "tic-tac-toe"
).build()
Enter fullscreen mode Exit fullscreen mode

Initially, when using database, I go through some crashes. It's because the database cannot be accessed on the main thread, to prevent blockings on the UI.

Behind the scenes, Room is just a wrapper around SQLite. So it's just a file, and the interaction with the file is maded with some C functions.

How to access the database without blocking the UI?
You can use a corroutine with Dispatchers.IO dispatcher, like that:

viewModelScope.launch(Dispatchers.IO) {
  dao.create(user)
}
Enter fullscreen mode Exit fullscreen mode

I find that a good way to abstract this is using in viewModel.

Conclusion

That was a good experience, and I learned a lot of things!

The final code for this challenge is here:

https://github.com/EronAlves1996/TicTacToe

pt-BR version: https://github.com/EronAlves1996/artigos-ptbr/blob/main/Coisas-que-aprendi-fazendo-um-desafio-de-codigo-em-kotlin-(android).md

💖 💪 🙅 🚩
eronalves1996
EronAlves1996

Posted on November 6, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related