Как я могу реализовать таймер портативным способом в Jetpack Compose?

Есть приложения, которые я хотел бы написать, в которых я хотел бы, чтобы некоторые вещи происходили по расписанию.

Опрос URL-адреса на наличие обновлений каждые несколько минут кажется довольно распространенным вариантом использования. Однако в данном конкретном случае я просто пытаюсь реализовать часы.

Это работает:

      @Composable
fun App() {
    var ticks by remember { mutableStateOf(0) }

    // Not 100% happy about this unused variable either
    val timer = remember {
        Timer().apply {
            val task = object : TimerTask() {
                override fun run() {
                    ticks++
                }
            }
            scheduleAtFixedRate(task, 1000L, 1000L)
        }
    }

    MaterialTheme {
        Text(
            // A real application would format this number properly,
            // but that's a different question
            text = "$ticks"
        )
    }
}

Но мне пришлось импортировать java.util.Timer, поэтому он не будет переносимым.

Jetpack Compose умеет делать анимацию, поэтому у него наверняка где- то есть собственный таймер , подразумевая, что должен быть какой-то портативный способ сделать это, но я не могу понять это.

Есть ли кроссплатформенный способ получить таймер для этой цели?

3 ответа

В Compose вы можете использовать LaunchedEffect- это побочный эффект, который запускается в области сопрограммы, поэтому вы можете использовать delayвнутри вот так:

      var ticks by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
    while(true) {
        delay(1.seconds)
        ticks++
    }
}

Я просто хотел поделиться альтернативой, с которой я экспериментировал, на случай, если кто-то еще подумает об этом и столкнется с теми же проблемами, что и я. Вот наивная реализация:

      @Composable
fun Countdown(targetTime: Long, content: @Composable (remainingTime: Long) -> Unit) {
    var remainingTime by remember(targetTime) {
        mutableStateOf(targetTime - System.currentTimeMillis())
    }

    content.invoke(remainingTime)

    LaunchedEffect(remainingTime) {
        delay(1_000L)
        remainingTime = targetTime - System.currentTimeMillis()
    }
}

Предполагая, что вам нужна точность до секунды, этот фрагмент заставит обновиться через секунду после обновления, обновляя его по отношению к текущему времени в миллисекундах. Это в основном создает петлю. Оборачивая эту логику вэто хорошо, поскольку предотвращает чрезмерную рекомпозицию, которая может возникнуть, если вы встроите свой в большое дерево компонентов.

Это работает, но есть одна загвоздка: в конце концов вы заметите, что ваш таймер пропускает секунды. Это происходит потому, что будет некоторая дополнительная задержка между присвоением нового значенияпеременная, и повторное выполнение которой будет означать, что между обновлениями будет больше секунды.

Вот улучшенная реализация вышеизложенного:

      @Composable
fun Countdown(targetTime: Long, content: @Composable (remainingTime: Long) -> Unit) {
    var remainingTime by remember(targetTime) {
        mutableStateOf(targetTime - System.currentTimeMillis())
    }

    content.invoke(remainingTime)

    LaunchedEffect(remainingTime) {
        val diff = remainingTime - (targetTime - System.currentTimeMillis())
        delay(1_000L - diff)
        remainingTime = targetTime - System.currentTimeMillis()
    }
}

Мы просто вычитаем время, затраченное надля повторного выполнения с предполагаемого времени задержки. Это позволит избежать пропуска секунд вашего таймера.

Дополнительная задержка не должна быть проблемой для реализации в принятом ответе. Единственное преимущество этого подхода, которое я заметил, заключается в том, что цикл перестанет работать, как только мы уйдем от экрана. В моих тестах цикл while сУсловие продолжало работать при переходе к другому действию.

      @Composable
fun TimerTicks(
    initTick: Long = 1_000L,
    interval: Long = 1_000L,
    content: @Composable (tickTime: Long) -> Unit
) {

    var ticks by remember(initTick) {
        mutableStateOf(initTick)
    }

    content.invoke(ticks)

    LaunchedEffect(ticks) {
        val diff = ticks + interval
        delay(interval)
        ticks = diff
    }
}
Другие вопросы по тегам