Unable to create a new Loader after long periods of inactivity - java

I have an Android application whose Fragments rely on loaders to fetch data. Below is the skeleton code of my Fragment. Everything is the same except I have some custom code in the onLoadFinished method.
public class Events extends Fragment implements LoaderCallbacks<ArrayList<Event>> {
private Integer intWeek;
public static Events newInstance(Integer intWeek) {
Events pageFragment = new Events();
pageFragment.intWeek = intWeek;
pageFragment.setArguments(new Bundle());
return pageFragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.events, null);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(this.intWeek, savedInstanceState, this);
}
public Loader<ArrayList<Event>> onCreateLoader(int intLoader, Bundle bndBundle) {
return new Scraper(getActivity().getApplicationContext());
}
public void onLoadFinished(Loader<ArrayList<Event>> ldrEvents, final ArrayList<Event> lstEvents) {
//Do something with the returned data
}
public void onLoaderReset(Loader<ArrayList<Event>> ldrEvents) {
return;
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
}
This Fragment is used inside a FragmentActivity which uses ViewPager which gets its Fragment´s using aFragmentPagerAdapter`.
This works fine and I'm able to page between the Fragments like the one shown above. Every time a new Fragment is added to the ViewPager, the onCreate method fires and it creates a new Loader.
When I press the "Home" button on my phone, the application pauses and goes into the background. I can always restore the application and it works just fine — after 5/10/20 minutes of being in the background.
...but If I leave the application in the background for a long time, an hour or more, the application crashes upon start and the stacktrace points to the following line in the onCreate method:
getLoaderManager().initLoader(this.intWeek, savedInstanceState, this);
Now I'm very lost as to why this is happening. It seems that the Android framework destroys something in the background after long periods of inactivity in the background and the initLoader method isn't able to create a Loader. The documentation about the initLoader method specifically says:
Ensures a loader is initialized and active. If the loader doesn't
already exist, one is created and (if the activity/fragment is
currently started) starts the loader. Otherwise the last created
loader is re-used.
Would anyone be able to point out what I'm doing wrong here? This seems to be a pretty difficult issue to debug because I can't replicate it at will. It is very random. Thanks
Stacktrace from logcat:
Transmitting stack trace: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mridang.stadi/com.mridang.stadi.Main}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2079)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2104)
at android.app.ActivityThread.access$600(ActivityThread.java:132)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1157)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4575)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:556)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.mridang.stadi.events.Events.onCreate(Events.java:73)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:834)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1080)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1062)
at android.support.v4.app.FragmentManagerImpl.dispatchCreate(FragmentManager.java:1805)
at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:200)
at com.mridang.stadi.Main.onCreate(Main.java:23)
at android.app.Activity.performCreate(Activity.java:4465)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2033)
... 11 more

I ran into a similar problem with the loaders. What I found is that the Fragment/Loader lifecycle is complicated. I'd love to find a document that explains it in detail.
Anyhow, what I did to fix the problem was move the initLoader() call to the onActivityCreated() method. Give that a try.

Try using something like this:
boolean canFire = ((loader != null) && !loader.isReset());
// restart or initialise loader
if (canFire) {
getLoaderManager().restartLoader(loaderId, args, this);
} else {
getLoaderManager().initLoader(loaderId, args, this);
}

Related

Android Studio App Stops Working + Logcat

06-06 23:50:28.340 27670-27706/mys.timer E/AndroidRuntime: FATAL EXCEPTION: Timer-0
Process: mys.timer, PID: 27660
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
My code:
protected void onCreate(Bundle savedInstanceState) {
public void run() {
new Timer().scheduleAtFixedRate(new TimerTask(){
#Override
public void run(){
status.setText(getResources().getString(R.string.timercount));
status.setTextColor(Color.parseColor("#990000"));
}
}
}
Please tell me how can i avoid my app from closing itself
Android’s UI components are not thread safe so one may need to update Views or other UI components from a secondary thread when returning from an asynchronous database query or a web service call. If you run the code from a secondary thread you might see that the code crashes almost on each try.
runOnUIThread Activity’s method
yourActivity.runOnUiThread(new Runnable(){
public void run() {
// UI code goes here
}
});
I hope that it helps you.

Unable to restore nested fragment state

I'm having a problem restoring the state of nested fragments. Here's the hierarchy:
MainActivity
----> AlarmOverviewFragment (doesn't really hold any important data)
--------> AlarmFragment (holds data)
The problem I am running into is when I minimize the app (press home button) and then reopen the app, it crashes every time when trying to get data from an array stored as one of Fragment's instance variables inside of onCreateView().
The frustrating bit is that I'm saving the instance variables in onSaveInstanceState(), but savedInstanceState is always null in onCreateView(), onCreate(), and onActivityCreated().
Clearly I am missing something huge here because I've tried many solutions found on stackoverflow to no avail. Here's some relevant code bits:
Saving instance data:
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("hour", this.hour);
outState.putInt("window", this.window);
outState.putInt("minutes", this.minutes);
outState.putBooleanArray("days", this.days);
super.onSaveInstanceState(outState);
}
Unsuccessfully trying to restore data
#Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
/* Never happens */
hour = savedInstanceState.getInt("hour");
window = savedInstanceState.getInt("window");
minutes = savedInstanceState.getInt("minutes");
days = savedInstanceState.getBooleanArray("days");
}
super.onCreate(savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
if (savedInstanceState != null) {
/* Also never happens */
}
super.onActivityCreated(savedInstanceState);
}
And just in case this is helpful, here's the stack trace:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: group12.wakemeup, PID: 22756
java.lang.RuntimeException: Unable to resume activity {group12.wakemeup/group12.wakemeup.MainActivity}: java.lang.NullPointerException: Attempt to read from null array
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3121)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3152)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1400)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5525)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:730)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:620)
Caused by: java.lang.NullPointerException: Attempt to read from null array
at group12.wakemeup.AlarmFragment.onCreateView(AlarmFragment.java:113)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2087)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1113)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1295)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:801)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1682)
at android.support.v4.app.FragmentController.execPendingActions(FragmentController.java:388)
at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:607)
at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:181)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1237)
at android.app.Activity.performStart(Activity.java:6288)
at android.app.Activity.performRestart(Activity.java:6334)
at android.app.Activity.performResume(Activity.java:6339)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3110)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3152) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1400) 
at android.os.Handler.dispatchMessage(Handler.java:102) 
at android.os.Looper.loop(Looper.java:148) 
at android.app.ActivityThread.main(ActivityThread.java:5525) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:730) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:620) 
EDIT
Extra information
Each AlarmFragment is added dynamically and given a unique ID via View.generateViewId(), so I've been unsure of how to save each fragment in MainActivity. Also worth mentioning that I do use a FragmentPagerAdapter to handle tabs/swiping (there are other Overview fragments in this app, but I left them out as they aren't causing me problems). I've heard there is a way to use a ViewPager to save state, perhaps there is one with a FragmentPagerAdapter as well?
Since your problem seems different from the start, actually you don't need to re-create your fragments inside onNewIntent every time.
You can do this instead:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if ("some_action".equals(intent.getAction())) {
//your action, in your case create and assign fragment
//remove the action
intent.setAction(null);
}
}
Try to save your fragments in your activity
private YourFragment yourFragment;
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the fragment instances
getFragmentManager().putFragment(outState, "any string you want as key", yourFragment);
//or use code below if you use android.support.v4.app.Fragment
//getSupportFragmentManager().putFragment(outState, "any string you want as key", yourFragment);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
//retrieve your fragment that saved from onSaveInstanceState()
yourFragment = (YourFragment) getFragmentManager().getFragment(savedInstanceState, "any string you want as key");
//or
//yourFragment = (YourFragment) getSupportFragmentManager().getFragment(savedInstanceState, "any string you want as key");
}
else {
//create your fragment if it is first time
yourFragment = new YourFragment();
}
}
Well I guess my problem came from something completely different. MainActivity was creating a new AlarmFragment using onNewIntent() when it returned from a series of activities to define the values for each AlarmFragment. Evidently, minimizing the app and then reopening it calls onNewIntent() every time, so my solution was to make sure that I was actually creating new AlarmFragments with data. Stupid problem, stupid solution

Returning a thread result to the Fragment using interface implementation

I have an application that shows News articles in a listview inside a fragment.
When the fragment is first created, I start a thread that will fetch the list of articles (Stories) through an API call
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mContext = getActivity();
new GetStoriesThread(mContext,this).start();
Both the Fragment and the Thread implement the same Interface for passing the data from the thread to the Fragment
public interface GetStoriesThreadInterface {
public void onGetStoriesThreadResult(final ArrayList<Story> result);
}
After the Thread is done processing, it will call the interface method and pass the data back to the calling Fragment.
The problem
Now when I get the result in the fragment, through this code:
#Override
public void onGetStoriesThreadResult(final ArrayList<Story> result)
{
if(result!=null)
{
mStoriesList.clear(); //mStoriesList is the list that i supply to the adapter of the ListView
mStoriesList.addAll(result);
adapter.notifyDataSetChanged(); //Exception here
}
}
I get the following exception:
04-28 18:03:58.432: E/ViewRootImpl(21513): com.says.news.Stories : Only the original thread that created a view hierarchy can touch its views.
I know that using getActivity().runOnUiThread(new Runnable... solves the issue, but I dont understand why. And sometimes getActivity() returns null and that`s a whole different issue.
Thanks in advance !!
Are you calling onGetStoriesThreadResult() from within the working thread? You shouldn't. Consider using AsyncTask<> instead of bare Thread , override the onPostExecute() method and call your events from there.

