How to Keep a Countdowntimer Running Even If the App Is Closed

How to keep a CountDownTimer running even if the app is closed?

Run it in a service as such: use create a broadcast receiver in your activity and have the service send broadcasts.

package com.example.cdt;

import android.app.Service;
import android.content.Intent;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.util.Log;

public class BroadcastService extends Service {

private final static String TAG = "BroadcastService";

public static final String COUNTDOWN_BR = "your_package_name.countdown_br";
Intent bi = new Intent(COUNTDOWN_BR);

CountDownTimer cdt = null;

@Override
public void onCreate() {
super.onCreate();

Log.i(TAG, "Starting timer...");

cdt = new CountDownTimer(30000, 1000) {
@Override
public void onTick(long millisUntilFinished) {

Log.i(TAG, "Countdown seconds remaining: " + millisUntilFinished / 1000);
bi.putExtra("countdown", millisUntilFinished);
sendBroadcast(bi);
}

@Override
public void onFinish() {
Log.i(TAG, "Timer finished");
}
};

cdt.start();
}

@Override
public void onDestroy() {

cdt.cancel();
Log.i(TAG, "Timer cancelled");
super.onDestroy();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}

@Override
public IBinder onBind(Intent arg0) {
return null;
}
}

From the main activity:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

startService(new Intent(this, BroadcastService.class));
Log.i(TAG, "Started service");
}

private BroadcastReceiver br = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateGUI(intent); // or whatever method used to update your GUI fields
}
};

@Override
public void onResume() {
super.onResume();
registerReceiver(br, new IntentFilter(BroadcastService.COUNTDOWN_BR));
Log.i(TAG, "Registered broacast receiver");
}

@Override
public void onPause() {
super.onPause();
unregisterReceiver(br);
Log.i(TAG, "Unregistered broacast receiver");
}

@Override
public void onStop() {
try {
unregisterReceiver(br);
} catch (Exception e) {
// Receiver was probably already stopped in onPause()
}
super.onStop();
}
@Override
public void onDestroy() {
stopService(new Intent(this, BroadcastService.class));
Log.i(TAG, "Stopped service");
super.onDestroy();
}

private void updateGUI(Intent intent) {
if (intent.getExtras() != null) {
long millisUntilFinished = intent.getLongExtra("countdown", 0);
Log.i(TAG, "Countdown seconds remaining: " + millisUntilFinished / 1000);
}
}

Note I got this code from How to run CountDownTimer in a Service in Android?, which I modified for my own android countDownTimer.

CountDownTimer problem when app is closed

UPDATED

Below is your code converted into a code snippet for a CountdownTimer which will keep working even when the app is closed, pushed to background or restarted.

set START_TIME_IN_MILLIS as the Timer start time, in the following example it is set to 15 seconds.

import android.content.SharedPreferences;
import android.os.CountDownTimer;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Locale;

public class MainActivity2 extends AppCompatActivity {
private static final long START_TIME_IN_MILLIS = 15000;
private TextView mTextViewCountDown;
private CountDownTimer mCountDownTimer;
private boolean mTimerRunning;
private long mTimeLeftInMillis;
private long mEndTime;
private long remainingTimeInMillis;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new);

mTextViewCountDown = findViewById(R.id.tv);
}

private void startTimer() {
mCountDownTimer = new CountDownTimer(remainingTimeInMillis, 1000) {
@Override
public void onTick(long millisUntilFinished) {
remainingTimeInMillis = millisUntilFinished;
mTimeLeftInMillis = millisUntilFinished;
updateCountDownText();
}

@Override
public void onFinish() {
//mTimerRunning = false;
//updateButtons();

updateCountDownText();
resetTimer();
startTimer();

}
}.start();

//mTimerRunning = true;

}

private void resetTimer() {
remainingTimeInMillis = START_TIME_IN_MILLIS;
updateCountDownText();

}

