CountDownTimer : In Activity, ViewModel or separate class? - android

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 :
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)
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
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?

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.

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.

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

Related

Android ViewModel and startActivity

I am learning ViewModel and LiveData and, in the process, a doubt arose.
What should I do if I need to start an Activity?
Is it ok to pass the context as a parameter to the ViewModel (the context will not be stored inside the ViewModel)?
ActivityAViewModel : ViewModel() {
// ...
fun openActivityB(context: Context) {
context.startActivity(...)
}
// ...
}
ActivityA {
// ...
fun onSomethingHappened() {
viewModel.openActivityB(this)
}
// ...
}
If not, what is the most correct thing to do in that case?
I like firing Events. :D
As everyone says ViewModel should not contain Context or reference to classes that contain Context. So it is not a good idea to do startActivity from ViewModel.
What I would do is have a LiveData containing data for an event. This event will be fired from your ViewModel based on your business logic (Maybe you are showing a CountDown and at the end of it you move to the next Activity?). It is a LiveData and you can observe on it. Based on the data of this event you can start your activity.
You may want to look at SingleLiveEvent
You should call startActivity from activity, not from viewmodel. If you want to open it from viewmodel, you need to create livedata in viewmodel with some navigation parameter and observe on livedata inside the activity.
IMHO, viewmodel should know nothing about view and how it presents info to user.
/**
* Activity (as view) responsible only for gathering actions and intentions from user and
* show result state.
* View must know "What user want". View knows meaning its interface.
* Click on button 'login' means INTENTION to login somewhere.
* This intention pass to ViewModel to process it and wait some changing state from LiveData.
* For example implemented as Actions.
*/
public class LoginActivity extends AppCompatActivity {
private LoginViewModel mLoginViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLoginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
mLoginViewModel.getAction().observe(this, new Observer<Action>() {
#Override
public void onChanged(#Nullable final Action action) {
if(action != null){
handleAction(action);
}
}
});
//Emulate user intention
mLoginViewModel.userWantToLogin("0123456789", "admin");
}
private void handleAction(#NonNull final Action action) {
switch (action.getValue()){
case Action.SHOW_WELCOME:
//show Activity.
break;
case Action.SHOW_INVALID_PASSWARD_OR_LOGIN:
//show Toast
break;
}
}
}
public class LoginViewModel extends ViewModel {
//Stores actions for view.
private MutableLiveData<Action> mAction = new MutableLiveData<>();
public LiveData<Action> getAction() {
return mAction;
}
/**
* Takes intention to login from user and process it.
*
* #param password Dummy password.
* #param login Dummy login.
*/
public void userWantToLogin(String password, String login){
if(validateInfo(password, login)){
showWelcomeScreen();
}else {
showPasswordOrLoginInvalid();
}
}
/*
* Changes LiveData. Does not act directly with view.
* View can implement any way to show info
* to user (show new activity, alert or toast)
*/
private void showPasswordOrLoginInvalid() {
mAction.setValue(new Action(Action.SHOW_INVALID_PASSWARD_OR_LOGIN));
}
/*
* Changes LiveData. Does not act directly with view.
* View can implement any way to show info
* to user (show new activity, alert or toast)
*/
private void showWelcomeScreen() {
mAction.setValue(new Action(Action.SHOW_WELCOME));
}
//As example of some logic.
private boolean validateInfo(String password, String login) {
return password.equals("0123456789") && login.equals("admin");
}
}
public class Action {
public static final int SHOW_WELCOME = 0;
public static final int SHOW_INVALID_PASSWARD_OR_LOGIN = 1;
private final int mAction;
public Action(int action) {
mAction = action;
}
public int getValue() {
return mAction;
}
}
It would be a good design choice if the viewmodel knows nothing about the activities. Basically, viewmodel and activities play observable and observers roles. ViewModel, being a wrapper around your repository or business model or orchestration layer, provides the reactive style data streaming and plays observable role. It means, several activities or fragments, being observers, can listen to one view model.
So it is better to keep louse coupling, by not tightening the one particular activity to one view model but it is common convention among mobile developers that they prefer to create one view model to one activity/fragment.
If you have retrofit or okhttp or other libraries that need context, pass them context thru dagger2 or Koin DI libraries. It would be a clean architecture.
You can use an Application context which is provided by the AndroidViewModel, you should extend AndroidViewModel which is simply a ViewModel that includes an Application reference.

