Android object value changed when resume - android

I got a VERY STRANGE situation...(to me)
For example, 2 objects,
1 is an activity member boolean called isInPage,
2 is a static bitmap object called bmpPhoto.
When I get into my own activity called FacebookShareActivity
isInPage will be true until I quit this activity,
bmpPhoto will be given a picture.
After onCreare() and onResume(), there is no any code running, until user click some GUI.
What I did is close screen by press hardware power button, and maybe wait 5 or 10 minutes.
OK, now I press porwe again to wake phone up, unlock screen,
and my FacebookShareActivity goes back to front.
And I click my GUI button to check variable value via Logcat, it says:
isInPage=false;
And I forget bmpPhoto's value, but on my GUI, the photo just gone,
not displayed anymore...
How is this happen ?
And it just not happen every time after I do that......
What if I override onSaveInstanceState(Bundle savedInstanceState) and
onRestoreInstanceState(Bundle savedInstanceState) ?
Will it help ?
And what about the bitmap object ?
Still don't know how is that happen...
Did I miss something ?
I really need your help, please everyone~
Following is part of my code, quite long...
The "isPageRunning" and "bmp" changed sometime when back from desktop, but not everytime.
public class FacebookShareActivity extends Activity
{
private Bundle b=null;
private Bitmap bmp=null;
private boolean isFacebookWorking=false;
private boolean isPageRunning=true; //This value sometime changed when back from desktop, but not every time
protected void onCreate(Bundle savedInstanceState)
{
Log.i(Constants.TAG, "ON Facebook Share create......");
super.onCreate(savedInstanceState);
setContentView(R.layout.facebook_share);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
private void initUI()
{
btnBack=(Button)findViewById(R.id.btnBack);
btnBack.setOnClickListener(new ButtonClickHandler());
formImage=(RelativeLayout)findViewById(R.id.form_image);
formImage.setDrawingCacheEnabled(true);
btnShare=(Button)findViewById(R.id.btnShare);
btnShare.setOnClickListener(new ButtonClickHandler());
txtIntroText=(TextView)findViewById(R.id.txtIntroText);
txtIntroText.setOnClickListener(new ButtonClickHandler());
txtIntroText.setText(getUploadInImageText());
photo=(ImageView)findViewById(R.id.photo);
bmp=Constants.PROFILE.getName().getPhoto();
if(bmp!=null)
{photo.setImageBitmap(bmp);} //bmp wouldn't be null, it filled by some other activity before
}
#Override
protected void onResume()
{
super.onResume();
Log.i(Constants.TAG, "Trying to set UI on resume...");
b=getIntent().getExtras();
// ...
// ... Get some String value passed from prev activity
facebook=new Facebook("123456789012345"); //Test
asyncFacebook=new AsyncFacebookRunner(facebook);
initUI();
System.gc();
}
#Override
public void onBackPressed()
{
Log.d(Constants.TAG, "Activity receive back key...");
lockButtons(false);
return;
}
private void lockButtons(boolean b)
{
if(isPageRunning)
{
btnBack.setClickable(!b);
btnShare.setClickable(!b);
}
}
private class DelayReleaseKey implements Runnable
{
public void run()
{
try{Thread.sleep(10000);}
catch(InterruptedException ie){}
handler.sendEmptyMessage(0);
}
}
private class ButtonClickHandler implements OnClickListener
{
public void onClick(View v)
{
if(v==btnBack)
{
if(isFacebookWorking)
{ShowAlertDialog(Constants.MESSAGE_FACEBOOK_WORK);}
else
{
lockButtons(true);
formImage=null;
photo=null;
b=null;
facebook=null;
isPageRunning=false;
Intent intent=new Intent(FacebookShareActivity.this, PracticeListActivity.class);
startActivity(intent);
FacebookShareActivity.this.finish();
overridePendingTransition(android.R.anim.slide_in_left,android.R.anim.slide_out_right);
}
}
if(v==btnShare)
{
lockButtons(true);
facebookLogin();
}
}
}
}
Now I know i must override onSaveInstanceState, onRestoreInstanceState.
They can help me to save variable like String, int, boolean...
What about Bitmap ?
And what if my variable is static ?

Now try again.
public class FacebookShareActivity extends Activity
{
private Bundle b=null;
private static Bitmap bmp=null;
private static boolean isFacebookWorking=false;
private static boolean isPageRunning=true; //This value sometime changed when back from desktop, but not every time
protected void onCreate(Bundle savedInstanceState)
{
Log.i(Constants.TAG, "ON Facebook Share create......");
super.onCreate(savedInstanceState);
setContentView(R.layout.facebook_share);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
private void initUI()
{
btnBack=(Button)findViewById(R.id.btnBack);
btnBack.setOnClickListener(new ButtonClickHandler());
formImage=(RelativeLayout)findViewById(R.id.form_image);
formImage.setDrawingCacheEnabled(true);
btnShare=(Button)findViewById(R.id.btnShare);
btnShare.setOnClickListener(new ButtonClickHandler());
txtIntroText=(TextView)findViewById(R.id.txtIntroText);
txtIntroText.setOnClickListener(new ButtonClickHandler());
txtIntroText.setText(getUploadInImageText());
photo=(ImageView)findViewById(R.id.photo);
bmp=Constants.PROFILE.getName().getPhoto();
if(bmp!=null)
{photo.setImageBitmap(bmp);} //bmp wouldn't be null, it filled by some other activity before
}
#Override
protected void onResume()
{
super.onResume();
isPageRunning = true;
Log.i(Constants.TAG, "Trying to set UI on resume...");
b=getIntent().getExtras();
// ...
// ... Get some String value passed from prev activity
facebook=new Facebook("123456789012345"); //Test
asyncFacebook=new AsyncFacebookRunner(facebook);
initUI();
System.gc();
}
#Override
protected void onPause()
{
isPageRunning = false;
}
#Override
public void onBackPressed()
{
Log.d(Constants.TAG, "Activity receive back key...");
lockButtons(false);
return;
}
private void lockButtons(boolean b)
{
if(isPageRunning)
{
btnBack.setClickable(!b);
btnShare.setClickable(!b);
}
}
private class DelayReleaseKey implements Runnable
{
public void run()
{
try{Thread.sleep(10000);}
catch(InterruptedException ie){}
handler.sendEmptyMessage(0);
}
}
private class ButtonClickHandler implements OnClickListener
{
public void onClick(View v)
{
if(v==btnBack)
{
if(isFacebookWorking)
{ShowAlertDialog(Constants.MESSAGE_FACEBOOK_WORK);}
else
{
lockButtons(true);
formImage=null;
photo=null;
b=null;
facebook=null;
isPageRunning=false;
Intent intent=new Intent(FacebookShareActivity.this, PracticeListActivity.class);
startActivity(intent);
FacebookShareActivity.this.finish();
overridePendingTransition(android.R.anim.slide_in_left,android.R.anim.slide_out_right);
}
}
if(v==btnShare)
{
lockButtons(true);
facebookLogin();
}
}
}
}

For primitive values, you should use onSaveInstanceState. For restoring you can use onRestoreInstanceState or you can some code in onCreate like this:
if(savedInstanceState != null) {
// restore old state
} else {
// a fresh start
}
Now for restoring objects like Bitmap if they are not expensive to create and doesn't make your UI sluggish, create them again on restore. If you do not want to that then use onRetainNonConfigurationInstance and code will look like this:
#Override
public Object onRetainNonConfigurationInstance () {
return bmp;
}
#Override
public void onCreate() {
if
bmp = (Bitmap)getLastNonConfigurationInstance();
}
WARNING: This api is deprecate, you might use it on old platforms. I put it here for illustration purpose. The new way to do this is more involving.
Here is detailed ref:
getLastNonConfigurationInstance
onRetainNonConfigurationInstance

Related

Disable a button when an event occure in main activity

I have two activities named Main activity and Second Activity. Main activity has an event handler. I need to disable a button in second activity when an event occurs.
Main activity
public void myEventListener(int eventID){
switch (eventID) {
case : 0
// disable button of second activity here
break;
}
}
This is an easy one.
Use SharedPreference of changing data(boolean maybe) in MainAcitivity
Use SharedPreference.OnSharedPreferenceChangeListener in SecondActivity for listening to that specific data and changing button state at runtime in.
MainActivity.java
public class MainActivity extends AppCompatActivity {
SharedPreferences.Editor editor;
public void myEventListener(int eventID){
switch (eventID) {
case 0:
editor = getSharedPreferences("pref",MODE_PRIVATE).edit();
editor.putBoolean("event",true);
break;
}
}
}
SecondActivity
public class SecondActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
SharedPreferences sharedPreferences;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
}
#Override
protected void onStart() {
super.onStart();
sharedPreferences=getSharedPreferences("pref",MODE_PRIVATE);
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onStop() {
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equals("event") && sharedPreferences.getBoolean(key,false))
{
//add your code to disable your button or any action you want
}
}
}
It's very simple to disable a button. Follow the below steps to achieve your problem.
Define a global boolean value as "false"
In onClickEvent override, the boolean value as "true".
Then check with the boolean value as follows
private boolean isClicked = false;
if(isClicked){
button.disabled(true);
} else {
button.disabled(false);
}
Please let me know if you have any issues while applying.
In you First Activity make Boolean static variable.
Example:
FirstActivity
create a Boolean static global variable
public static Boolean clicked = false;
onFirstActivity if Event occurs.
event occurred => clicked = true; otherwise it is false
SecondActivity
in second activity get the value to static boolean from FirstActivity
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (FirstActivity.clicked){
//Do Nothing
}else{
//Perform action
}
}
});
first make reference of second activity and set button visibility GONE or INVISIBLE It's Work
SeconActivity sa; //reference of second activity
public void myEventListener(int eventID){
switch (eventID) {
case : 0
sa.btnofsecondactivity.setVisibilty(View.GONE);
break;
}
}
You can go with LocalBroadCastManager.
in MainActivity wherever you want to trigger the method
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("event-occured"));
in SecondActivity register the LocalBroadcastManager and receive it.
public class SecondActivity extends AppCompatActivity {
private BroadcastReceiver mainActivityReceiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mainActivityReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// do whatever you want to do
Log.d("TAG", "broadcast received");
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(mainActivityReceiver, new IntentFilter("main-activity-initialized"));
}
#Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mainActivityReceiver);
}
Don't forget to unregister the listener in SecondActivity's onDestroy method. Taken reference from here.

