I'm trying to understand a right way to work with viewmodel & livedata.
I created a simple app which started timer when activity start.
When activity destroyed - timer continues running and on restart activity created a second timer and now we have two running timers - it's a potential memory leak ?
How a right way to stop our "task" on stop\destroy activity ?
My code:
MainActivity.java
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding mActivityMainBinding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mActivityMainBinding.getRoot());
MainActivityViewModel mainActivityViewModel =
new ViewModelProvider(this).get(MainActivityViewModel.class);
Observer<Integer> liveDataObserver = integer ->
mActivityMainBinding.tvCounter.setText("Elapsed : "+integer+ " s");
mainActivityViewModel.getLiveData().observe(this, liveDataObserver);
getLifecycle().addObserver((LifecycleEventObserver) (source, event) ->
Log.d("mylog", event.name()));
} }
MainActivityViewModel .java
public class MainActivityViewModel extends ViewModel {
private MutableLiveData<Integer> mutableLiveData = new MainActivityLiveData<>();
private static int BEGIN_AFTER = 1000, INTERVAL = 5000;
private static int counter = 0;
public MainActivityViewModel() {
startTimer();
}
private void startTimer() {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Log.d("mylog", timer.toString() + " " + ++counter);
mutableLiveData.postValue(counter);
}
}, BEGIN_AFTER, INTERVAL);
}
public MutableLiveData<Integer> getLiveData() {
return mutableLiveData;
} }
MainActivityLiveData.java
public class MainActivityLiveData<T> extends MutableLiveData<T> {
#Override
protected void onActive() {
Log.d("mylog", "onActive");
}
#Override
protected void onInactive() {
Log.d("mylog", "onInactive");
}}
You need to stop the timer inside onClear() of viewModel. for that you have to make it a global variable .
public class MainActivityViewModel extends ViewModel {
private MutableLiveData<Integer> mutableLiveData = new MainActivityLiveData<>();
private Timer timer = new Timer();
private static int BEGIN_AFTER = 1000, INTERVAL = 5000;
private static int counter = 0;
public MainActivityViewModel() {
startTimer();
}
private void startTimer() {
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Log.d("mylog", timer.toString() + " " + ++counter);
mutableLiveData.postValue(counter);
}
}, BEGIN_AFTER, INTERVAL);
}
public MutableLiveData<Integer> getLiveData() {
return mutableLiveData;
}
#Override
protected void onCleared() {
super.onCleared();
stopTimer();
}
private void stopTimer(){
if(timer != null) {
timer.cancel();
timer.purge();
timer = null;
}
}
}
Related
I'm a beginner...
Why did my "setImageRessource" doesn't work?
The image "fiole" have to change at 10% and at 20% of the duration of the timer but on my phone, the image never change...
However, the text of the textViex is changed when the time equals the quarter of the duration.
public class XActivity extends AppCompatActivity {
int duree;
String duration;
int durationInt;
protected TextView tempsRestant;
protected ImageView fiole;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_X);
this.duree = 0;
this.duration = getIntent().getStringExtra("DURATION");
this.durationInt = Integer.parseInt(duration)*60;
this.tempsRestant=findViewById(R.id.tempsRestant);
this.fiole=findViewById(R.id.fiole);
final Timer modecTimer = new Timer();
modecTimer.schedule(new TimerTask() {
#Override
public void run() {
duree += 1;
if (duree==(durationInt/100)*10){
fiole.setImageResource(R.drawable.fiole10p);
}
if (duree==(durationInt/100)*20){
fiole.setImageResource(R.drawable.fiole20p);
}
if (duree==durationInt/4){
tempsRestant.setText("some text");
}
}
}
}, 1000, 1000);
}
Your setImageResource didn't work, because it needs to be run on UIThread
modecTimer.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(() -> {
duree += 1;
if (duree==(durationInt/100)*10){
fiole.setImageResource(R.drawable.fiole10p);
}
if (duree==(durationInt/100)*20){
fiole.setImageResource(R.drawable.fiole20p);
}
if (duree==durationInt/4){
tempsRestant.setText("some text");
}
});
}
}, 1000, 1000);
This question already has answers here:
Running code in main thread from another thread
(17 answers)
Closed 5 years ago.
I'm trying to dynamically update an android LinearLayout in the main thread.
Unfortunately I'm having a lot of trouble ascertaining anything from the tutorials online. None of them seem to provide a complete picture of how to communicate between threads.
My idea is something like this:
public class MainActivity extends AppCompatActivity {
private LinearLayout layout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
layout = new LinearLayout(this);
setContentView(layout);
Updater updater = new Updater();
Thread workerThread = new Thread(updater);
//somehow update layout
The updater class would look something like this:
public class Updater implements Runnable {
private int count = 0;
public Updater() {}
#Override
public void run()
{
for (int i = 0; i < 10; i ++){
try {
count++;
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
I know I need a Handler in order to communicate messages between the threads, but I don't know how to set that up.
I would like to avoid anonymous classes, and dynamically create new TextViews whenever Updater has a new message.
create WorkerThreadListener interface:
public interface WorkerThreadListener {
void onUpdate(int counter);
}
Change your Updater class:
public class Updater implements Runnable {
private final WorkerThreadListener mWorkerThreadListener;
private final Handler mHandler;
private int count = 0;
public Updater(final WorkerThreadListener workerThreadListener) {
this.mWorkerThreadListener = workerThreadListener;
this.mHandler = new Handler();
}
#Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
count++;
mHandler.post(new Runnable() {
#Override
public void run() {
mWorkerThreadListener.onUpdate(count);
}
});
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Change MainActivity class:
public class MainActivity extends AppCompatActivity {
private LinearLayout layout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
layout = new LinearLayout(this);
setContentView(layout);
Updater updater = new Updater(new WorkerThreadListener() {
#Override
public void onUpdate(int counter) {
//update layout here
}
});
Thread workerThread = new Thread(updater);
workerThread.start();
}
}
Hi please check my below answer hope it helps you.
public class ProgressTestActivity extends Activity {
private ProgressBar progress;
private TextView text;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
progress = (ProgressBar) findViewById(R.id.progressBar1);
text = (TextView) findViewById(R.id.textView1);
}
public void startProgress(View view) {
// do something long
Runnable runnable = new Runnable() {
#Override
public void run() {
for (int i = 0; i <= 10; i++) {
final int value = i;
doFakeWork();
progress.post(new Runnable() {
#Override
public void run() {
// here you can add any view or anyof your logic which is related to UI put it into here.
text.setText("Updating");
progress.setProgress(value);
}
});
}
}
};
new Thread(runnable).start();
}
// Simulating something timeconsuming
private void doFakeWork() {
SystemClock.sleep(5000);e.printStackTrace();
}
}
Other ways are also possible.if you have any doubt please comment below post i will explain you in details.
If you just want to use a tick timer and set progress to ui thread . You can use CountDownTimer.
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView textView;
private CountDownTimer countDownTimer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new);
textView = (TextView) findViewById(R.id.textView);
findViewById(R.id.b2).setOnClickListener(this);
}
public void processData() {
countDownTimer = new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
textView.setText("seconds remaining: " + millisUntilFinished / 1000);
}
public void onFinish() {
textView.setText("done!");
}
}.start();
}
#Override
protected void onStop() {
super.onStop();
if (countDownTimer != null) {
countDownTimer.cancel();
}
}
#Override
public void onClick(View v) {
processData();
}
}
Apart from that to post a callback on UI thread you can use Handler .
Handler mainThreadHandler = new Handler(Looper.getMainLooper());
mainThreadHandler.post(new Runnable() {
#Override
public void run() {
}
});
I am trying to create an AsyncTask for following function:
private void updateStreamImageRequest() {
countDownTimer = new CountDownTimer(10000, 2000) {
#Override
public void onTick(long millisUntilFinished) {
imageRequest();
}
#Override
public void onFinish() {
countDownTimer.start();
}
};
}
I do not have much knowledge in AsyncTaskand I am struggling to make it work. The AsyncTask shall continueally run a get request.
So far I have done this so far but it does not work:
public void getImgAsync() {
new requestAsyncTask();
}
public class requestAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
countDownTimer = new CountDownTimer(10000, 2000) {
#Override
public void onTick(long millisUntilFinished) {
API_StreamImage_Request();
}
#Override
public void onFinish() {
countDownTimer.start();
}
};
return null;
}
}
So do it this way -
Create your Countdown timer that runs for 10 seconds. So it will finish after 10 seconds. Then in finish method call your Asynctask to fetch in background.
countDownTimer = new CountDownTimer(10000, 5000) {
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
new requestAsyncTask().execute();
}
};
countDownTimer.start();
class requestAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
countDownTimer.cancel();
countDownTimer.start();
}
#Override
protected Void doInBackground(Void... params) {
API_StreamImage_Request();
return null;
}
}
You might have to create a global variable of your countdown timer so that you can access it inside your asynctask class. Or you can even pass it as well if you want.
You can also use a Handler for this.
private int mInterval = 10000;
private Handler mHandler = new Handler();
Runnable requestCaller = new Runnable() {
#Override
public void run() {
try {
new requestAsyncTask().execute();
} finally {
mHandler.postDelayed(requestCaller, mInterval);
}
}
};
Or a Timer
new Timer().scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
new requestAsyncTask().execute();
}
},0, 10000);
I am extending the Application class to do 2 things. 1) Make onPause() stop the CounterThread until onResume() is pressed. 2) To make the threads continue working while I change orientation of the phone from portrait mode to landscape. Both of the above statements work, however the MakingFileThread when it returns a string value to set the TextView does not persist the orientation changes. Help!!
public class MainActivity extends Activity {
private TextView mText;
private EditText mUserInput;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mText = (TextView)findViewById(R.id.text);
mUserInput = (EditText)findViewById(R.id.userInput);
}
#Override
public void onPause(){
super.onPause();
MyApplication app = (MyApplication)getApplication();
app.pause(null);
}
#Override
public void onResume(){
super.onResume();
MyApplication app = (MyApplication)getApplication();
app.resume(this);
}
public void button_handler(View v){
String val = mUserInput.getText().toString();
String file = "myfile.txt";
MyApplication app = (MyApplication)getApplication();
app.MakeFileThread(val,file);
app.startCounterThread();
}
public void updateCountDown(final int seconds){
String secondText = String.valueOf(seconds);
mText.setText(secondText);
}
public void updateFileOutput(final String userText){
mText.setText(userText);
}
}
public class CounterApplication extends Application {
private MainActivity currentActivity = null;
private CounterThread myThread;
public synchronized void resume(MainActivity update){
this.currentActivity = update;
myThread.interrupt();
}
public synchronized void pause(MainActivity update){
this.currentActivity = update;
myThread.interrupt();
}
public synchronized void update(final long seconds){
Runnable notifyAction = new Runnable(){
public void run(){
if(currentActivity != null){
currentActivity.updateResult(seconds);
}
}
};
currentActivity.runOnUiThread(notifyAction);
}
public void onCreate(){
super.onCreate();
myThread = new CounterThread();
myThread.start();
}
private class CounterThread extends Thread{
private long seconds = 0;
private boolean running = false;
#Override
public void run(){
while(true){
try{
Thread.sleep(running?1000:100000);
}
catch(InterruptedException e){
running = !running;
}
if(interrupted()){
running = !running;
}
if(running){
update(++seconds);
}
}
}
}
}
I am not sure, this might help you, add the following line in your manifest file for the activities you need to work in both portrait and landscape:
android:configChanges="orientation|screenSize"
I'm trying to update my digital clock using timertask. I have created a function called updateClock() which sets the hours and minutes to the current time but I haven't been able to get it to run periodically. From what I've read in other answers one of the best options is to use timertask however I haven't been able to make any example I found online work inside an Android activity.
This is what I've written so far:
public class MainActivity extends Activity {
TextView hours;
TextView minutes;
Calendar c;
int cur_hours;
int cur_minutes;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.clock_home);
hours = (TextView) findViewById(R.id.hours);
minutes = (TextView) findViewById(R.id.minutes);
updateClock();
}
public void updateClock() {
c = Calendar.getInstance();
hours.setText("" + c.get(Calendar.HOUR));
minutes.setText("" + c.get(Calendar.MINUTE));
}
public static void init() throws Exception {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
updateClock(); // ERROR
}
}, 0, 1 * 5000);
}
}
How can I make it work?
Use runOnUiThread for updating Ui from Timer Thread
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
MainActivity.this.runOnUiThread (new Runnable() {
public void run() {
updateClock(); // call UI update method here
}
}));
}
}, 0, 1 * 5000);
}
if you just need updates every minute, you can also listen to the ACTION_TIME_TICK broadcast event.
private boolean timeReceiverAttached;
private final BroadcastReceiver timeReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
updateClock();
}
};
private Handler handler = new Handler();
#Override
protected void onResume() {
super.onResume();
updateClock();
if (!timeReceiverAttached) {
timeReceiverAttached = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
registerReceiver(timeReceiver, filter, null, handler);
}
}
#Override
protected void onPause() {
super.onPause();
if (timeReceiverAttached) {
unregisterReceiver(timeReceiver);
timeReceiverAttached = false;
}
}
OR, periodically post the Runnable to the Handler of UI thread. Also, pause and resume tasks to save battery.
public class MyActivity extends Activity {
private final Handler mHandler = new Handler();
private final Timer mTimer = new Timer();
#Override
protected void onResume() {
super.onResume();
mTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
mHandler.post(new Runnable() {
#Override
public void run() {
//---update UI---
}
});
}
},0,5000);
}
#Override
protected void onPause() {
super.onPause();
mTimer.cancel();
}
}