Anton Tretyakov
Anton Tretyakov

Reputation: 355

how to update Composable function when timer is set

Basically, I am pursuing a really trivial task - setting up a timer, change button to "running", when it fires change button back to "not running". It also should work when the app is closed. I cannot figure out how to put together an AlarmManager, BroadcastReceiver and DisposableEffect. It seems like the last to are the keys, but DisposableEffect doest not execute, broadcast is not set and is not thus receiving something.

Probably I am missing some very important pieces of understanding (as a non-Android dev). Would appreciate a good advice!

The lastest implementation is as follows:

package com.example.myapp.presentation

import android.app.Activity.RECEIVER_NOT_EXPORTED
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import java.util.concurrent.TimeUnit


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            TimerScreen()
        }
    }
}

@Composable
fun TimerScreen() {
    var timerRunning by remember { mutableStateOf(false) }
    val updateTimerRunning by rememberUpdatedState { updateWith: Boolean ->
        timerRunning = updateWith
    }
    var broadcast: BroadcastReceiver? by remember { mutableStateOf(null) }
    val updateBroadcast by rememberUpdatedState { updateWith: BroadcastReceiver ->
        broadcast = updateWith
    }
    val context = LocalContext.current

    val totalTimeMillis = TimeUnit.SECONDS.toMillis(5);

    DisposableEffect(context) {
        val filter = IntentFilter()
        filter.priority = 1
        filter.addAction("com.example.myapp.TIMER_EXPIRED")

        updateBroadcast(object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                updateTimerRunning(false);
            }
        })
        context.registerReceiver(broadcast, filter, RECEIVER_NOT_EXPORTED)

        onDispose {
            context.unregisterReceiver(broadcast)
        }
    }

    fun getIntentForAlarm(): PendingIntent {
        val intent = Intent("com.example.myapp.TIMER_EXPIRED")
        return PendingIntent.getBroadcast(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
    }

    fun getAlarm(): AlarmManager {
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        return alarmManager
    }

    fun startTimer() {
        val alarmManager  = getAlarm()
        val pendingIntent = getIntentForAlarm()

        alarmManager.setExactAndAllowWhileIdle(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + totalTimeMillis,
            pendingIntent
        )
        updateTimerRunning(true)
    }

    fun stopTimer() {
        val alarmManager  = getAlarm()
        val pendingIntent = getIntentForAlarm()

        alarmManager.cancel(pendingIntent)
        updateTimerRunning(false)
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = if (timerRunning) "Timer is running..." else "Timer is stopped",
            fontSize = 30.sp,
            textAlign = TextAlign.Center,
            modifier = Modifier.padding(16.dp)
        )

        Button(
            onClick = {
                if (timerRunning) {
                    stopTimer()
                } else {
                    startTimer()
                }
            },
            modifier = Modifier.padding(16.dp)
        ) {
            Text(if (timerRunning) "Stop" else "Start")
        }
    }
}

Upvotes: 0

Views: 38

Answers (0)

Related Questions