private void updateCountDownText() {

int minutes = (int) (remainingTimeInMillis / 1000) / 60;
int seconds = (int) (remainingTimeInMillis / 1000) % 60;

String timeLeftFormatted = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);

mTextViewCountDown.setText(timeLeftFormatted);
}

@Override
protected void onStop() {
super.onStop();

SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();

editor.putLong("millisLeft", mTimeLeftInMillis);
editor.putBoolean("timerRunning", mTimerRunning);
editor.putLong("endTime", System.currentTimeMillis());
editor.apply();

}

@Override
protected void onStart() {
super.onStart();

SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);

mTimeLeftInMillis = prefs.getLong("millisLeft", START_TIME_IN_MILLIS);
mTimerRunning = prefs.getBoolean("timerRunning", false);
mEndTime = prefs.getLong("endTime", 0);
if (mEndTime == 0L) {
remainingTimeInMillis = (mTimeLeftInMillis);
} else {
Long timeDiff = (mEndTime - System.currentTimeMillis());
//to convert into positive number
timeDiff = Math.abs(timeDiff);

long timeDiffInSeconds = (timeDiff / 1000) % 60;
long timeDiffInMillis = timeDiffInSeconds * 1000;
Long timeDiffInMillisPlusTimerRemaining = remainingTimeInMillis = mTimeLeftInMillis - timeDiffInMillis;

if (timeDiffInMillisPlusTimerRemaining < 0) {
timeDiffInMillisPlusTimerRemaining = Math.abs(timeDiffInMillisPlusTimerRemaining);
remainingTimeInMillis = START_TIME_IN_MILLIS - timeDiffInMillisPlusTimerRemaining;
}
}
updateCountDownText();
startTimer();
}
}

How can I keep a countdown timer running even if I don't open the app for a long time?

You don't. You persist the time the countdown counts toward when the user starts it. If the application closes,then the user opens the application again, you set up the CountDownTimer component with the proper remaining time. If you want to do something when the countdown ends, even when the user is not using the application, you can use AlarmManager to wake up the device.

HOW TO KEEP TIMER RUNNING WHEN CLOSING THE APP (Background) IN A FRAGMENT

For running tasks after app is killed consider using background services in android

How can I keep a Timer running when the app is closed? (Kotlin)

the best thing you can do is create a Background service.
In this youtube video teach how to make a tamer with a background service
https://www.youtube.com/watch?v=lvibl8YJfGo

And look at this code as a example:

package playstore.com.a02backgroundtimer;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.util.Log;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

public class Timer_Service extends Service {

public static String str_receiver = "com.countdowntimerservice.receiver";

private Handler mHandler = new Handler();
Calendar calendar;
SimpleDateFormat simpleDateFormat;
String strDate;
Date date_current, date_diff;
SharedPreferences mpref;
SharedPreferences.Editor mEditor;

private Timer mTimer = null;
public static final long NOTIFY_INTERVAL = 1000;
Intent intent;

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();

mpref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
mEditor = mpref.edit();
calendar = Calendar.getInstance();
simpleDateFormat = new SimpleDateFormat("HH:mm:ss");

mTimer = new Timer();
mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 5, NOTIFY_INTERVAL);
intent = new Intent(str_receiver);
}

class TimeDisplayTimerTask extends TimerTask {

@Override
public void run() {
mHandler.post(new Runnable() {

@Override
public void run() {

calendar = Calendar.getInstance();
simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
strDate = simpleDateFormat.format(calendar.getTime());
Log.e("strDate", strDate);
twoDatesBetweenTime();

}

});
}

}

