Reputation: 1
I have a countdown timer. I am using Countdown Timer object and in its onTick function I update my state with remaining time. And then I send an intent to service for updating notification to show remaining time.
Now I have a foreground service too. When the user starts the timer and then minimizes app, the foreground service starts and onTick of timer updates notification with remaining time.
Now if I come back to app by clicking on app icon or via recent apps, the state shows me remaining time.
But if I click the notification and go to my app with Pending intent, the state shows initia value which is empty text. It does not shows remaining time.
Can someone help me so that after opening app from pending intent, the timer continues.
Thanks in advance.
I have tried changing launch mode to singleTask and single instance, but same behaviour.
I have tried using collectLatest.
I have tried creating another timer if the timer before minimization was running, but then two timers updates state.
I am expecting the timer to start from where it left.
This is my viewmodel :
@HiltViewModel
class DashboardViewModel @Inject constructor(
private val wrap: DashboardWrapper,
private val timerUtil : TimerStringLongConverterUtils,
private val timerServiceHelper: TimerServiceHelper
) : ViewModel() {
lateinit var timer : CountDownTimer
private val _state = MutableStateFlow(DashboardState())
val state = _state.asStateFlow()
private var dashboardTimerRunning : Boolean = false
fun habitEvents(e : DashboardHabitEvents){
when(e){
is DashboardHabitEvents.StartStopTimer -> {
when(state.value.timerMode){
TimerModeConstants.STOP -> {
timer.cancel()
if(dashboardTimerRunning){
dashboardTimerRunning = false
timerServiceHelper.stopForegroundService(
TimerServiceConstant.CANCEL.name
)
}
}
TimerModeConstants.START -> {
dashboardTimerRunning = true
var remainingTimeInMills = timerUtil
.convertTimeStringToSeconds(
state.value.remainingTimeString
) * 1000
createTimer(time = remainingTimeInMills)
timer.start()
}
}
}
is DashboardHabitEvents.TimerServiceEvents -> {
when(e.v){
TimerServiceEventConstants.START -> {
if(state.value.
isDashboardTimerRunningForDecidingWhetherToStartOrNotStartTimerService){
timerServiceHelper.triggerForegroundService(
TimerServiceConstant.START.name
)
}
}
TimerServiceEventConstants.STOP -> {
if(dashboardTimerRunning){
dashboardTimerRunning = false
timerServiceHelper.stopForegroundService(
TimerServiceConstant.CANCEL.name
)
}
}
}
}
else -> {}
}
}
private fun createTimer(
time : Long,
){
timer = object : CountDownTimer(time, 1000){
override fun onTick(millisUntilFinished: Long) {
val remainingTimeLong = millisUntilFinished / 1000
val remainingTimeString = timerUtil
.convertTimeLongToString(
remainingTimeLong
)
_state.update {
it.copy(
remainingTimeLong = remainingTimeLong,
remainingTimeString = remainingTimeString,
isDashboardTimerRunningForDecidingWhetherToStartOrNotStartTimerService =
true
)
}
timerServiceHelper.updateNotificationWithTimeString(
state.value.remainingTimeString
)
}
override fun onFinish() {
}
}
}
override fun onCleared() {
super.onCleared()
if(dashboardTimerRunning){
timer.cancel()
}
}
}
```
This is my Timer Service :
@AndroidEntryPoint
class TimerService : Service(){
@Inject
lateinit var notificationM : NotificationManager
@Inject
lateinit var notificationB : NotificationCompat.Builder
private var timerServiceIsRunning : Boolean = false
override fun onBind(intent: Intent?) = null
override fun onUnbind(intent: Intent?): Boolean {
return super.onUnbind(intent)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.action.let { intentAction->
when(intentAction){
TimerServiceConstant.START.name -> {
if(!timerServiceIsRunning){
timerServiceIsRunning = true
createNotificationChannedl()
ServiceCompat.startForeground(
this,
TimerServiceConstant.TIMER_NOTIFICATION_ID.value,
notificationB.build(),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE}else 0
)
}
}
TimerServiceConstant.CANCEL.name -> {
if(timerServiceIsRunning){
timerServiceIsRunning = false
stopTimerService()
}
}
TimerServiceConstant.UPDATE_NOTIFICATION.name -> {
if(timerServiceIsRunning){
val timeToUpdate = intent?.getStringExtra("timer_time")
updateNotification(timeToUpdate!!)
}
}
}
}
return START_NOT_STICKY
}
private fun startTimerService(){
createNotificationChannedl()
startForeground(
TimerServiceConstant.TIMER_NOTIFICATION_ID.value,
notificationB.build())
}
private fun stopTimerService(){
timerServiceIsRunning = false
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
notificationM.cancel(TimerServiceConstant.TIMER_NOTIFICATION_ID.value)
}
private fun updateNotification(timerTime : String){
notificationM.notify(
TimerServiceConstant.TIMER_NOTIFICATION_ID.value,
notificationB.setContentText(timerTime).build()
)
}
private fun createNotificationChannedl(){
val channel = NotificationChannel(
TimerServiceConstant.TIMER_CHANNEL_ID.name,
TimerServiceConstant.TIMER_NOTIFICATION_CHANNEL_NAME.name,
NotificationManager.IMPORTANCE_LOW
)
notificationM.createNotificationChannel(channel)
}
}
This is Service Helper :
class TimerServiceHelper @Inject constructor(
private val appContext: Context
){
private var isTimerserviceRunning : Boolean = false
fun triggerForegroundService(newAction : String){
Intent(appContext, TimerService::class.java).apply {
isTimerserviceRunning = true
this.action = newAction
appContext.startForegroundService(this)
}
}
fun updateNotificationWithTimeString(time : String){
Intent(appContext, TimerService::class.java).apply {
putExtra("timer_time", time)
this.action = TimerServiceConstant.UPDATE_NOTIFICATION.name
appContext.startForegroundService(this)
}
}
fun stopForegroundService(newAction: String){
if(isTimerserviceRunning){
Intent(appContext, TimerService::class.java).apply {
this.action = newAction
appContext.stopService(this)
}
}
}
}
This is Pending Intent Helper Class :
class TimerServicePendingIntentHandler(
private val appContext: Context
) {
fun clickPendingIntent() : PendingIntent {
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
DashboardScreenRoutes.MainDashboardScreen.route.toUri(),
appContext,
MainActivity::class.java
)
return TaskStackBuilder.create(appContext).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(
TimerServiceConstant.TIMER_SERVICE_REQUEST_CODE.value,
PendingIntent.FLAG_IMMUTABLE
)
}
}
}
This is Dagger Class :
@Module
@InstallIn(ServiceComponent::class)
object DaggerStopWatchServiceModule {
@Provides
@ServiceScoped
fun provideNotificationBuilder(
@ApplicationContext app: Context,
pendingIntHand : TimerServicePendingIntentHandler
) : NotificationCompat.Builder{
return NotificationCompat
.Builder(app, TimerServiceConstant.TIMER_CHANNEL_ID.name)
.setContentTitle("Vijaysarthi Timer")
.setContentText("00:00:00")
.setSmallIcon(R.drawable.timer_24px)
.setOngoing(true)
.setContentIntent(pendingIntHand.clickPendingIntent())
}
@ServiceScoped
@Provides
fun provideBaseNotificationManager(
@ApplicationContext app : Context
) : NotificationManager {
return app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
@ServiceScoped
@Provides
fun provideTimerServicePendingIntentHandler(
@ApplicationContext app : Context
) : TimerServicePendingIntentHandler{
return TimerServicePendingIntentHandler(app)
}
}
This is Main Activity :
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()
setContent {
VijaysarthiTheme {
SetupRootNavGraph(navHostController =
rememberNavController()
)
}
}
}
override fun onDestroy() {
Intent([email protected], TimerService::class.java).apply {
this.action = TimerServiceConstant.CANCEL.name
[email protected](this)
}
super.onDestroy()
}
}
Upvotes: 0
Views: 61