Handle AsyncTask if the task completes while the Activity is in the background

I've been using AsyncTasks for a while however, I've recently encountered a scenario where I'm unsure of how to handle correctly. Since I thought it would be a somewhat common scenario I decided to ask the question here.
So, I'm trying to use an AsyncTask to make a simple call to sign a user in to the app. After the call completes, if it succeeds, the user should be taken to another activity. This logic is simple. The problem arrises when the user navigates away from the app before the sign in call returns. In such a case, what should I do in onPostExecute()?
What I've seen some apps do is they continue with the call anyways, as long as the activity is still around, and will launch the next activity. However this creates a weird experience where the user navigates away from the app, then several seconds later, the app just pops back up in their face. Of course, I would like to avoid doing this.
Update
Example code:
public class ExampleActivity extends Activity {
private boolean mIsPaused;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
btnSignIn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
new SignInTask(ExampleActivity.this).execute();
}
});
...
}
#Override
protected void onPause() {
super.onPause();
mIsPaused = true;
}
#Override
protected void onResume() {
super.onResume();
mIsPaused = false;
}
private boolean isPaused() {
return mIsPaused;
}
...
private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {
private final WeakReference<ExampleActivity> mAct;
public SignInTask(ExampleActivity act) {
mAct = new WeakReference<ExampleActivity>(act);
}
#Override
protected SomeResult doInBackground(Void... params) {
return mApi.signIn(creds);
}
#Override
protected void onPostExecute(SomeResult result) {
if (result.getCode() == OK) {
ExampleActivity act = mAct.get();
if (act != null) {
if (act.isPaused()) {
// do something
} else {
startActivity(new Intent(act, NextActivity.class));
}
} else {
// do something
}
}
}
}
}
made your AsyncTask class as static inner class.
Pretty interesting problem... Going with what you've started by using booleans, you could save the response the Activity receives to the SharedPreferences in the event it is paused, or continue processing normally if it is not. If the Activity later resumes (or is recreated), check whether or not there is a saved response and handle accordingly. I was thinking something along the lines of:
import org.json.JSONObject;
import android.app.Activity;
import android.os.Bundle;
public class TaskActivity extends Activity {
private static final String KEY_RESPONSE_JSON = "returned_response";
private boolean paused = false;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// don't setup here, wait for onPostResume() to figure out what to do
}
#Override
public void onPostResume(){
super.onPostResume();
paused = false;
if(isSavedResponseAvailable()) processResponse(getSavedResponse());
else setup();
}
#Override
public void onPause(){
paused = true;
super.onPause();
}
private void setup(){
// normal setup
}
public void onReceiveResponse(JSONObject response){
if(paused) setSavedResponse(response);
else processResponse(response);
}
private void processResponse(JSONObject response){
// Continue with processing as if they never left
getSharedPreferences(this.getClass().getName(), 0).edit().clear().commit(); // Clear everything so re-entering won't parse old data
}
private boolean isSavedResponseAvailable(){
return getSavedResponse() != null;
}
private JSONObject getSavedResponse(){
try{
return new JSONObject(getSharedPreferences(this.getClass().getName(), 0).getString(KEY_RESPONSE_JSON, ""));
}
catch(Exception e){ }
return null;
}
private void setSavedResponse(JSONObject response){
getSharedPreferences(this.getClass().getName(), 0).edit().putString(KEY_RESPONSE_JSON, response.toString()).commit();
}
}
Clearly that's assuming your response from the task is JSON, but there's no reason you couldn't extend that to save the data individually and rebuild the necessary response object from the saved preference data.
As far as clean approaches go, though... I give this about a 3/10, but I can't think of anything better (well, other than making the TaskActivity abstract and forcing implementations to override setup(), processResponse(), isResponseAvailable(), getSavedResponse(), and setSavedResponse(), but that would only be mildly better for like a 4/10)
I would suggest putting a try/catch statement in the post execute - as far as I know what would happen in this situation is that you would get some kind of Window Manager exception.
What I would STRONGLY recommend, however, is stopping any async tasks (with the cancel method) on the onPause method, meaning that you won't interrupt them.
http://developer.android.com/reference/android/os/AsyncTask.html#cancel(boolean)
public final boolean cancel (boolean mayInterruptIfRunning)
Added in API level 3
Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
Calling this method will result in onCancelled(Object) being invoked on the UI thread after doInBackground(Object[]) returns. Calling this method guarantees that onPostExecute(Object) is never invoked. After invoking this method, you should check the value returned by isCancelled() periodically from doInBackground(Object[]) to finish the task as early as possible.
Parameters
mayInterruptIfRunning true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete.
Returns
false if the task could not be cancelled, typically because it has already completed normally; true otherwise
See Also
isCancelled()
onCancelled(Object)
boolean isRunning; //set it to true in onResume, and false in onStop
boolean isWaiting; // set it to true in onPostExecute, if "isRunning" is false
check in onResume whether isWaiting is true, if yes, take user to another screen.
Use the cancel() of AsynchTask class onBackPress() of Activty class
public class ExampleActivity extends Activity {
private boolean mIsPaused;
SignInTask singleTaskObj;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
btnSignIn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
singleTaskObj = new SignInTask(ExampleActivity.this).execute();
}
});
...
}
#Override
protected void onPause() {
super.onPause();
mIsPaused = true;
}
#Override
protected void onResume() {
super.onResume();
mIsPaused = false;
}
protected void onBackPressed()
{
singleTaskObj.cancel();
}
private boolean isPaused() {
return mIsPaused;
}
...
private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {
private final WeakReference<ExampleActivity> mAct;
public SignInTask(ExampleActivity act) {
mAct = new WeakReference<ExampleActivity>(act);
}
#Override
protected SomeResult doInBackground(Void... params) {
return mApi.signIn(creds);
}
#Override
protected void onPostExecute(SomeResult result) {
if (result.getCode() == OK) {
ExampleActivity act = mAct.get();
if (act != null) {
if (act.isPaused()) {
// do something
} else {
startActivity(new Intent(act, NextActivity.class));
}
} else {
// do something
}
}
}
}
}