Extending LoaderManager - How to implement "publishProgress"?

Imagine I have a very simple long-running task as an AsyncTaskLoader:
public class DumbLoader extends AsyncTaskLoader{
private static final String TAG = "DumbLoader";
public DumbLoader(Context context) {
super(context);
}
#Override
public List<String> loadInBackground() {
List<String> allData = new ArrayList<>();
List<String> someData = DummyData.getItems();
notify(someData.size()) ;// publish amount of elements of someData
List<String> someOtherData = DummyData.getSomeOtherItems();
notify(someOtherData.size()); //publish amount of elements of someOtherData
allData.addAll(someData);
allData.addAll(someOtherData);
return allData;
}
}
And I have an activity implementing LoaderCallbacks:
#Override
public Loader<List<String> onCreateLoader(int i, Bundle bundle) {
return new DumbLoader(this);
}
#Override
public void onLoadFinished(Loader<String> dummyLoader, List<String> result) {
// do something with result
}
#Override
public void onLoaderReset(Loader<String> dummyLoader) {
}
How would you implement an AsyncTask-like publishProgress?
The whole purpose of using LoaderManager is to not have to work with references, when Context/Orientation changes - however, with publishProgress the only way I can think of is passing a Handler into my DumbLoader which then notifies me. Is this a safe way? Are there better ways?
Edit: My example might be a bit misleading. In case of having two seperate functions which both return the values of my "final" result I could easily call the AsyncTaskLoader seperately for each function. I modified it to visualize that the data "published" can be different from the final result (in this example, I would like to know the size of the data, but not the data).
An indeterminate ProgressBar seems to me the obvious choice, for this use case. I would instantiate it when the onCreateLoader is called and dismiss it in onLoadFinished
however, with publishProgress the only way I can think of is passing a
Handler into my DumbLoader which then notifies me. Is this a safe way?
Are there better ways?
I would use the LocalBroadcastManager. The intent will be broadcasted only within your app. In your Activity/Fragment register a BroadcastReceiver and update the progress when onReceiver is invoked. Nice thing of BroadcastReceiver is that it runs always on the ui thread

RxJava as event bus?

