Skip to content Skip to sidebar Skip to footer

CountDownTimer Problem When App Is Closed

I made a CountDownTimer code, i would like to CountDownTimer restart when countdown finished even if app is closed, but it only restart if app running or when app is re-launched. S

Solution 1:

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();
    }
}

Solution 2:

First, take a look here: Understand the Activity Lifecycle

You need onResume, onPause and onDestroy so that all scenarios are covered.

For onResume, the reason is, when you put your app to background, and resume the app by making it to foreground, action can be further applied, for example, getting the last saved state from SharedPreferences to ensure the condition is executed.

For onPause, it is crucial because when you click your phone home button, this state will be executed. Therefore, it ensures all the states is saved before it is destroyed (insurance).

For onDestroy, it is the most crucial part, because for some low-end phones, resources are limited, the os will do the 'cleaning' by killing apps, so for your app, it will get kill, so before it is killed, you can save the state.

Hence, whenever you start or use your app, query the SharedPreferences, and do some calculation to ensure everything is correct.

Use service or broadcast, which allow your program to run background.

Some detailed explanation from the documentation:

  1. onResume()

When the activity enters the Resumed state, it comes to the foreground, and then the system invokes the onResume() callback. This is the state in which the app interacts with the user. The app stays in this state until something happens to take focus away from the app. Such an event might be, for instance, receiving a phone call, the user’s navigating to another activity, or the device screen’s turning off.

When the activity moves to the resumed state, any lifecycle-aware component tied to the activity's lifecycle will receive the ON_RESUME event. This is where the lifecycle components can enable any functionality that needs to run while the component is visible and in the foreground, such as starting a camera preview.

When an interruptive event occurs, the activity enters the Paused state, and the system invokes the onPause() callback.

If the activity returns to the Resumed state from the Paused state, the system once again calls onResume() method. For this reason, you should implement onResume() to initialize components that you release during onPause(), and perform any other initializations that must occur each time the activity enters the Resumed state.

  1. onPause

The system calls this method as the first indication that the user is leaving your activity (though it does not always mean the activity is being destroyed); it indicates that the activity is no longer in the foreground (though it may still be visible if the user is in multi-window mode). Use the onPause() method to pause or adjust operations that should not continue (or should continue in moderation) while the Activity is in the Paused state, and that you expect to resume shortly. There are several reasons why an activity may enter this state.

  1. onDestroy()

onDestroy() is called before the activity is destroyed. The system invokes this callback either because:

  1. the activity is finishing (due to the user completely dismissing the activity or due to finish() being called on the activity), or
  2. the system is temporarily destroying the activity due to a configuration change (such as device rotation or multi-window mode)

When the activity moves to the destroyed state, any lifecycle-aware component tied to the activity's lifecycle will receive the ON_DESTROY event. This is where the lifecycle components can clean up anything it needs to before the Activity is destroyed.

Have a look here: Background Execution Limits


Post a Comment for "CountDownTimer Problem When App Is Closed"