onCreate being called again when navigating away from Activity

Code works great. However, I am seeing something strange from an Activity Lifecycle perspective. The onCreate code basically sets the adapters to the relevant GridView and pulls items from a database and populates said GridView. Each item can be tapped by the user that will push the item's "playerId" to HubActivity.
The below is my onCreate code:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Const.err("##> Running PlayerSearchActivity.onCreate");
setContentView(R.layout.activity_playersearch);
this.mPlayerSearchBtn=(Button) findViewById(R.id.playerSearchButton);
this.mPlayerSearchText=(TextView) findViewById(R.id.playerSearchText);
this.mPlayerSearchGridView=(GridView) findViewById(R.id.psearch_playersearchgrid);
this.mSavedPlayersGridView=(GridView) findViewById(R.id.psearch_savedplayersgrid);
mPlayerSearchBtn.setOnClickListener(this);
this.savedPlayersAdapter = new PlayerGridAdapter(this);
mSavedPlayersGridView.setAdapter(savedPlayersAdapter);
mSavedPlayersGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position,long arg3) {
Intent intent = new Intent(getApplicationContext(), HubActivity.class);
intent.putExtra(Keys._PLAYERID,savedPlayersAdapter.getItemPlayerId(position));
startActivity(intent);
}
});
Const.err("<## Completed PlayerSearchActivity.onCreate");
}
I was curious about how each method was being called so I applied some logging around the functions (Const.err). The results are strange:
03-04 00:29:50.942: ##> Running PlayerSearchActivity.onCreate
03-04 00:29:50.982: <## Completed PlayerSearchActivity.onCreate
03-04 00:29:50.982: ##> Running PlayerSearchActivity.onResume
03-04 00:29:51.058: <## Completed PlayerSearchActivity.onResume
---Clicked item---
03-04 00:29:54.718: ##> Running PlayerSearchActivity.onCreate
03-04 00:29:54.730: <## Completed HubActivity.onCreate
..
Firstly, why is onCreate being called after I click an item? Also, I expected to see a "Completed PlayerSearchActivity.onCreate" message but this never occurrs.
I guess this is a copy-paste error, check your HubActivity.onCreate(), if you didn't paste by accident
Const.err("##> Running PlayerSearchActivity.onCreate");
instead of
Const.err("##> Running HubActivity.onCreate");