Android - does defining a button static leak my activity? Can I avoid it?

I need the button to be static so I can enable it/ disable it form my services in case the activity is shown. Still I setOnClickListener and anyway static views are considered dangerous. Do I leak ? Can I avoid it ?
public class MonitorActivity extends FragmentActivity implements
OnClickListener {
private static Button updateButton; // static??
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_monitor);
// button
updateButton = (Button) findViewById(R.id.update_data_button);
updateButton.setOnClickListener(this); // oops ?
}
public static void onDataUpdated(Context ctx) {
if (updateButton != null) { //that's why I need it static
updateButton.setEnabled(true); // + set the text etc
}
}
public static void onUpdating() {
if (updateButton != null) {
updateButton.setEnabled(false);
}
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.update_data_button:
serviceIntent.putExtra(MANUAL_UPDATE_INTENT_KEY, true);
this.startService(serviceIntent);
}
}
#Override
protected void onResume() {
super.onResume();
Boolean isUpdating = AccessPreferences.get(this, updateInProgressKey,
false);
// set the button right
updateButton.setText((isUpdating) ? defaultUpdatingText
: getResources().getString(R.string.update_button_text));
updateButton.setEnabled(!isUpdating);
}
}
I think You can create BroadcastReceiver in MonitorActivity. And send extras message from Service to enable/disable button.
I suggest you use LocalBroadcastManager
In your Activity define a BroadcastReceiver and register the Broadcast in onStart()onResume() and unregister it in onStop()onPause().
From your Service send the Broadcast to the Activity if the Activity is active it will receive the Broadcast and update the UI, if not nothing will happen.
Define another BroadcastReceiver in your Service, Register the Broadcast in onCreate() and Unregister it in onDestroy().
When your Activity is started send a Broadcast to the Service and let the Service reply to the Activity using the first Broadcast to update the UI.
UPDATE
After doing some investigation I found you're correct "sticky broadcasts are discouraged", but if you check the date of that post it's on 2008 - before Google implemented the LocalBroadcastManager.
And I have checked the source code of LocalBroadcastManager, it's not a real Broadcast it's an interface, Singleton with a list of BroadcastReceivers (not global and no IPC communication).
I really hate public static and I always avoid them. every body should.
So yes - the static button would leak my activity. I came up with callback below but it is ugly. I finally solved it by making the Activity extend OnSharedPreferenceChangeListener
public final class MonitorActivity extends FragmentActivity implements
OnClickListener, OnSharedPreferenceChangeListener {
private Button updateButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
updateButton = (Button) findViewById(R.id.update_data_button);
updateButton.setOnClickListener(this); //no need to unregister methinks
}
#Override
public synchronized void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
if (updateInProgressKey.equals(key)) {
final Boolean isUpdating = AccessPreferences.get(this,
updateInProgressKey, false);
// set the button right
updateButton.setText((isUpdating) ? sDefaultUpdatingText
: sUpdateButtonTxt);
updateButton.setEnabled(!isUpdating);
}
}
#Override
protected void onStart() {
super.onStart();
AccessPreferences.registerListener(this, this);
AccessPreferences.callListener(this, this, updateInProgressKey);
}
#Override
protected void onStop() {
// may not be called (as onStop() is killable), but no leak,
// see: http://stackoverflow.com/a/20493608/281545
AccessPreferences.unregisterListener(this, this);
super.onStop();
}
}
Callback
onPause() is guaranteed to run - so I null the static fields there and populate them on onResume(). I only do a read from default shared preferences so it should not take long in the synchronized blocks (I have to synchronize cause the service might call onUpdating() or onDataUpdated() any odd time). Not sure about unregistering the listener though
public class MonitorActivity extends FragmentActivity implements
OnClickListener {
private static TextView dataTextView; //null this onPause() to avoid a leak
private static Button updateButton; // null this onPause() to avoid a leak
// ...
public static synchronized void onDataUpdated(Context ctx) {
if (updateButton != null) {
updateButton.setEnabled(true); // + set the text etc
}
}
public static synchronized void onUpdating() {
if (updateButton != null) {
updateButton.setEnabled(false);
}
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.update_data_button:
serviceIntent.putExtra(MANUAL_UPDATE_INTENT_KEY, true);
this.startService(serviceIntent);
}
}
#Override
protected void onResume() {
super.onResume();
synchronized (MonitorActivity.class) {
Boolean isUpdating = AccessPreferences.get(this,
updateInProgressKey, false);
updateButton = (Button) findViewById(R.id.update_data_button);
updateButton.setOnClickListener(this);
// set the button right
updateButton.setText((isUpdating) ? defaultUpdatingText
: getResources().getString(R.string.update_button_text));
updateButton.setEnabled(!isUpdating);
}
}
#Override
protected void onPause() {
synchronized (MonitorActivity.class) {
updateButton.setOnClickListener(null); // TODO : needed ??
dataTextView = updateButton = null; // to avoid leaking my activity
}
super.onPause();
}
}
There are 3 solutions for you:
Set button = null when context is destroyed(onStop);
Use WeakReference for button field, Example:
private static WeakReference<Button> updateButton;
Not creating static button. It's always hold the context.