public String twoDatesBetweenTime() {

try {
date_current = simpleDateFormat.parse(strDate);
} catch (Exception e) {

}

try {
date_diff = simpleDateFormat.parse(mpref.getString("data",""));
} catch (Exception e) {

}

try {

long diff = date_current.getTime() - date_diff.getTime();
int int_hours = Integer.valueOf(mpref.getString("hours", ""));

long int_timer = TimeUnit.HOURS.toMillis(int_hours);
long long_hours = int_timer - diff;
long diffSeconds2 = long_hours / 1000 % 60;
long diffMinutes2 = long_hours / (60 * 1000) % 60;
long diffHours2 = long_hours / (60 * 60 * 1000) % 24;

if (long_hours > 0) {
String str_testing = diffHours2 + ":" + diffMinutes2 + ":" + diffSeconds2;

Log.e("TIME", str_testing);

fn_update(str_testing);
} else {
mEditor.putBoolean("finish", true).commit();
mTimer.cancel();
}
} catch (Exception e) {
mTimer.cancel();
mTimer.purge();

}

return "";

}

@Override
public void onDestroy() {
super.onDestroy();
Log.e("Service finish", "Finish");
}

private void fn_update(String str_time) {

intent.putExtra("time", str_time);
sendBroadcast(intent);
}
}

Countdown timer not run when user close application

You need to return START_STCKY in your onStartCommand() method for the service to run even when app is closed.

 ....
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STCKY;
}
....

You can refer thislink for correctly implementing a service.

Alternatively, you can refer this SO question.

Update
Use a Foreground Service to avoid your Service being killed. In order to make your service Foreground, replace your onStartCommand code with this

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);

Notification notification = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Timer")
.setContentText("Doing some work...")
.setContentIntent(pendingIntent).build();

startForeground(1337, notification);

cdt = new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {

bi.putExtra("countdown", millisUntilFinished);
sendBroadcast(bi);
}

public void onFinish() {
stopForeground(true);
}
};
cdt.start();
return START_STICKY;
}

Udpate 2: Counter using Service and SharedPreferences

Replace your Actvity's code with this:

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.support.v4.os.ResultReceiver;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import java.util.Calendar;
import java.util.Date;

public class MainActivity extends AppCompatActivity {

private static final String TAG = MainActivity.class.getSimpleName();
private static final String SHARED_PREF = "MyPref";
private final static int MAX_COUNTER = 30;

public static final String KEY_COUNTER_SECONDS = "seconds";
public static final String KEY_SAVED_COUNTER = "saved_counter";
public static final String KEY_SAVED_TIME_MILLI = "saved_time_milli";

MyResultReceiver mReceiver;
TextView mTvCounter;
SharedPreferences mSharedPref;
long mMaxCounterValueInSeconds = MAX_COUNTER;
long mCurCounterValue = 0;
boolean mShouldSaveValues;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvCounter = (TextView) findViewById(R.id.tv_counter);
mReceiver = new MyResultReceiver(null);
mSharedPref = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
}

@Override
protected void onResume() {
super.onResume();

//register listener
MyService.registerReceiver(mReceiver);

//get values from shared pref
long savedCounter = mSharedPref.getLong(KEY_SAVED_COUNTER, -1);
long savedTime = mSharedPref.getLong(KEY_SAVED_TIME_MILLI, -1);

//if -1 counter was running when app was closed, get saved values from shared pref
if (savedTime != -1) {
//elapsedTime is the time spent in seconds while the app was in background
long elapsedTime = (getCurrentTimeInMilli() - savedTime)/1000; //convert to sec

mCurCounterValue = savedCounter + elapsedTime;

if(mCurCounterValue < MAX_COUNTER){
//calculate current counter value from values retrieved from shared pref
mMaxCounterValueInSeconds = MAX_COUNTER - mCurCounterValue;
//start the value with updated max count value
startService(mMaxCounterValueInSeconds);
}else{
mCurCounterValue = MAX_COUNTER;
}
}else{
//if counter was not running, start the service with max count value = MAX_COUNTER
startService(mMaxCounterValueInSeconds);
}

//update text view
mTvCounter.setText("" + mCurCounterValue);
}

