I have implemented a timer in Jetpack Compose. It counts down a given amount of seconds. The remaining seconds are displayed. Moreover the past and remaining seconds are displayed as a circular graph. The user can start, pause and reset the timer.
Finally it works fine, now. But I'm asking myself if I use Coroutine dispatchers correct.
Here's my code:
@SuppressLint("SimpleDateFormat")
fun formatTime(time: Long): String {
  return SimpleDateFormat("mm:ss").format(Date(time * 1000))
}
@Composable
fun TimerScreen(modifier: Modifier = Modifier) {
  val initTime = 30 L
  var startedAt by remember {
    mutableLongStateOf(System.currentTimeMillis())
  }
  var remainingTime by remember {
    mutableLongStateOf(initTime)
  }
  var isRunning by remember {
    mutableStateOf(false)
  }
  var progress by remember {
    mutableFloatStateOf(1.0 f)
  }
  LaunchedEffect(key1 = startedAt) {
    withContext(Dispatchers.IO) {
      while (remainingTime > 0) {
        if (isRunning) {
          delay(1000)
          withContext(Dispatchers.Main) {
            remainingTime -= 1
            progress -= 0.03333334 f
          }
        }
      }
    }
  }
  Column(
    modifier = Modifier.fillMaxSize(),
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.Center
  ) {
    Box(contentAlignment = Alignment.Center) {
      CircularProgressIndicator(
        progress = {
          progress
        },
        modifier = Modifier.size(160. dp),
        color = Color.Green,
        strokeWidth = 12. dp,
        trackColor = Color.LightGray
      )
    }
    Spacer(modifier = Modifier.height(10. dp))
    Text(text = formatTime(remainingTime),
      fontSize = 32. sp, fontWeight = FontWeight.Bold)
    Spacer(modifier = Modifier.height(20. dp))
    Row {
      IconButton(onClick = {
        isRunning = !isRunning
      }, modifier = Modifier, enabled = remainingTime > 0) {
        val icon =
          if (isRunning)
            R.drawable.pause_icon
        else
          R.drawable.play_icon
        Icon(painter = painterResource(id = icon),
          modifier = Modifier.size(50. dp),
          contentDescription = "")
      }
      IconButton(onClick = {
        remainingTime = initTime
        startedAt = System.currentTimeMillis()
        isRunning = false
        progress = 1.0 f
      }) {
        Icon(painter = painterResource(id = R.drawable.refresh_icon),
          modifier = Modifier.size(50. dp),
          contentDescription = "")
      }
    }
  }
}
- Is my usage of LaunchedEffect correct? Or should I use something else? 
- Is my Dispatchers-usage correct? 
Switching to IO was necessary. Running it from Main crashed the app. I switch back to Main, because I'm not sure, if it is a good idea to update the data from a background-thread. A data-update from a background-thread might lead to problems? What's your opinion?
Looking forward to reading your comments and answers.


