I spent my summer learning how to code and building my first app (in android studio). Therefore I am not an expert coder. This week I encountered a problem. Basically, I am developing a quiz app and when the user gets the answer wrong he have to wait 5 min. The thing is that when the countdowntimer starts and the user closes the app (by closing the app I mean destroy it and not just push the home button) the countdowntimer stops. I am wondering how I can manage to keep the timer running even if the app is closed. Your help will be greatly appreciated and it would be fantastic if you guys give me a sample code. Thanks in advance!
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.
Download source code from here Android Countdown Timer Run In Background
activity_main.xml
<RelativeLayout android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/et_hours"
android:hint="Hours"
android:inputType="time"
android:layout_marginRight="5dp"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_timer"
android:layout_above="@+id/btn_cancel"
android:text="Start Timer"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:id="@+id/btn_cancel"
android:text="cancel timer"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_timer"
android:layout_centerInParent="true"
android:textSize="25dp"
android:textColor="#000000"
android:text="00:00:00"/>
</RelativeLayout>
MainActivity.java
package com.countdowntimerservice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_start, btn_cancel;
private TextView tv_timer;
String date_time;
Calendar calendar;
SimpleDateFormat simpleDateFormat;
EditText et_hours;
SharedPreferences mpref;
SharedPreferences.Editor mEditor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
listener();
}
private void init() {
btn_start = (Button) findViewById(R.id.btn_timer);
tv_timer = (TextView) findViewById(R.id.tv_timer);
et_hours = (EditText) findViewById(R.id.et_hours);
btn_cancel = (Button) findViewById(R.id.btn_cancel);
mpref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
mEditor = mpref.edit();
try {
String str_value = mpref.getString("data", "");
if (str_value.matches("")) {
et_hours.setEnabled(true);
btn_start.setEnabled(true);
tv_timer.setText("");
} else {
if (mpref.getBoolean("finish", false)) {
et_hours.setEnabled(true);
btn_start.setEnabled(true);
tv_timer.setText("");
} else {
et_hours.setEnabled(false);
btn_start.setEnabled(false);
tv_timer.setText(str_value);
}
}
} catch (Exception e) {
}
}
private void listener() {
btn_start.setOnClickListener(this);
btn_cancel.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_timer:
if (et_hours.getText().toString().length() > 0) {
int int_hours = Integer.valueOf(et_hours.getText().toString());
if (int_hours<=24) {
et_hours.setEnabled(false);
btn_start.setEnabled(false);
calendar = Calendar.getInstance();
simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
date_time = simpleDateFormat.format(calendar.getTime());
mEditor.putString("data", date_time).commit();
mEditor.putString("hours", et_hours.getText().toString()).commit();
Intent intent_service = new Intent(getApplicationContext(), Timer_Service.class);
startService(intent_service);
}else {
Toast.makeText(getApplicationContext(),"Please select the value below 24 hours",Toast.LENGTH_SHORT).show();
}
/*
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 5, NOTIFY_INTERVAL);*/
} else {
Toast.makeText(getApplicationContext(), "Please select value", Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_cancel:
Intent intent = new Intent(getApplicationContext(),Timer_Service.class);
stopService(intent);
mEditor.clear().commit();
et_hours.setEnabled(true);
btn_start.setEnabled(true);
tv_timer.setText("");
break;
}
}
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String str_time = intent.getStringExtra("time");
tv_timer.setText(str_time);
}
};
@Override
protected void onResume() {
super.onResume();
registerReceiver(broadcastReceiver,new IntentFilter(Timer_Service.str_receiver));
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(broadcastReceiver);
}
}
Timer_Service.java
package com.countdowntimerservice;
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);
}
}
Thanks!
To keep the timer running while the app is closed, you need to use a service.
import android.app.Service
import android.content.Intent
import android.os.CountDownTimer
import android.os.IBinder
import java.util.concurrent.TimeUnit
class TimerService : Service() {
private val finishedIntent = Intent(ACTION_FINISHED)
private val tickIntent = Intent(ACTION_TICK)
private lateinit var timer: CountDownTimer
override fun onCreate() {
timer = createTimer()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
timer.start()
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
timer.cancel()
}
private fun createTimer(): CountDownTimer =
object : CountDownTimer(COUNTDOWN_LENGTH, COUNTDOWN_INTERVAL) {
override fun onTick(millisUntilFinished: Long) {
tickIntent.putExtra(TIME_LEFT_KEY, millisUntilFinished)
sendBroadcast(tickIntent)
}
override fun onFinish() {
sendBroadcast(finishedIntent)
stopSelf() // Stop the service within itself NOT the activity
}
}
companion object {
const val ACTION_FINISHED: String = "your.pkg.name.ACTION_FINISHED"
const val ACTION_TICK: String = "your.pkg.name.ACTION_TICK"
const val TIME_LEFT_KEY: String = "timeLeft"
private val COUNTDOWN_INTERVAL = TimeUnit.SECONDS.toMillis(1)
private val COUNTDOWN_LENGTH = TimeUnit.MINUTES.toMillis(5)
}
}
Add the service to the AndroidManifest.xml
<application ...>
<service android:name=".TimerService"
android:description="@string/timer_service_description"
android:exported="false"/>
...
</application
Listen to the broadcast of the service in the activity.
class MainActivity : AppCompatActivity() {
private val timerReceiver = TimerReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
// ...
val intent = Intent(this, TimerService::class.java)
startService(intent) // This kicks off the timer when the activity is launched. Move to a click handler if needed.
}
override fun onResume() {
super.onResume()
registerReceiver(timerReceiver, IntentFilter(TimerService.ACTION_TICK))
registerReceiver(timerReceiver, IntentFilter(TimerService.ACTION_FINISHED))
}
override fun onPause() {
unregisterReceiver(timerReceiver)
super.onPause()
}
private inner class TimerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null) return
when (intent.action) {
TimerService.ACTION_TICK -> {
val timeLeft = intent.getLongExtra(TimerService.TIME_LEFT_KEY, 0)
updateUIForTick(timeLeft)
}
TimerService.ACTION_FINISHED -> updateUIForTimerFinished()
}
}
}
}
See this SO answer to learn whether registering the receiver in onCreate, onStart, or onResume is right for you.
The Best Way to use System.currentTimeMillis() and manage your CountDown. See the Below Code after searching everywhere this code work perfectly (this is Kotlin Solution)
TimerService : Service()
override fun onStartCommand(intent:Intent?, flags:Int, startId:Int):Int {
val mSharePref= getSharedPreferences("BillingActivity", MODE_PRIVATE)
val day = mSharePref.getLong("futureTime", 0)
val curDateTime = System.currentTimeMillis()
var diff = day - curDateTime
if (diff < 0)diff = 0L
Toast.makeText(this, "$diff", Toast.LENGTH_SHORT).show()
countDown(diff)
return START_STICKY
}
private fun countDown(day: Long){
cdt?.cancel()
val mSharePref= getSharedPreferences("BillingActivity", MODE_PRIVATE)
cdt = object : CountDownTimer(day, 1000) {
override fun onTick(millisUntilFinished: Long) {
val mDays = millisUntilFinished / (24 * 60 * 60 * 1000)
val mHours = millisUntilFinished / (60 * 60 * 1000) % 24
val mMinutes = millisUntilFinished / (60 * 1000) % 60
val mSeconds = millisUntilFinished / 1000 % 60
val timeFormat = String.format(Locale.getDefault(), "Day: %02d, Time: %02d:%02d:%02d", mDays, mHours, mMinutes, mSeconds)
bi.putExtra("time", timeFormat)
bi.putExtra("milis", millisUntilFinished )
sendBroadcast(bi)
}
override fun onFinish() {
bi.putExtra("time", "Finish")
sendBroadcast(bi)
}
}.start()
}
And in Main Activity
Main Activity
override fun onResume() {
super.onResume()
startService(Intent(this, TimerService::class.java))
}
Thanks you
There's another way to implement it without using a service. I used SharedPreference to handle the app close scenario.
This is my code:
override fun onResume() {
super.onResume()
binding.state = getReadingState()
when (binding.state) {
SessionState.NOT_STARTED -> {
}
SessionState.PAUSED -> {
startService()
val startTime = if (getSessionPauseTime() > 0)
getSessionStartTime() + (Date().toMillis() - getSessionPauseTime())
else getSessionStartTime()
updateTimer(startTime)
}
SessionState.IN_PROGRESS -> updateTimer(getSessionStartTime())
else -> Unit
}
}
fun Context.getReadingState(): ReadingSessionState {
return when {
getSessionPauseTime() > 0 -> SessionState.PAUSED
isReadingSessionRunning() -> SessionState.IN_PROGRESS
else -> SessionState.NOT_STARTED
}
}
fun onStartClicked(){
setSessionPauseTime(0)
setSessionStartTime(Date().toMillis())
binding.state = SessionState.IN_PROGRESS
}
fun onStopClicked() {
val lastPauseMillis = getSessionPauseTime()
val lastStartMillis = getSessionStartTime()
if (lastPauseMillis > 1000) {
setSessionPauseTime(0)
setSessionStartTime(lastStartMillis + (Date().toMillis() - lastPauseMillis))
}
onPauseClicked()
}
fun onPauseClicked() {
setSessionPauseTime(Date().toMillis())
binding.state = SessionState.PAUSED
}
override fun onResumeClicked() {
val lastPauseMillis = getSessionPauseTime()
val lastStartMillis = getSessionStartTime()
setSessionPauseTime(0)
if (lastPauseMillis > 1000)
setSessionStartTime(lastStartMillis + (Date().toMillis() - lastPauseMillis))
binding.state = SessionState.IN_PROGRESS
}
© 2022 - 2024 — McMap. All rights reserved.