Custom view isn't being restored after adding it to a layout from AsyncTask's onPostExecute()

I've written a pretty large custom view which overrides onSaveInstanceState() and onRestoreInstanceState(Parcelable state).
I wanted to populate a LinearLayout with my custom view, so I wrote the following code:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
// Test: adding 10 instances of MyCustomView.
for (i = 0; i < 10; ++i) {
MyCustomView cv = new MyCustomView(this);
// I set an ID for this view so that onSaveInstanceState() and
// onRestoreInstanceState(Parcelable state) will be called
// automatically.
cv.setId(++i);
mRootLayout.addView(cv);
}
}
// ...
}
It works fine - mRootLayout is indeed being populated with 10 instances of MyCustomView, and each instance of MyCustomView is being properly restored after, for example, screen rotation.
I've noticed that due to the fact that MyCustomView is pretty large, my code is being heavy on the UI thread.
To solve the issue and take some effort off of the UI thread, I decided to use a custom AsyncTask, which will create an instance of MyCustomView in doInBackground() and add it to the the main layout ( mRootLayout ) in onPostExecute().
The following code is my custom AsyncTask:
private class LoadMyCustomViewTask extends AsyncTask<Void, Void, MyCustomView> {
private Context mContext;
private LinearLayout mLayoutToPopulate;
private int mId;
public LoadMyCustomViewTask(Context context, LinearLayout layout, int id) {
mContext = context;
mLayoutToPopulate = layout;
mId = id;
}
#Override
protected MyCustomView doInBackground(Void... params) {
MyCustomView cv = new MyCustomView(mContext);
return cv;
}
#Override
protected void onPostExecute(MyCustomView result) {
result.setId(mId);
mLayoutToPopulate.addView(result);
}
}
In MainActivity I use it as follows:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
for (i = 0; i < 10; ++i) {
new LoadMyCustomViewTask(this, mRootLayout, ++i).execute();
}
}
// ...
}
This code works too, but there is only one problem - MyCustomView is not being restored at all.
For debug purposes I put a Log.d(...) in MyCustomView's onSaveInstanceState() and in onRestoreInstanceState(Parcelable state), and I've noticed that onRestoreInstanceState(Parcelable state) isn't being called.
Do you have any idea why onRestoreInstanceState(Parcelable state) isn't being called when I use an AsyncTask to populate mRootLayout, but it is indeed being called when I create MyCustomView completely on the UI thread?
Thank you.
Edit: I'm posting the methods onSaveInstanceState() and onRestoreInstanceState() of MyCustomView
#Override
protected Parcelable onSaveInstanceState() {
debug("onSaveInstanceState()");
Bundle state = new Bundle();
state.putParcelable(_BUNDLE_KEY_PARENT_STATE, super.onSaveInstanceState());
state.putBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS, mClickedViews);
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
debug("onRestoreInstanceState(Parcelable state)");
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mClickedViews = bundle.getBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS);
state = bundle.getParcelable(_BUNDLE_KEY_PARENT_STATE);
}
super.onRestoreInstanceState(state);
}
View state restoration begins at the root view and moves down to all of the child views attached at that time. This can be seen in the ViewGroup.dispatchRestoreInstanceState method. This means that Android can only restore your views if they are part of the view hierarchy at the time Activity.onRestoreInstanceState is called.
Using the AsyncTask, you are creating your views asynchronously and then scheduling them to be added some time later when the main looper is idle. Considering the lifecycle, Android only lets your AsyncTask.onPostExecute run after Activity.onStart, Activity.onRestoreInstanceState, Activity.onResume, etc. are called. Your views are being added to the layout too late for automatic restoration to take place.
If you add log statements to those methods mentioned above, as well as to your AsyncTask.onPostExecute, you will be able to see how the ordering/timing plays out in reality. The following code runs after Activity.onRestoreInstanceState even though it all happens on the main thread, simply because of the scheduling:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new Handler().post(new Runnable() {
#Override
public void run() {
Log.i("TAG", "when does this run?");
}
});
...
}
Smells like a false observation... creating a view on a background thread should not affect your activity lifecycle.
That said, doing anything at all with View objects on background threads is a no-no and I'm surprised you got this far with such an approach. All View code should be quick and avoid blocking. If you have long-running work to do then separate that work into the background thread, post the results of that complex computation to the main thread, and keep all the actual View/presentation stuff on the main thread where it belongs.
I remember having read about how onSaveInstanceState()/onRestoreInstanceState() work a time ago and it was not a "fixed-situation-rules" thing. As I can't find the references to it at this moment, I'll try to explain it with my own words:
Basically, a factor on which depends the calling of those methods is the resources left that the device has. Both methods will get in action when a second Activity gets focused and the first one gets killed due to lack of resources. Concretely, onRestoreInstanceState should be triggered when that Activity was killed and restarted, so it gets the previous state.
Although you've not posted your MyCustomView implementation, my guess is that when you do that entirely on the main UI Thread, you're involving some action that makes the MainActivity lose its focus and once the MyCustomView is created, it needs to restore its state. Doing this in a separate thread (as AsyncTask does) makes creating those Views in paralell, so your main Activity doesn't lose its focus and thus it doesn't get killed, so those methods are not called.
Concluding this, don't worry if those methods are not always called as they don't have to be called everytime, just when needed, and that doesn't mean there's something going wrong.
I recommend you to connect SDK sources to your IDE and walk there with debugger through the whole process related with the View class onRestoreInstanceState(). Since I don't have your code, looking at sources I can only guess what might have gone wrong, but from what I see, that might be related to problem:
1) try to set an Id to every view you generate
2) try to use Fragment as a host to your views (instead of MainActivity).

Categories