private void startService(long maxCounter){
mShouldSaveValues = true;

Intent intent = new Intent(this, MyService.class);
Bundle bundle = new Bundle();
bundle.putLong(KEY_COUNTER_SECONDS, maxCounter);
intent.putExtras(bundle);
startService(intent);
}
@Override
protected void onPause() {
super.onPause();

//stop the service
stopService(new Intent(this, MyService.class));
//unregister listener
MyService.unregisterReceiver();

if(mShouldSaveValues) {//save the values only when counter has started
//save values in the shared preference
SharedPreferences.Editor editor = mSharedPref.edit();
Log.d(TAG, "saving counter: " + Long.parseLong(mTvCounter.getText().toString()));
editor.putLong(KEY_SAVED_COUNTER, Long.parseLong(mTvCounter.getText().toString()));
editor.putLong(KEY_SAVED_TIME_MILLI, getCurrentTimeInMilli());
editor.apply();
}
}

/**
* This method returns current time in milli seconds
*
* @return time in milliseconds
*/
private long getCurrentTimeInMilli() {
Calendar cal = Calendar.getInstance();
Date date = cal.getTime();
long timeInMilli = date.getTime();
return timeInMilli;
}

/**
* ResultReceiver is used to get values from MyService.class
* It is registered in onResume() &
* unregistered in onPause()
*/
class MyResultReceiver extends ResultReceiver {

public MyResultReceiver(Handler handler) {
super(handler);
}

@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
String strMilliFinished = resultData.getString(MyService.KEY_MSG);
updateUI(Long.parseLong(strMilliFinished));
}

private void updateUI(final long milliFinished) {
runOnUiThread(new Runnable() {
@Override
public void run() {

mCurCounterValue++;
mTvCounter.setText("" + mCurCounterValue);

if(milliFinished == 0) {
//resetting counter values

mShouldSaveValues = false;
mMaxCounterValueInSeconds = MAX_COUNTER;
mCurCounterValue = 0;

SharedPreferences.Editor editor = mSharedPref.edit();
editor.putLong(KEY_SAVED_COUNTER, -1);
editor.putLong(KEY_SAVED_TIME_MILLI, -1);
editor.apply();

}
}
});
}
}
}

Replace your Service code with this:

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.support.v4.os.ResultReceiver;
import android.util.Log;

public class MyService extends Service {

public static final String KEY_MSG = "msg";

CountDownTimer cdt = null;
private static ResultReceiver mReceiver;

public MyService() {
}

public static void registerReceiver(ResultReceiver receiver) {
mReceiver = receiver;
}

public static void unregisterReceiver() {
mReceiver = null;

}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

Bundle bundle = intent.getExtras();
long maxCounterValueInSeconds = bundle.getLong(MainActivity.KEY_COUNTER_SECONDS);
long maxCounter = maxCounterValueInSeconds * 1000;
cdt = new CountDownTimer(maxCounter, 1000) {
public void onTick(long millisUntilFinished) {

sendMessage(1, "" + millisUntilFinished);
}

public void onFinish() {
sendMessage(1, "" + 0);
stopSelf();
}
};
cdt.start();
return START_STICKY;
}

private void sendMessage(int resultCode, String message) {

if (mReceiver != null) {
Bundle bundle = new Bundle();
bundle.putString(KEY_MSG, message);
mReceiver.send(resultCode, bundle);
}
}

@Override
public void onDestroy() {
super.onDestroy();
cdt.cancel();
}

@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}

Note: I am using ResultReceiver instead of BroadcastReciver

Countdown timer which runs in the background in android

Add to your AndroidManifest.xml

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

BroadcastReceiver.java

public class BroadcastReceiver extends AppCompatActivity {

TextView tvTimer, tvTimerRunningState, tvTimerFinishedState;
private static final String TAG = "CountdownTimer";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_broadcast_receiver);
}
public void handleStartTimer(View view) {
Intent intent = new Intent(this, BroadcastService.class);
intent.putExtra("inputExtra", "");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ContextCompat.startForegroundService(this, intent);
} else {
this.startService(intent);
}
Log.i(TAG, "timerStarted");
}
public void handleCancelTimer (View view) {
Intent intent = new Intent(this, BroadcastService.class);
stopService(intent);
}
/* CountDown */
final private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateGUI(intent);
}
};