I'm start learning RxJava and I like it so far. I have a fragment that communicate with an activity on button click (to replace the current fragment with a new fragment). Google recommends interface for fragments to communicate up to the activity but it's too verbose, I tried to use broadcast receiver which works generally but it had drawbacks.
Since I'm learning RxJava I wonder if it's a good option to communicate from fragments to activities (or fragment to fragment)?. If so, whats the best way to use RxJava for this type of communication?. Do I need to make event bus like this one and if that's the case should I make a single instance of the bus and use it globally (with subjects)?
Yes and it's pretty amazing after you learn how to do it. Consider the following singleton class:
public class UsernameModel {
private static UsernameModel instance;
private PublishSubject<String> subject = PublishSubject.create();
public static UsernameModel instanceOf() {
if (instance == null) {
instance = new UsernameModel();
}
return instance;
}
/**
* Pass a String down to event listeners.
*/
public void setString(String string) {
subject.onNext(string);
}
/**
* Subscribe to this Observable. On event, do something e.g. replace a fragment
*/
public Observable<String> getStringObservable() {
return subject;
}
}
In your Activity be ready to receive events (e.g. have it in the onCreate):
UsernameModel usernameModel = UsernameModel.instanceOf();
//be sure to unsubscribe somewhere when activity is "dying" e.g. onDestroy
subscription = usernameModel.getStringObservable()
.subscribe(s -> {
// Do on new string event e.g. replace fragment here
}, throwable -> {
// Normally no error will happen here based on this example.
});
In you Fragment pass down the event when it occurs:
UsernameModel.instanceOf().setString("Nick");
Your activity then will do something.
Tip 1: Change the String with any object type you like.
Tip 2: It works also great if you have Dependency injection.
Update:
I wrote a more lengthy article
Currently I think my preferred approach to this question is this to:
1.) Instead of one global bus that handles everything throughout the app (and consequently gets quite unwieldy) use "local" buses for clearly defined purposes and only plug them in where you need them.
For example you might have:
One bus for sending data between your Activitys and your ApiService.
One bus for communicating between several Fragments in an Activity.
One bus that sends the currently selected app theme color to all Activitys so that they can tint all icons accordingly.
2.) Use Dagger (or maybe AndroidAnnotations if you prefer that) to make the wiring-everything-together a bit less painful (and to also avoid lots of static instances). This also makes it easier to, e. g. have a single component that deals only with storing and reading the login status in the SharedPreferences - this component could then also be wired directly to your ApiService to provide the session token for all requests.
3.) Feel free to use Subjects internally but "cast" them to Observable before handing them out to the public by calling return subject.asObservable(). This prevents other classes from pushing values into the Subject where they shouldn't be allowed to.
Define events
public class Trigger {
public Trigger() {
}
public static class Increment {
}
public static class Decrement {
}
public static class Reset {
}
}
Event controller
public class RxTrigger {
private PublishSubject<Object> mRxTrigger = PublishSubject.create();
public RxTrigger() {
// required
}
public void send(Object o) {
mRxTrigger.onNext(o);
}
public Observable<Object> toObservable() {
return mRxTrigger;
}
// check for available events
public boolean hasObservers() {
return mRxTrigger.hasObservers();
}
}
Application.class
public class App extends Application {
private RxTrigger rxTrigger;
public App getApp() {
return (App) getApplicationContext();
}
#Override
public void onCreate() {
super.onCreate();
rxTrigger = new RxTrigger();
}
public RxTrigger reactiveTrigger() {
return rxTrigger;
}
}
Register event listener wherever required
MyApplication mApp = (App) getApplicationContext();
mApp
.reactiveTrigger() // singleton object of trigger
.toObservable()
.subscribeOn(Schedulers.io()) // push to io thread
.observeOn(AndroidSchedulers.mainThread()) // listen calls on main thread
.subscribe(object -> { //receive events here
if (object instanceof Trigger.Increment) {
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) + 1));
} else if (object instanceof Trigger.Decrement) {
if (Integer.parseInt(fabCounter.getText().toString()) != 0)
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) - 1));
} else if (object instanceof Trigger.Reset) {
fabCounter.setText("0");
}
});
Send/Fire event
MyApplication mApp = (App) getApplicationContext();
//increment
mApp
.reactiveTrigger()
.send(new Trigger.Increment());
//decrement
mApp
.reactiveTrigger()
.send(new Trigger.Decrement());
Full implementation for above library with example -> RxTrigger

using handler for every single task(method) in android

