article banner

Exercise: CounterContext

Implement a CounterContext class that is a mutable counter and can always be asked for a new value by using the next function. It should be possible to create multiple instances of CounterContext, each of which should have its own counter.

Example usage:

fun main(): Unit = runBlocking(CounterContext()) { println(coroutineContext[CounterContext]?.next()) // 0 println(coroutineContext[CounterContext]?.next()) // 1 launch { println(coroutineContext[CounterContext]?.next())// 2 println(coroutineContext[CounterContext]?.next())// 3 } launch(CounterContext()) { println(coroutineContext[CounterContext]?.next())// 0 println(coroutineContext[CounterContext]?.next())// 1 } }

The simplest way to create a custom context is by creating a class that extends the AbstractCoroutineContextElement class. This class implements the CoroutineContext.Element interface, and it is a good starting point for creating custom contexts. It requires the value that is used as a key for this context. It is recommended to use a companion object for this purpose. Here is an example of a simple context:

class MyContext : AbstractCoroutineContextElement(MyContext){ companion object : CoroutineContext.Key<MyContext> }

This problem can either be solved in the below playground or you can clone kotlin-exercises project and solve it locally. In the project, you can find code template for this exercise in coroutines/context/CounterContext.kt. You can find there example usage and unit tests.

Once you are done with the exercise, you can check your solution here.

Playground

import kotlinx.coroutines.* import org.junit.Test import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.* import kotlin.test.assertEquals // TODO fun main(): Unit = runBlocking(CounterContext()) { println(coroutineContext[CounterContext]?.next()) // 0 println(coroutineContext[CounterContext]?.next()) // 1 launch { println(coroutineContext[CounterContext]?.next())// 2 println(coroutineContext[CounterContext]?.next())// 3 } launch(CounterContext()) { println(coroutineContext[CounterContext]?.next())// 0 println(coroutineContext[CounterContext]?.next())// 1 } } class CounterContextTests { @Test fun `should return next numbers in the same coroutine`() = runBlocking<Unit>(CounterContext()) { assertEquals(0, coroutineContext[CounterContext]?.next()) assertEquals(1, coroutineContext[CounterContext]?.next()) assertEquals(2, coroutineContext[CounterContext]?.next()) assertEquals(3, coroutineContext[CounterContext]?.next()) assertEquals(4, coroutineContext[CounterContext]?.next()) } @Test fun `should have independent counter for each instance`() = runBlocking<Unit> { launch(CounterContext()) { assertEquals(0, coroutineContext[CounterContext]?.next()) assertEquals(1, coroutineContext[CounterContext]?.next()) assertEquals(2, coroutineContext[CounterContext]?.next()) } launch(CounterContext()) { assertEquals(0, coroutineContext[CounterContext]?.next()) assertEquals(1, coroutineContext[CounterContext]?.next()) assertEquals(2, coroutineContext[CounterContext]?.next()) } } @Test fun `should propagate to the child`() = runBlocking<Unit>(CounterContext()) { assertEquals(0, coroutineContext[CounterContext]?.next()) launch { assertEquals(1, coroutineContext[CounterContext]?.next()) launch { assertEquals(2, coroutineContext[CounterContext]?.next()) } launch(CounterContext()) { assertEquals(0, coroutineContext[CounterContext]?.next()) assertEquals(1, coroutineContext[CounterContext]?.next()) launch { assertEquals(2, coroutineContext[CounterContext]?.next()) } } } } }