@Override
public void onResume() {
super.onResume();
registerReceiver(broadcastReceiver, new IntentFilter(BroadcastService.COUNTDOWN_BR));
Log.i(TAG, "Registered broadcast receiver");
}

@Override
public void onPause() {
super.onPause();
unregisterReceiver(broadcastReceiver);
Log.i(TAG, "Unregistered broadcast receiver");
}

@Override
public void onStop() {
try {
unregisterReceiver(broadcastReceiver);
} catch (Exception e) {
// Receiver was probably already stopped in onPause()
}
super.onStop();
}

private void updateGUI(Intent intent) {
if (intent.getExtras() != null) {
long millisUntilFinished = intent.getLongExtra("countdown", 0);
long seconds = (millisUntilFinished / 1000) % 60;
long minutes = (millisUntilFinished / (1000*60)) % 60;
long hours = (millisUntilFinished / (1000*60*60)) % 60;
String time = (hours + " : " + minutes + " : " + seconds);
tvTimer = findViewById(R.id.tvTimer);
tvTimer.setText(time);

boolean countdownTimerRunning = intent.getBooleanExtra("countdownTimerRunning", false);
tvTimerRunningState = findViewById(R.id.tvTimerRunningState);
if (countdownTimerRunning) {
tvTimerRunningState.setText("CountdownTimerRunning");
} else {
tvTimer.setText("0 : 0 : 0");
tvTimerRunningState.setText("CountdownTimerNotRunning");
}

boolean countdownTimerFinished = intent.getBooleanExtra("countdownTimerFinished", false);
tvTimerFinishedState = findViewById(R.id.tvTimerFinishedState);
if (countdownTimerFinished) {
tvTimerFinishedState.setText("Finished");
} else {
tvTimerFinishedState.setText("Unfinished");
}
}
}

activity_broadcast_receiver.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/btnStartJob"
android:onClick="handleStartTimer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Timer" />
<Button
android:id="@+id/btnStopJob"
android:onClick="handleCancelTimer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Cancel Timer" />
<TextView
android:id="@+id/tvTimer"
android:text="0 : 0 : 0"
android:gravity="center"
android:textSize="30sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tvTimerFinishedState"
android:gravity="center"
android:textSize="20sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tvTimerRunningState"
android:gravity="center"
android:textSize="18sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

BroadcastService.java

public class BroadcastService extends Service {

public static final String CHANNEL_ID = "ForegroundServiceChannel";
private final static String TAG = "BroadcastService";
public static final String COUNTDOWN_BR = "your.package.name";
Intent bi = new Intent(COUNTDOWN_BR);

CountDownTimer cdt = null;

@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Starting timer...");
cdt = new CountDownTimer(30000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
Log.i(TAG, "Countdown seconds remaining: " + millisUntilFinished / 1000);
bi.putExtra("countdown", millisUntilFinished);
bi.putExtra("countdownTimerRunning", true);
bi.putExtra("countdownTimerFinished", false);
sendBroadcast(bi);
}
@Override
public void onFinish() {
Log.i(TAG, "Timer finished");
bi.putExtra("countdownTimerFinished", true);
sendBroadcast(bi);
stopForeground(true);
stopSelf();
}
}; cdt.start();

}

@Override
public void onDestroy() {
cdt.cancel();
Log.i(TAG, "Timer cancelled");
bi.putExtra("countdownTimerRunning", false);
sendBroadcast(bi);
super.onDestroy();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
/* Notification */
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent(this, BroadcastReceiver.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
/* NotificationBuilder */
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
return START_NOT_STICKY;
}

@Nullable
@Override
public IBinder onBind(Intent arg0) {
return null;
}

private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}


Related Topics



Leave a reply



Submit