Updating Views through AsyncTasks

Below is working sample code of an Android Activity that has 2 buttons, of which only 1 is ever visible: a refresh button and a stop button.
This code does the following:
Refresh is clicked: start some AsyncTasks. The preExecute of the tasks will hide the refresh button and show the stop button. The postExecute of the task will check if there are no more running tasks and if so, show the refresh button and hide the stop button.
Stop is clicked: all tasks are cancelled, the refresh button is shown and the stop button is hidden.
This code works fine, but for one exception: when I recreate the activity while a task is running by changing the screen orientation. The buttons will now return to the state as defined in the xml (refresh=visibile, stop=gone).
Using a static variable to keep track of the current state of the visibility only makes it worse, because the running Task that has to toggle it back, can only modify views in the calling activity, which has been stopped or destroyed at that point!
public class MainActivity extends Activity
{
private static List<MyAsyncTask> activeTasks = new LinkedList<MyAsyncTask>();
private View refresh;
private View stop;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_main);
this.refresh = findViewById(R.id.refresh);
this.refresh.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{ // Start a couple tasks
new MyAsyncTask(MainActivity.this).execute();
new MyAsyncTask(MainActivity.this).execute();
}
});
this.stop = findViewById(R.id.stop);
this.stop.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{ // Cancel all tasks and toggle refresh button
cancelAll();
MainActivity.this.enableRefresh(true);
}
});
}
public void enableRefresh(boolean enable)
{
if (this.refresh != null && this.stop != null)
{
this.refresh.setVisibility(enable ? View.VISIBLE : View.GONE);
this.stop.setVisibility(!enable ? View.VISIBLE : View.GONE);
}
}
public static void cancelAll()
{
for (MyAsyncTask task : MainActivity.activeTasks)
task.cancel(true);
MainActivity.activeTasks = new LinkedList<MyAsyncTask>();
}
private class MyAsyncTask extends AsyncTask<Void,Void,Void>
{
private MainActivity activity;
public MyAsyncTask(MainActivity activity)
{
this.activity = activity;
}
#Override
protected void onPreExecute()
{
MainActivity.activeTasks.add(this);
this.activity.enableRefresh(false);
}
#Override
protected Void doInBackground(Void... v)
{
try
{ // Simulate a task
Thread.sleep(3000);
}
catch (InterruptedException e)
{
}
return null;
}
#Override
protected void onPostExecute(Void v)
{
MainActivity.activeTasks.remove(this);
if (MainActivity.activeTasks.size() == 0)
this.activity.enableRefresh(true);
}
}
}
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
See this it will help you ... on handling the states in orientation change
to handle AsyncTasks on screen orientation follow this example
MyAsyncTask myasynce;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_main);
this.refresh = findViewById(R.id.refresh);
this.refresh.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
//Register new Task
myasynce = ( MyAsyncTask ) new MyAsyncTask(MainActivity.this).execute();
}
});
this.stop = findViewById(R.id.stop);
this.stop.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{ // Cancel all tasks and toggle refresh button
cancelAll();
MainActivity.this.enableRefresh(true);
}
});
}
now onSaveInstanceState add
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//put myasynce status true if still runing false if finished
outState.putBoolean("myasynce", ( myasynce != null && query.getStatus() != AsyncTask.Status.FINISHED ) ? true : false );
if ( myasynce != null )
{
myasynce.cancel(true);
}
}
on savedInstanceState
add
if ( savedInstanceState.getBoolean("myasynce") == true )
{
//if task was running before screen orientation run it again
myasynce = ( MyAsyncTask ) new MyAsyncTask(MainActivity.this).execute();
}
hope this help
I think this link http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html by Tony Stark is probably the best solution, because that potentially solves even more problems.
However, I think I came up with an simpler solution for the problem at hand:
Add static vars to MainActivity:
private static MainActivity current;
private static boolean enableRefresh = true;
Save input value of enableRefresh():
public static void enableRefresh(boolean enableRefresh)
{
MainActivity.enableRefresh = enableRefresh;
(...) // Same as before
}
Add to MainActivity onCreate():
MainActivity.current = this;
enableRefresh(enableRefresh);
In the AsyncTask, use
MainActivity.current as the activity to update, instead of the activity provided in the constructor.

