Reputation: 45
The following simple example code have an asynchronous task that repeatedly sleeps for 2 seconds then trigger a callback function onResultAvailable
with "some text .."
(which I use here to simulate a websocket thread that receives messages ...).
The onResultAvailable
method in the MyViewModel
class is supposed to update the variable text
(which is a MutableLiveData<String>
) using postValue
(because it is called from a background thread).
So this code is supposed to update the text
variable each two seconds and display the text via the observer in MyTestActivity
; so it is supposed to display the following, which works as expected the first time:
Some text 0
Some text 1
Some text 2
... etc
However, when I press the backbutton and then open MyTestActivity
again, the text
variable isn't modified anymore by postvalue
and the observer isn't called anymore. Note that the other variables which are modified using setvalue
are still behaving as expected.
Any ideas why the postvalue
works fine from background thread the first time, but doesn't work anymore when the activity is destroyed then recreated ? How, can I update the value of the text
variable in this case ?
MainActivity.java
public class MainActivity extends AppCompatActivity {
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.myButton);
button.setOnClickListener(v -> {
startActivity(new Intent(MainActivity.this, MyTestActivity.class));
});
}
}
MyTestActivity.java
public class MyTestActivity extends AppCompatActivity {
TextView myTextView, myTextViewTime;
Button myButton;
MyViewModel vm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_test);
myTextView = findViewById(R.id.myTextView);
myTextViewTime = findViewById(R.id.myTextViewTime);
myButton = findViewById(R.id.myButton);
vm = ViewModelProviders.of(this).get(MyViewModel.class);
vm.getText().observe(this, text -> {
myTextView.setText(text);
Log.d("DEBUG", "observed: " + text);
});
vm.getTimestamp().observe(this, strTs -> {
myTextViewTime.setText(strTs);
Log.d("DEBUG", "observed: " + strTs);
});
myButton.setOnClickListener(v -> {
vm.setTimestamp("" + System.currentTimeMillis());
});
}
}
MyViewModel.java
public class MyViewModel extends ViewModel implements MyAsyncTaskDelegate {
private MutableLiveData<String> text;
private MutableLiveData<String> timestamp;
private MyAsyncTask task;
public MyViewModel() {
text = new MutableLiveData<>();
text.setValue("Hello World!");
timestamp = new MutableLiveData<>();
timestamp.setValue(""+System.currentTimeMillis());
task = new MyAsyncTask(this);
task.execute();
}
public LiveData<String> getText() {
return text;
}
public LiveData<String> getTimestamp() {
return timestamp;
}
public void setTimestamp(String ts) {
timestamp.setValue(ts);
}
@Override
public void onResultAvailable(String result) {
text.postValue(result);
}
}
MyAsyncTask.java
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private MyAsyncTaskDelegate delegate;
MyAsyncTask(MyAsyncTaskDelegate delegate) {
this.delegate = delegate;
}
@Override
protected Void doInBackground(Void... voids) {
for(int i = 0; i < 100; i++) {
try {
Thread.sleep(2000);
delegate.onResultAvailable("Some text " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
}
MyAsyncTaskDelegate.java
public interface MyAsyncTaskDelegate {
void onResultAvailable(String result);
}
Upvotes: 2
Views: 1907
Reputation: 13009
Any ideas why the postvalue works fine from background thread the first time, but doesn't work anymore when the activity is destroyed then recreated ?
In a nutshell: this happens because MyAsyncTask.doInBackground()
will loop infinitely and for most Android versions, there is no parallel execution of AsyncTask
s.
In MyTestActivity.onCreate()
, you generate a new instance of MyViewModel
which starts its own MyAsyncTask
. Because the first instance of MyAsyncTask
never finishes, the second one never really starts. The first instance of MyAsyncTask
still holds on to the first instance of MyViewModel
(aka MyAsyncTaskDelegate
). But since this first MyViewModel
instance does not have any connection to an active UI element, the updates don't show on the screen.
You can verify this with a debugger by setting breakpoints in the various methods and comparing the "instance numbers" (there is a '@' and a unique number appended to each class name)
Please note that the problematic behavior only happens if you leave MyTestActivity
by pressing BACK. If you rotate the device, the updates continue.
This is "working as intended". By pressing BACK, you indicate that you don't need the current instance of MyTestActivity
anymore: it won't be kept on the back stack, it can and will be completely destroyed.
The AsyncTask
is another case: it is a member of MyViewModel
and on the other hand holds a reference to MyViewModel
. One could say there is a reference deadlock: it will only be stopped if the whole surrounding pocess is stopped.
How can I update the value of the text variable in this case ?
Override onBackPressed()
in MyTestActivity
and stop the execution of MyAsyncTask
. See the docs on AsyncTask.cancel() on how to do so.
Upvotes: 2