CountDownTimer : In Activity, ViewModel or separate class?
Asked Answered
F

3

7

I would like to create a CountdownTimer which will trigger events that will update the UI (trigger popup, start an animation, etc.).

I wonder how to do this clean, here are my hypothesis and why :

  1. A separate component EventCountdownTimer. I could then benefit the use of LifecycleObserver, but I wonder how to communicate the information back to the activity (I tried extending CountdownTimer and using it in the activity but I have an error and can't get it to compile)
  2. In the Activity itself, it's the simplest but I'm not sure it belongs there as it isn't a UI component and I can't benefit the LifecycleObserver
  3. In the ViewModel. I thought as it's activity related and the CountdownTimer is kinda logic data, it should go in here, but that means also watching the lifecycle of the activity, and holding any Activity related field within ViewModel is bad practice.

What's the best option according to you? And why?

Fake answered 24/6, 2019 at 9:7 Comment(0)
H
2

A separate component "EventCountdownTimer"

In my opinion, this is the best implementation that you might have in your case. For communicating information back to your activity, you might consider having an interface like the following.

public interface TimerListener {
    void onTimerResponse(String response);
}

Modify your EventCountdownTimer to have a constructor which takes TimerListener as a parameter and override the onTimerResponse method in your activity. Now from your EventCountdownTimer, when you are trying to communicate with your activity along with a message, for example, you might just call the function onTimerResponse(msgToDeliver).

Hence your EventCountdownTimer should look something like this.

public class EventCountdownTimer {
    public static Context context;
    public static TimerListener listener;

    public EventCountdownTimer(Context context, TimerListener listener) {
        this.context = context;
        this.listener = listener;
    }

    public startCountdown() {
        // Start the count down here
        // ... Other code

        // When its time to post some update to your activity
        listener.onTimerResponse(msgToDeliver);
    }
}

And from your activity, initialize the EventCountdownTimer like the following.

EventCountdownTimer timer = new EventCountdownTimer(this, new TimerListener() {
    @Override
    public void onTimerResponse(String message) {
        // Do something with the message data 
        // Update your UI maybe
    }
});

I think you have provided good reasons already for not going for other options that you have mentioned.

Hartle answered 24/6, 2019 at 9:33 Comment(2)
Alright, with this option I can even use the LifecycleObserver to start and stop the timer depending on the activity lifecycle state. Will give it a shot and let you knowFake
Sure! Let me know if that helps!Hartle
C
7

In a MVVM pattern you could have a LiveData observable in your ViewModel which will be observed by the UI and upon value change you update the UI accordingly. How that observable changes value, that is your business logic and all of it should be in your ViewModel or in separate components that will be used by the ViewModel to update the observable state.

This will allow you to separate the UI from the business logic being your observable the bridge of communication between both, without the ViewModel having any knowledge of whats going on in the UI. In simple words it only executes what it is told to execute and updates a variable that is being observed, what then happens in the UI is the UI responsibility and with this you have reached a clear separation of concerns.

Circumscribe answered 24/6, 2019 at 9:30 Comment(4)
That's true but as I want to have a LifecycleObserver to start and stop my Countdown timer and that requires holding the Activity Lifecycle object, that means my ViewModel would hold an activity field, isn't that not recommended ?Fake
If you instantiate your LifecycleObserver in your activity you can call the viewModel methods from the callbacks. Anyway you dont need a life cycle observer for this, since you can call the viewModel methods from the activity life cycle itselfCircumscribe
LifecycleObserver isn't needed but I read it's good practice to avoid starting and canceling component such as a CountdownTimer within the Activity in its lifecycle method.Fake
Im not telling you to call the timer in the activity, Im telling you to call the view model. Remember, the view model is not life cycle awareCircumscribe
E
3

Google solution : see it on github

/**
 * A ViewModel used for the {@link ChronoActivity3}.
 */
public class LiveDataTimerViewModel extends ViewModel {

    private static final int ONE_SECOND = 1000;

    private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();

    private long mInitialTime;
    private final Timer timer;

    public LiveDataTimerViewModel() {
        mInitialTime = SystemClock.elapsedRealtime();
        timer = new Timer();

        // Update the elapsed time every second.
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
                // setValue() cannot be called from a background thread so post to main thread.
                mElapsedTime.postValue(newValue);
            }
        }, ONE_SECOND, ONE_SECOND);

    }

    public LiveData<Long> getElapsedTime() {
        return mElapsedTime;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        timer.cancel();
    }
}
Everyplace answered 20/7, 2021 at 17:8 Comment(1)
thanks for finding this gem, the codelab is currently archived, so it's always nice to have another copy in the internet.Acciaccatura
H
2

A separate component "EventCountdownTimer"

In my opinion, this is the best implementation that you might have in your case. For communicating information back to your activity, you might consider having an interface like the following.

public interface TimerListener {
    void onTimerResponse(String response);
}

Modify your EventCountdownTimer to have a constructor which takes TimerListener as a parameter and override the onTimerResponse method in your activity. Now from your EventCountdownTimer, when you are trying to communicate with your activity along with a message, for example, you might just call the function onTimerResponse(msgToDeliver).

Hence your EventCountdownTimer should look something like this.

public class EventCountdownTimer {
    public static Context context;
    public static TimerListener listener;

    public EventCountdownTimer(Context context, TimerListener listener) {
        this.context = context;
        this.listener = listener;
    }

    public startCountdown() {
        // Start the count down here
        // ... Other code

        // When its time to post some update to your activity
        listener.onTimerResponse(msgToDeliver);
    }
}

And from your activity, initialize the EventCountdownTimer like the following.

EventCountdownTimer timer = new EventCountdownTimer(this, new TimerListener() {
    @Override
    public void onTimerResponse(String message) {
        // Do something with the message data 
        // Update your UI maybe
    }
});

I think you have provided good reasons already for not going for other options that you have mentioned.

Hartle answered 24/6, 2019 at 9:33 Comment(2)
Alright, with this option I can even use the LifecycleObserver to start and stop the timer depending on the activity lifecycle state. Will give it a shot and let you knowFake
Sure! Let me know if that helps!Hartle

© 2022 - 2024 — McMap. All rights reserved.