Android save data from nested AsyncTask onPostExecute after screen rotation

I have spent many hours looking for a solution to this and need help.
I have a nested AsyncTask in my Android app Activity and I would like to allow the user to rotate his phone during it's processing without starting a new AsyncTask. I tried to use onRetainNonConfigurationInstance() and getLastNonConfigurationInstance().
I am able to retain the task; however after rotation it does not save the result from onPostExecute() to the outer class variable. Of course, I tried getters and setters. When I dump the variable in onPostExecute, that it is OK. But when I try to access to the variable from onClick listener then it is null.
Maybe the code will make the problem clear for you.
public class MainActivity extends BaseActivity {
private String possibleResults = null;
private Object task = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.task = getLastNonConfigurationInstance();
setContentView(R.layout.menu);
if ((savedInstanceState != null)
&& (savedInstanceState.containsKey("possibleResults"))) {
this.possibleResults = savedInstanceState
.getString("possibleResults");
}
if (this.possibleResults == null) {
if (this.task != null) {
if (this.task instanceof PossibleResultWebService) {
((PossibleResultWebService) this.task).attach();
}
} else {
this.task = new PossibleResultWebService();
((PossibleResultWebService) this.task).execute(this.matchToken);
}
}
Button button;
button = (Button) findViewById(R.id.menu_resultButton);
button.setOnClickListener(resultListener);
}
#Override
protected void onResume() {
super.onResume();
}
OnClickListener resultListener = new OnClickListener() {
#Override
public void onClick(View v) {
Spinner s = (Spinner) findViewById(R.id.menu_heatSpinner);
int heatNo = s.getSelectedItemPosition() + 1;
Intent myIntent = new Intent(MainActivity.this,
ResultActivity.class);
myIntent.putExtra("matchToken", MainActivity.this.matchToken);
myIntent.putExtra("heatNo", String.valueOf(heatNo));
myIntent.putExtra("possibleResults",
MainActivity.this.possibleResults);
MainActivity.this.startActivityForResult(myIntent, ADD_RESULT);
}
};
private class PossibleResultWebService extends AsyncTask<String, Integer, Integer> {
private ProgressDialog pd;
private InputStream is;
private boolean finished = false;
private String possibleResults = null;
public boolean isFinished() {
return finished;
}
public String getPossibleResults() {
return possibleResults;
}
#Override
protected Integer doInBackground(String... params) {
// quite long code
}
public void attach() {
if (this.finished == false) {
pd = ProgressDialog.show(MainActivity.this, "Please wait...",
"Loading data...", true, false);
}
}
public void detach() {
pd.dismiss();
}
#Override
protected void onPreExecute() {
pd = ProgressDialog.show(MainActivity.this, "Please wait...",
"Loading data...", true, false);
}
#Override
protected void onPostExecute(Integer result) {
possibleResults = convertStreamToString(is);
MainActivity.this.possibleResults = possibleResults;
pd.dismiss();
this.finished = true;
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (this.possibleResults != null) {
outState.putString("possibleResults", this.possibleResults);
}
}
#Override
public Object onRetainNonConfigurationInstance() {
if (this.task instanceof PossibleResultWebService) {
((PossibleResultWebService) this.task).detach();
}
return (this.task);
}
}
It is because you are creating the OnClickListener each time you instantiate the Activity (so each time you are getting a fresh, new, OuterClass.this reference), however you are saving the AsyncTask between Activity instantiations and keeping a reference to the first instantiated Activity in it by referencing OuterClass.this.
For an example of how to do this right, please see https://github.com/commonsguy/cw-android/tree/master/Rotation/RotationAsync/
You will see he has an attach() and detach() method in his RotationAwareTask to solve this problem.
To confirm that the OuterClass.this reference inside the AsyncTask will always point to the first instantiated Activity if you keep it between screen orientation changes (using onRetainNonConfigurationInstance) then you can use a static counter that gets incremented each time by the default constructor and keep an instance level variable that gets set to the count on each creation, then print that.

Categories

Resources