Hello i am new to android and android thread so want to know that
How could we use more number of thread in order to perform every single task or method so that while user click on any UI component it does effect the performance ,having little knowledge of how the handler thread and asynctask work.But how can we run every method inside the asynctask so to do the operation and mean while user can do the other operation also.
In the application
i have voice recording from mic.
next showing progress bar.
next showing gallery with some image and with that setting effect to the picture.
The recommended way is to use AsyncTasks for long running tasks. So, not everything needs to be run with AsyncTasks, as you can get a performance hit due to the context switching.
As for how AsyncTasks work, read the documentation.
Use an AsyncTask and make sure to implement these as needed. You mention the idea of doing something in the background while a user is doing something so I'm guessing you'll want to alter the UI.
Take a look at these links for an more details from Android. They cover Runnable, AsyncTask and Handler
Overview of them all http://developer.android.com/guide/components/processes-and-threads.html
AsyncTask example http://developer.android.com/reference/android/os/AsyncTask.html
Old but relevant, Painless Threading http://android-developers.blogspot.com/2009/05/painless-threading.html
Another, more complex example http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
I don't generally paste full examples in here but I had a lot of trouble finding an example I was happy with for a long time and to help you and others, here is my preferred method. I generally use an AsyncTask with a callback to the Activity that started the task.
In this example, I'm pretending that a user has triggered onClick(...) such as with a button, but could be anything that triggers a call into the Activity.
// Within your Activity, call a custom AsyncTask such as MyTask
public class MyActivity extends Activity implements View.OnClickListener, MyTask.OnTaskComplete {
//...
public void onClick(View v) {
// For example, thet user clicked a button
// get data via your task
// using `this` will tell the MyTask object to use this Activty
// for the listener
MyTask task = new MyTask(this);
task.execute(); // data returned in callback below
}
public void onTaskComplete(MyObject obj) {
// After the AsyncTask completes, it calls this callback.
// use your data here
mTextBox.setText(obj.getName);
}
}
Getting the data out of a task can be done many ways, but I prefer an interface such as OnTaskComplete that is implemented above and triggered below.
The main idea here is that I often want to keep away from inner classes as they become more complex. Mostly a personal preference, but it allows me to separate reusable tasks outside of one class.
public class MyTask extends AsyncTask<Void, Void, MyObject> {
public static interface OnTaskComplete {
public abstract void onTaskComplete(MyObject obj);
}
static final String TAG = "MyTask";
private OnTaskComplete mListener;
public MyTask(OnTaskComplete listener) {
Log.d(TAG, "new MyTask");
if (listener == null)
throw new NullPointerException("Listener may not be null");
this.mListener = listener;
}
#Override
protected MyObject doInBackground(Void... unused) {
Log.d(TAG, "doInBackground");
// do background tasks
MyObbject obj = new MyObject();
// Do long running tasks here to not block the UI
obj.populateData();
return
}
#Override
protected void onPostExecute(MyObject obj) {
Log.d(TAG, "onPostExecute");
this.mListener.onTaskComplete(obj);
}
}

Callback to activity from other class

I have Activity class, Controller class (normal java class use to control number of activity) and BusinessEngine class (normal java class use to process data).
When I need to do some calculation from activity, Activity will call Controller and Controller will call BusinessEngine to do the calculation. When BusinessEngine done with the calculation, it will pass the value back to Controller and finally let the activity know the calculation is complete.
The problem is how I callback Activity from Controller class? Or pass any data to Activity and notify it the data has been change?
Any "long" running tasks must be performed in a background thread. I'm not sure if your currently doing this for your task, so just in case your not, there are a couple of ways to do this. The first is to simply use a AsyncTask, the second is to create your own instance of AbstractExecutorService (AsyncTask uses ThreadPoolExecutor) and use that to post Runnable or Callables to. The later way may save you a lot of re factoring depending on your code base.
Assuming you're now running the task in a background thread, it's necessary to perform your UI updates on the UI thread. There are again a couple of ways to do this. One method is to post a runnable to the method Activity#runOnUiThread, the second is to use a Handler which has previously been created on the UI thread (which Activity#runOnUiThread does behind the scenes).
So, assume your Activity has a method #postResults(final Object o), and your controller has the method #doSomething(final Activity activity).
Your activity would look something like this.
public class MyActivity extends Activity {
Controller controller = ....
ExecutorService service = Executors.newFixedThreadPool(10);
private void startTask() {
Runnable r = new Runnable() {
public void run() {
c.doSomething(MyActivity.this);
}
}
service.submit(r);
}
public void postResults(final Object o) {
Runnable r = new Runnable() {
public void run() {
// Update your UI here
}
}
runOnUiThread(r)
}
}
and your controller
public class Controller {
public void doSomething(final Activity activity) {
// Perform some long running task here
activity.postResults(someObject);
}
}
Obviously this example could be tidied up (for example passing a interface to doSomething rather than the Activity), but hopefully it should be enough to understand what you need to do :)
Why are you looking for the controller to call you Activity? Normally, your Activity must call the controller via its methods and directly get results from them:
// Code in your Activity
result = controller.doSomething(args);
try using a android AsyncTask, if your method takes a long time to process. example
Add your classes to an Async task or if you're calling the classes and passing them from one class to the other.I would say to use static class. And provide some code so we can know how you are passing your data.
If not use general methods to call the superclass or the subclass.
My answer is a bit abstract as information is less.

Categories

Resources