Implementing Coroutines using Jetpack

This tutorial guides you through using Kotlin coroutines in Jetpack Compose to create a responsive ticker timer for smooth UI updates.

July 27, 20245 min read

Introduction

In modern Android development, managing asynchronous tasks efficiently is crucial for creating responsive and smooth applications. Kotlin coroutines have emerged as a powerful solution for handling concurrency, providing a more straightforward and concise way to manage background operations compared to traditional threading mechanisms. Jetpack Compose, Android's new declarative UI toolkit, integrates seamlessly with coroutines, allowing developers to handle asynchronous tasks within their UI code effectively.

Jetpack Compose offers a modern approach to building user interfaces by using Kotlin's language features and a reactive programming model. With its declarative nature, Compose makes it easy to describe UI components and their state, enabling more readable and maintainable code. When combined with Kotlin coroutines, Jetpack Compose can manage background operations such as network requests, database interactions, and time-based tasks without blocking the main thread.

One common use case for coroutines in Jetpack Compose is managing a ticker timer, which involves periodically updating the UI based on a time interval. This can be useful in various applications, such as displaying a countdown timer, updating live scores, or showing elapsed time. Implementing a ticker timer with coroutines ensures that the UI remains responsive and updates smoothly, providing a better user experience.

In this tutorial, we will walk you through the process of creating a simple ticker timer using Jetpack Compose and Kotlin coroutines. We will cover setting up the project, creating a ViewModel to manage the timer state, and building the Compose UI to display the timer. By the end of this tutorial, you will have a solid understanding of how to use coroutines in Jetpack Compose to handle time-based tasks efficiently.

Prerequisites

Before starting, ensure you have the following:

  1. Basic Knowledge of Kotlin: Understanding Kotlin syntax and basic concepts.
  2. Android Studio Installed: Latest version with Jetpack Compose support.
  3. Jetpack Compose Setup: A new or existing project with Jetpack Compose configured.

Try Kodaschool for free

Click below to sign up and get access to free web, android and iOs challenges.

Sign Up

Step-by-Step Tutorial

Step 1: Set Up Your Project

First, ensure your project is set up for Jetpack Compose. Add the necessary dependencies in your build.gradle (app level):


dependencies {
    ...
    implementation (libs.androidx.lifecycle.viewmodel.compose)
    implementation (libs.kotlinx.coroutines.core)
    implementation(libs.kotlinx.coroutines.android)
    ...
}

Step 2: Create the ViewModel Class

- Navigate to Your Project Directory: In Android Studio, navigate to the kotlin + java directory within your src folder. Typically, this is found in app/src/main/java/com/yourprojectname/.

- Create a New Package for ViewModels: It is recommended to organize your code into packages. Right-click on the kotlin +java directory, select New > Package, and name it viewmodel.

- Create the ViewModel Class:

Right-click on the viewmodel package, select New > Kotlin Class/File, and name it TimerViewModel.

package com.example.tickertimer.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class TimerViewModel : ViewModel() {
    private val _time = MutableStateFlow(0)
    val time: StateFlow<Int> = _time.asStateFlow()

    private val _isRunning = MutableStateFlow(true)
    val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()

    init {
        startTimer()
    }

    private fun startTimer() {
        viewModelScope.launch {
            while (true) {
                delay(1000L) // 1 second delay
                if (_isRunning.value) {
                    _time.update { it + 1 }
                }
            }
        }
    }

    fun toggleTimer() {
        _isRunning.update { !it }
    }
}

Step 3: Create the Compose UI

Create a Composable function to display the timer.

package com.example.tickertimer.ui.theme

import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.tickertimer.viewmodel.TimerViewModel

@Composable
fun TimerScreen() {
    val timerViewModel: TimerViewModel = viewModel()
    val time = timerViewModel.time.collectAsState()
    val isRunning = timerViewModel.isRunning.collectAsState()

    Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
        Column(
            modifier = Modifier.fillMaxSize().padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Text(text = "Time: ${time.value} seconds")
            Spacer(modifier = Modifier.height(16.dp))
            Button(onClick = { timerViewModel.toggleTimer() }) {
                Text(text = if (isRunning.value) "Pause" else "Resume")
            }
        }
    }
}

Step 4: Set Up the Main Activity

Set up the MainActivity to display the TimerScreen Composable.

package com.example.tickertimer

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.tickertimer.ui.theme.TickerTimerTheme
import com.example.tickertimer.ui.theme.TimerScreen

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            TickerTimerTheme {
                TimerScreen()

            }
        }
    }
}

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

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    TickerTimerTheme {
       TimerScreen()
    }
}

Step 5: Define the Theme

Define the Compose theme in a separate file.One advantage of using Android Studio is that theme package is automatically generated containing Color.kt,Theme.kt and Type.kt

Theme.kt file

package com.example.tickertimer.ui.theme

import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext

private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
    primary = Purple40,
    secondary = PurpleGrey40,
    tertiary = Pink40

    /* Other default colors to override
    background = Color(0xFFFFFBFE),
    surface = Color(0xFFFFFBFE),
    onPrimary = Color.White,
    onSecondary = Color.White,
    onTertiary = Color.White,
    onBackground = Color(0xFF1C1B1F),
    onSurface = Color(0xFF1C1B1F),
    */
)

@Composable
fun TickerTimerTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

Step 6: Run the App

When you run your application,You should see a timer that increments every second and a button that toggles between "Pause" and "Resume" to control the timer's state.

Pause the timer display

Image

Resume timer display

Image

Conclusion

In this tutorial, we explored how to use Kotlin coroutines to implement a ticker timer in Jetpack Compose. We covered setting up the project, creating a ViewModel to manage the timer state, and building the Compose UI to display the timer. By following these steps, you can leverage Kotlin coroutines to handle time-based tasks efficiently within your Jetpack Compose applications.

Using coroutines in Jetpack Compose not only simplifies the management of asynchronous tasks but also ensures that the UI remains responsive and smooth. This powerful combination can significantly enhance the user experience in your Android applications, making them more robust and enjoyable to use.

Wilson Ochieng

About Wilson Ochieng

Android Expert developing mobile applications with 4+ Years of Experience.