Currently I am trying to collect location updates periodically, say for every 10 samples. I am using an arraylist to collect them and clear off the arraylist in the main UI thread once I passed them to the server using async task. In the async task I am loading the arraylist with the one from main UI.
The problem is, it is clearing the arraylist in the async task even it is in separate variable. How can I keep the activity in sync. Do I need to sleep the main activity till the async task finishes. I am not sure about the variable. Can someone explain how to do this?
MainMapActivity(X){
locationupdate for every 1 min{
arraylist a;//this collects all location updates 10 samples each time
call asynctask b;
clear a;
}
asynctask b{
arraylist c = getall from a;
db= insert(c);//save a into database;
}
Clearing a in main UI clears of variable c. How can I prevent that? The variable c should be only cleared after saving all the data in it.
If I'm getting what you are trying to say is,then yes, we have a way to fix your problem using handlers.
In your async task, do something like this -
private mLocations;
public MyTask(Handler mResponseHandler, List<Location> mLocations){
super();
this.mLocations = mLocations;
this.mResponseHandler = mResponseHandler;
}
in onPostExecute,
onPostExecute(List<Location>){
#Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
Log.i(TAG, "result = "+result);
if (mResponseHandler == null) return;
MyLocationData<Location> resultData = new MyLocationData<Location>();
if(result != null && result){
resultData.requestSuccessful = true;
resultData.responseErrorCode = 0;
}else{
resultData.requestSuccessful = false;
resultData.responseErrorCode = errorCode; //set this when result is null
}
android.os.Message m = android.os.Message.obtain();
m.obj = resultData;
mResponseHandler.sendMessage(m);
}
}
Where MyLocationData is a model class in which I'm saving all relevant data. It can be a class like this -
public class MyLocationData<Type> {
public Type response;
public int responseErrorCode;
public boolean requestSuccessful;
}
Now, in your activity, you can get this data like,
private Handler mExportHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
MyLocationData<Location> responseData = (MyLocationData<Location>) msg.obj;
// your logic for fetching new locations from responseData and using them in your activity
};
};
Related
I need a safe practise to load data from the Database.
I need to load the data from the Sql db and put it into a list or recylerview.
So what i tryied, i created a class which call the method load more everytime when user reaches the end of the list. And than it load the data limited.
Like 'Select * From a Where 1 Limit listItemCount, 20'.
I run into problems with this because, the thread which was loading, started 2 times with the same count value. Than i have changed it to synch the global run method from the loading thread. And accessing the count from the background worker of the list. This helped me a lot, and the data was loaded correctly, but i got still troubles with this. Because i need to insert the data into listview from the background thread, because of the synchronisation and calling notify in main thread after. If i am not doing it like this, i will get still problem.
Thread A: Started work.
Thread B: Waiting for finishing thread A (synchronized)
Thread A: runOnUiThread() trying to fill data.
Thread B: Allowed to run the code, getting count from listView.
Thread B: Got the same count as A, because A not finished the insert
statement.
Thread A: Added data.
Thread B: Added same data.
After this i added a atomicboolean, that if the thread is running he is just not executed. With this it was working perfect, but sometimes you have to scroll up a bit and down to trigger the load more.
And i think this solution is a bit dirty for me. Anyone now how to load data in pieces depending on list size of the listview, in a background thread without this issues?
This method is getting called when reaching the end of list:
private ExecutorService executorService;
private void loadMore()
{
if(!isAdded())
return;
if(executorService == null)
executorService = Executors.newSingleThreadExecutor();
executorService.execute(new AsyncLoadMoreHashTags(getContext(), this, esaphTagAdapterVertical));
}
Background Thread:
public class AsyncLoadMoreHashTags extends MyDataLoaderClass
{
private static final Object obLock = new Object();
public AsyncLoadMoreHashTags(Context context, DataBaseLoadWaiter dataBaseLoadWaiter, SpecialRecylerView spRecylerView)
{
super(context, dataBaseLoadWaiter, spRecylerView);
}
#Override
public void run()
{
super.run();
synchronized (AsyncLoadMoreHashTags.obLock)
{
List<Object> objectList = null;
try
{
int startFrom = super.getStartFrom()[0];
SQLHashtags sqlHashtags = new SQLHashtags(super.getSoftReferenceContext().get());
if(startFrom <= 0)
{
objectList = sqlHashtags.getMostUsedHashtags();
}
else
{
objectList = sqlHashtags.getAllHashtagLimited(startFrom);
}
sqlHashtags.close();
}
catch (Exception ec)
{
Log.i(getClass().getName(), "AsyncLoadMoreHashTags run() failed: " + ec);
}
finally
{
publish(objectList);
}
}
}
}
What i need is to, just start one thread a time.
Anyone a idea?
First of all use You should have to use PagedList library provided by google, for implementing paging facility, and if you want to use background thread for database operations you can use Anko commons to do this. Here is an example,
internal val pagedList by lazy {
runBlocking { // Coroutine Context
withContext(Dispatchers.IO) {
// Read from database, and it will be initialised in background
}
}
}
and if you want something to be dispatched on UI but performed in Background Thread, then go with this, doAsync operation and then return data in uiThread by using a custom interface.
doAsync {
// LONG OPERATION
Thread.sleep(2000)
uiThread {
callback.onActionsDone(dataList)
}
}
Let me know if you want more help on this.
Fixed it with like this, working fine.
public class AsyncLoadMoreHashTags extends OwnDataLoader
{
private static AtomicBoolean obLock = new AtomicBoolean(false);
public AsyncLoadMoreHashTags(Context context, DataBaseLoadWaiter dataBaseLoadWaiter, MomentsRecylerView momentsRecylerView)
{
super(context, dataBaseLoadWaiter, momentsRecylerView);
}
#Override
public void run()
{
super.run();
if(!AsyncLoadMoreHashTags.obLock.compareAndSet(false, true)) //when any thread loading data, its return and skips.
return;
List<Object> objectList = null;
try
{
int startFrom = super.getStartFrom()[0];
SQLHashtags sqlHashtags = new SQLHashtags(super.getSoftReferenceContext().get());
if(startFrom <= 0)
{
objectList = sqlHashtags.getMostUsedHashtags();
}
else
{
objectList = sqlHashtags.getAllHashtagLimited(startFrom);
}
sqlHashtags.close();
System.out.println("TEST: " + startFrom + " LOAD: " + objectList.size());
}
catch (Exception ec)
{
Log.i(getClass().getName(), "AsyncLoadMoreHashTags run() failed: " + ec);
}
finally
{
publish(objectList, AsyncLoadMoreHashTags.obLock); //Passing the lock object, when data has been added, obLock.set(false); is called.
}
}
}
i've a little problem with my code.
I'm trying to perform a volley request on doInBackground behaviour and then to use its result to set some layout elements. Here's the async snippet:
private class MyAsyncTask extends AsyncTask<String, Void, Message>
{
#Override
protected Message doInBackground(String... params) {
rm = new RequestManager(MessageDisplay.this);
idM = params[0];
retrieveMessage(new VolleyCallbackOp(){
public void onSuccess(List<Message> ml) {
m = ml.get(0);
Log.d("print1", m.getContent()); <---- this is fine
}
});
Log.d("print2", m.getContent()); <---- m always null
return m;
}
#Override
protected void onPostExecute(Message m) {
super.onPostExecute(m);
Log.d("messaggio", m.getContent());
mds = new MessageDisplaySetter(MessageDisplay.this);
View v = mds.setMessageDisplayInfo(m, 3);
contentD.addView(v);
}
}
});
}
this code is called in activity onCreate():
Message m = new Message();
String target2 = i.getExtras().getString("messageId");
new MyAsyncTask().execute(target2);
The problem is that the Log "print2" tell the variable m is null while "print1" is ok.
Have you any tip to pass the result of this request to the related onPostExecute()?
The reason why your log for print2 is null - is because you are in a race condition. By calling new VolleyCallbackOp you are doing an asynchrounous network call which might take a while to complete. Your code does not wait until it's finished. That's why print2 is null most of the time.
There are several solutions for your case. One of them is to use the callback mechanism of onSuccess and do your logic there.
I am learning how to cancel asyncTask so there is no uses on the code below.
I tried to called the asyncTask and execute it then cancel it and execute it.
MyAsyncTask asyncTask = new MyAsyncTask();
Log.i("cancel","cancel 1");
asyncTask.execute();
Log.i("cancel","cancel 2");
asyncTask.onCancelled();
Log.i("cancel","cancel 3");
asyncTask.execute();
Log.i("cancel","cancel done");
"cancel 1" and "cancel 2" is executed finely as it shown on logcat but ActivityThread.performLaunchActivity error is thrown when "cancel 3" is trying to execute.
(cancel 3 is not showing on logcat) Anything wrong with my asyncTask code?
private class MyAsyncTask extends AsyncTask<String,Integer,Void>{
private ArrayList<Map<String, String>> list;
private ProgressBar progressBar;
#Override
protected Void doInBackground(String... arg0) {
progressBar = (ProgressBar)findViewById(R.id.year_expense_progressbar);
progressBar.setVisibility(View.VISIBLE);
getListView().setVisibility(View.GONE);
textView.setVisibility(View.GONE);
list = new ArrayList<Map<String, String>>();
String time = "";
String category = "";
String expense = "";
String day = "";
String month = "";
totalExpense = 0;
Cursor c = SQLLiteAdapter.fetchAllItems();
while(c.moveToNext() != false){
if(isCancelled()){
Log.e("cancel","cancel inside background");
break;
}
// if there is nothing is input, don't execute verifyLevel
if(tokenizedResult.isEmpty()) break;
category = c.getString(c.getColumnIndex(SQLLiteAdapter.col_category));
expense = c.getString(c.getColumnIndex(SQLLiteAdapter.col_price));
time = c.getString(c.getColumnIndex(SQLLiteAdapter.col_time));
day = c.getString(c.getColumnIndex(SQLLiteAdapter.col_day));
month = c.getString(c.getColumnIndex(SQLLiteAdapter.col_month));
VerifyLevel a = new VerifyLevel(tokenizedResult,category,expense,time,day,month);
if(!a.isEmpty()){
list.add(a.addToList());
}
totalExpense += a.totalExpense;
}
return null;
}
#Override
protected void onPostExecute(Void result)
{
progressBar.setVisibility(View.GONE);
getListView().setVisibility(View.VISIBLE);
textView.setVisibility(View.VISIBLE);
fillData(list);
textView.setText("Total Expense is "+convertNumeric(totalExpense));
Log.i("yearExpense","buildList is finished");
}
#Override
protected void onCancelled(){
super.onCancelled();
list.clear();
progressBar.setVisibility(View.GONE);
totalExpense = 0;
Log.i("yearEx","Cancel asyncTask");
Toast.makeText(getApplicationContext(), "cancelled", Toast.LENGTH_SHORT).show();
}
}
My approach is slightly different and perhaps, a little lengthy. But it has always worked without any issues.
This piece of code goes in the doInBackground(). If you have a for... loop in it, then use the break; statement. If you do not use a for.... loop, remove it.
// CHECK IF THE USER HAS CANCELLED THE TASK
if (isCancelled()) {
cancelTask = true; // (OPTIONAL) THIS IS AN ADDITIONAL CHECK I HAVE USED. THINK OF IT AS A FAIL SAFE.
break; // REMOVE IF NOT USED IN A FOR LOOP
}
You already have an Asynctask declared: MyAsyncTask asyncTask = new MyAsyncTask();. Along with that, also create two instances of boolean. Call them, for example:
boolean blMyAsyncTask;
boolean cancelTask;
Now, in the onPreExecute(), toggle the status of the blMyAsyncTask instance:
blMyAsyncTask = true;
And in the onPostExecute():
blMyAsyncTask = false;
And, in the same onPostExecute(), I also do the remainder functions after checking the state of the cancelTask boolean. For example:
if (cancelTask == false) {
// THE NORMAL CODE YOU HAVE IN YOUR onPostExecute()
}
Finally, in the onDestroy() (I use this, but I suspect the onPause() could work too. Never done it in the onPause() in all honesty), check the status of the boolean blMyAsyncTask
if (blMyAsyncTask== true) {
asyncTask.cancel(true);
}
As I said at the start, it is lengthy, perhaps even complicated, but it has never failed. I also think of this as a little modular if you would. If I have more Asycntasks added to the Activity, I can add another check in the onDestroy().
you should not call asyncTask.onCancelled(); directly. You can call cancel(true) and inside your doInBackground() you check for isCancelled().
To cancel a AsyncTask call the below line.
asyncTask.cancel();
onCancelled() is a override method that is executed when ever cancel is called.
You can use either
asynctask.cancel(true);
or
asyncTask.wait();
"true " if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete
I have some problem with Android AsyncTask. There is an Activity which contains some TextView a button and a picture. When an user entered this activity I start an asynctask to check whether the user can go toward from the activity (until the task not finish the button not active). Then I want to start another asyntask to get the picture.
So I made an inner class:
AsyncTask<String, Void, JSONObject>() authTask = new AsyncTask<String, Void, JSONObject>() {
#Override
protected JSONObject doInBackground(String... params) {
//call the rest api
}
#Override
protected void onPostExecute(JSONObject result) {
// check the result
// and make another asynctask
AsyncTask<String, Void, Bitmap> imageTask = new Async.... {
// get image
}
imageTask.execute();
}
}
and I call
authTask.execute(); from the UI thread.
I have a bad feeling about this, especially it seems doesn't work (it's ok few times but suddenly it "freeze": no exception just hanging and the progress bar is spinning. Nothing happens and the button won't be active.)
There is another way to get an information and when it's finished immediately start another task?
UDPATE:
I working with api level 10. In authTask I get some information which is needed to start imageTask (some id) so I have to call these tasks in a row. In api level 10 it's is possible?
Thanks in advance!
Br, Peter
you can use getStatus() checks whether the the AsyncTask is pending, running, or finished.and when finsh start your new task.like:
if(authTask .getStatus() == AsyncTask.Status.PENDING){
// My AsyncTask has not started yet
}
if(authTask .getStatus() == AsyncTask.Status.RUNNING){
// My AsyncTask is currently doing work in doInBackground()
}
if(authTask .getStatus() == AsyncTask.Status.FINISHED){
// START NEW TASK HERE
}
example for your app:
btn.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
if (authTask != null && authTask.getStatus() == AsyncTask.Status.FINISHED) {
//START YOUR NEW TASK HERE
}
else
{
//IGNORE BUTTON CLICK
}
}
});
1:
You could write the code for authTask and then for imageTask, one after the other, within a single doInBackground(). This single AsyncTask instance would be fire by a single execute() statement. This may or may not be practical depending on needed UI interactions.
2:
Edit: as noted by kabuku this information is mostly for HoneyComb+. Pre HoneyComb I would definitely go with option 1 above. executeOnExecutor() is api level 11+
In receent versions, execute() will send your AsyncTasks in series by default (ICS+). If you want to make sure this happens, specify the serial executor.
In your case this would be:
authTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
// Image task will only be done AFTER textViewTask is done
imageTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
And for newer versions a simple
...
// ICS+ and pre honeycomb (I think)
authTask.execute();
// Image task will only be done AFTER textViewTask is done
imageTask.execute();
...
From the AsycnTask.execute() documentation:
Note: this function schedules the task on a queue for a single
background thread or pool of threads depending on the platform
version. When first introduced, AsyncTasks were executed serially on a
single background thread. Starting with DONUT, this was changed to a
pool of threads allowing multiple tasks to operate in parallel. After
HONEYCOMB, it is planned to change this back to a single thread to
avoid common application errors caused by parallel execution.
PS:
To run tasks independent of each other you must use the AsyncTask.THREAD_POOL_EXECUTOR. That requires a different executor:
// Go parallel! (NOT what you want)
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Its not a good design to nest AsyncTask. Do all the heavy lifting in doInBackground and simply post/update the results. In other words, combine the processing of second AsyncTask in your first one.
From the code that you showed it does not seem to make sense to spawn second task. Just get you image inside doInBackground of the first task right after authorization.
If you need to update UI in between, you can do it in progress update.
int count;
private void attemptConnect()
{
count = 0;
str_lang = "English";
str_wait = "Plaese Wait";
new AllQuestion().execute();
}
private class AllQuestion extends AsyncTask<String, String, String> {
ProgressDialog pg;
#Override
protected void onPreExecute() {
super.onPreExecute();
pg = new ProgressDialog(LanguageActivity.this);
pg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
pg.setMessage(str_wait);
pg.setCancelable(false);
pg.show();
}
#Override
protected String doInBackground(String... strings) {
try {
SoapObject soapObject = new SoapObject(AppConstant.NAMESPACE, AppConstant.QUESTION_SOAP_METHOD);
soapObject.addProperty("language", str_lang);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(soapObject);
HttpTransportSE se = new HttpTransportSE(AppConstant.webUrl);
se.call(AppConstant.QUESTION_SOAP_ACTION, envelope);
Object responce = envelope.getResponse();
Log.d("Question List:=>>", "" + responce);
return responce.toString();
} catch (Exception e) {
e.printStackTrace();
pg.dismiss();
return null;
}
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
if (pg.isShowing()) {
pg.dismiss();
Log.i(TAG, s);
if (s != null || !s.equalsIgnoreCase("")) {
try {
JSONArray array = new JSONArray(s);
for (int i = 0; i < array.length(); i++) {
JSONObject obj = array.getJSONObject(i);
String queId = obj.getString(TAG_QID);
String que = obj.getString(TAG_QUE);
String str_Opt = obj.getString(TAG_OPT);
question = new Question(queId, que, str_lang, str_catId, str_Opt, manager.getDateTime());
helper.insertQuestion(question);
}
count++;
if (count < 5) {
if (count == 1) {
str_lang = "German";
str_wait = "bitte warte einen Moment";
new AllQuestion().execute();
}
if (count == 2) {
str_lang = "Italian";
str_wait = "per favore aspetta un momento";
new AllQuestion().execute();
}
if (count == 3) {
str_lang = "Chinese";
str_wait = "请稍候";
new AllQuestion().execute();
}
if (count == 4) {
str_lang = "French";
str_wait = "patientez s'il-vous-plait";
new AllQuestion().execute();
}
Log.d("All Question:-", question.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
}
I have an idea to make async series in just one async task:
protected Boolean doInBackground(String... params) {
if(params[0] == "taskA") {
//do somthing
params[0] = "taskB";
}
if(params[0] == "taskB") {
//do somthing
params[0] = "taskC";
}
if(params[0] == "taskC") {
//do somthing
params[0] = "taskD";
}
if(params[0] == "taskD") {
//do somthing
return true;
}
And in your main thread just call async task like this:
ShowMyProgress(); //if you like
new MyAsyncTask().execute("taskA");
And finally you can hide your progress on onPostExecute like:
protected void onPostExecute(final Boolean success) {
if (success) {
....
HideMyProgress();
}
}
I have solved this kind of problem when i had to download something from a database before login in the user into the app, with this i fixed this problem.
To use ObservableInteger you can do this
first declare it
private ObservableInteger mObsInt;
then in your onCreate you will have a listener waiting for the values of the mObsInt to change, after those values change you can do anything you want
//Listener
mObsInt = new ObservableInteger();
mObsInt.set(0);
mObsInt.setOnIntegerChangeListener(new OnIntegerChangeListener()
{
#Override
public void onIntegerChanged(int newValue)
{
if (mObsInt.get()==1)
//Do something if the first asyncTask finishes
if (mObsInt.get()==2){
//Do something if the second asyncTask finishes, in this case i just go to another activity when both asyncTasks finish
Intent mainIntent = new Intent().setClass(LoginActivity.this, Principal.class);
startActivity(mainIntent);
finish();
}
}
});
So, how it works
ObservableInteger will be looking for changes in the variable mObsInt, so lets say if mObsInt is equal to 1 it will do something, if is equal to 2 will do another thing, so, to solve this problem with 2 asynctasks is easy, when one of the asynctasks finishes mObsInt will be equal to 1 , if the other asyncTask finishes so mObsInt will be mObsInt++ , and then your mObsInt will be equal to 2, the listener will be waiting for the values, and then do what you want to do when the values match your if statment at the onCreate method
now, just in your asynctasks just put in your onPostExecute() method this line
mObsInt.set(mObsInt.get()+1);
so if the first async finish, mObsint == 1 , if the second finish mObsInt == 2, and then you handle what you want to do in your onCreate method
hope this helps for you, it helped me
You can get more info at this doc : https://developer.android.com/reference/android/databinding/ObservableInt.html
happy coding !
I have an acitivty which looks for user location (MyLocation class), then with the geopoint or without it it runs an AsyncTask to connect to server and get list of cities from my server. When the list is ready it saves them in ArrayList cities. Once the cities ArrayList is filled I would like it to be saved for good (proof to configuration changes). CityItem implements Parcelable. I save them in onSaveInstanceState and retrieve them onCreate.
Now, everything works fine if the task has completed and the cities list is filled. Then I rotate my device back and forth and Log.i("StartActivity", "Cities list downloaded:"+cities.toString()); gets called.
But if I rotate the device before the geopoint was found (or the task finished - hard to tell because it happens fast), then
public void gotCities(ArrayList<CityItem> _cities){
cities = _cities;
Log.i("StartActivity", "gotCities("+cities.size()+"): "+cities.toString());
}
gets called (and cities are perfectly fine in the log) but when I rotate it once more ArrayList cities appears to be null again.
It appears that if the configuration changed and the savedInstanceState.cities was null, the ArrayList cities is somehow created again and it's not the same ArrayList as the one in gotCities() function.
I'm pretty sure it's something easy but I've been searching for answer for hours and I simply can't do it.
Code of the Activity:
public class StartActivity extends Activity {
public static final String PREFS_NAME = "PrefsFile";
MyLocation myLocationObject = null;
LatLngPoint point = null;
ArrayList<CityItem> cities = null;
FindCityTask task = null;
Activity startActivity;
#Override
public void onCreate(Bundle savedInstanceState) {
if(savedInstanceState!=null) if(savedInstanceState.containsKey("cities")) cities = savedInstanceState.getParcelableArrayList("cities"); if(cities!=null) Log.i("Cities retrieved", cities.toString());
super.onCreate(savedInstanceState);
startActivity = this;
setContentView(R.layout.start);
//check if the configuration (orientation) has been changed
NonConfigurationObject nco = (NonConfigurationObject)getLastNonConfigurationInstance();
if(nco!=null) if(nco.myLocationObject!=null) myLocationObject = nco.myLocationObject;
if(nco!=null) if(nco.task!=null) task = nco.task;
if(cities==null){
Log.i("StartActivity", "Cities list is empty - retrieve them.");
if(myLocationObject==null){
getGeopoint();
}
} else {
Log.i("StartActivity", "Cities list downloaded:"+cities.toString());
}
}
private void getGeopoint(){
if(isOnline()){ //there is internet connection
if(myLocationObject==null){
myLocationObject = new MyLocation();
//calls function to check user location (returns false if no providers are enabled
if(!myLocationObject.getLocation(this, locationResult)){ /*TODO handle */Log.i("StartActivity", "Location providers disabled");}
}
} else { //not online - show msg
Log.i("StartActivity", "No internet connection");
}
}
//waits for user geopoint. then starts FindCityTask
LocationResult locationResult = new LocationResult(){
#Override
public void gotLocation(final Location location){
if(location!=null){
// location found
Log.i("StartActivity", "Received location: "+location.toString());
point = new LatLngPoint((float)location.getLatitude(), (float)location.getLongitude());
} else {
// location not found
Log.i("StartActivity", "No location received after 20 seconds");
point = null;
}
//RUN TASK to connect to server to get cities list (even if there's no geopoint)
task = new FindCityTask(startActivity);
task.execute(point);
}
};
public void gotCities(ArrayList<CityItem> _cities){
cities = _cities;
Log.i("StartActivity", "gotCities("+cities.size()+"): "+cities.toString());
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Log.i("onSaveInstanceState", "onSaveInstanceState");
if(cities!=null) savedInstanceState.putParcelableArrayList("cities", cities);
}
#Override
public NonConfigurationObject onRetainNonConfigurationInstance() {
NonConfigurationObject nco = new NonConfigurationObject();
if(myLocationObject!=null){
nco.myLocationObject = myLocationObject;
}
if(task!=null){
nco.task = task;
}
return nco;
}
static class NonConfigurationObject{
MyLocation myLocationObject;
FindCityTask task;
}
gotCities() method is called from AsyncTask onPostExecute:
#Override
protected void onPostExecute(Void result) {
if(this.activity!=null){
((StartActivity) activity).gotCities(cities);
}
}
When orientation cnange hits, your activity is stopped, destroyed and created anew, and the only callback which is guaranted to be called is onPause(). The same happens when screen lock kick in - it forces your activity in portrait mode.
I recommend you to read about android activity lifecycle. Rule of thumb is:
onCreate() is for initalizing interface objects and services
onResume() is called when your activity comes on top and is about to be presented to user
onPause() when it loses focus and is not presented to use anymore.
Since obtaining location takes long time, it is better to move it away from activity to background service, ( start it in onCreate() if necessary ) and decouple its lifecycle from activity. Service can pass results to activity via broadcast messages, or java method calls
And look into your onSaveInstanceState() - first you are calling super.onSaveInstanceState(), and then you modify bundle to include your data. This way they are never saved.
I finally got it. It's the AsyncTask which pointed to wrong (the one before configuration change) activity. The trick is to attach to the task the reference to activity (and to do it in the right moment).
So the first thing to do is to put these functions in the AsyncTask:
void attach(Activity activity){
this.activity = activity;
}
void detach(){
this.activity = null;
}
When the task is first called we should attach the activity to it.
Then onRetainNonConfigurationInstance() detach it.
#Override
public NonConfigurationObject onRetainNonConfigurationInstance() {
//normally it would return only the task, but i have to return another object
//hence the NonConfigurationObject which holds reference to both the AsyncTask and MyLocation
//(as seen in the original question)
NonConfigurationObject nco = new NonConfigurationObject();
if(task!=null){
task.detach();
nco.task = task;
}
return nco;
}
And finally attach it again when we call getLastNonConfigurationInstance() in onCreate().
//check if the configuration (orientation) has been changed
NonConfigurationObject nco = (NonConfigurationObject)getLastNonConfigurationInstance();
if(nco!=null){ //not created for the first time
Log.i("StartActivity", "NCO: "+nco.toString());
task = nco.task;
if(task!=null){ //nco can be present but task still null
task.attach(this);
} else {
task = new FindCityTask(this);
}
} else {
Log.i("StartActivity", "NCO: null");
task = new FindCityTask(this);
}
Please share your thoughts on this solution if you have any. I'll modify the question so it better suits the problem.