I grabbed the EU4You sample project demonstrated in The Busy Coder’s Guide to Android Development 4.2 (courtesy of Mark Murphy aka commonsware here on StackOverflow). It may be easier to follow along with this sample project, but I will try to post relevant portions of code in the question. You do need to import the ActionBarSherlock project as well. You’ll aslo need to set the Java compiler to 1.6 (to allow for the @Override annotation).
NOTE: I’ve changed the min-sdk to 8 (2.3, Froyo) and the target-sdk to 16 (4.1, Ice Cream Sandwich).
Note: I’ve changed the countries framelayout id to left_pane and the details framelayout id to right_pane.
Main layout file (layout-large-land):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:id="@+id/left_pane"
android:layout_weight="30"
android:layout_width="0px"
android:layout_height="fill_parent"
/>
<FrameLayout
android:id="@+id/right_pane"
android:layout_weight="70"
android:layout_width="0px"
android:layout_height="fill_parent"
/>
</LinearLayout>
Main layout file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/left_pane"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
The original version of the sample application has two activities (SherlockFragmentActivities to be exact).
- EU4You – Loads up the appropriate layout (default or landscape-large). If default, loads a list of countries in a SherlockListFragment. If landscape-large, loads up the list and a details fragment to display the selected country.
- DetailsActivity – Loads up the selected country via the DetailsFragment when not using the landscape-large layout.
If you click on a country in the list fragment, EU4You (the main hosting activity) checks for the existence and visibility of the DetailsFragment. If it exists, it loads up the country website inside that fragment. If not, it fires off the DetailsActivity, which then handles adding the DetailsFragment (which then loads the website for the given country).
This works fine, like a typical application supporting multiple screen layouts on tablets and handsets.
My problem is that I want to remove the DetailsActivity and stick to a single activity. I want to swap out (replace) the list fragment with the details fragment whenever I am not in the large landscape layout (in other words, I am either in a portrait layout on a tablet, or on a handset in any orientation). So what I have done, is changed the following in the EU4You SherlockFragmentActivity:
Original (with slight naming modification on the framelayout names mentioned in the note further up… countries is left_pane, details is right_pane):
@Override
public void onCountrySelected(Country c) {
String url=getString(c.url);
if (details != null && details.isVisible()) {
details.loadUrl(url);
}
else {
Intent i=new Intent(this, DetailsActivity.class);
i.putExtra(DetailsActivity.EXTRA_URL, url);
startActivity(i);
}
}
My modified single activity version:
@Override
public void onCountrySelected(Country c) {
String url=getString(c.url);
if (details != null && details.isVisible()) {
details.loadUrl(url);
}
else {
details = new DetailsFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.left_pane, details);
ft.addToBackStack(null);
ft.commit();
getSupportFragmentManager().executePendingTransactions();
details.loadUrl(url);
}
}
This works as expected. I click on a website on the list (in portrait mode on a tablet, or on a handset in any orientation) and the details show in the details fragment, which replaces the website list fragment. However, if I then click the home button on the device, while the country details are showing, I get a force close error, with the following logcat details (only with the default layout with just the single left_pane framelayout… I don’t have this issue in landscape orientation on tablets, when there is a left_pane and right_pane):
11-16 15:04:33.525: E/AndroidRuntime(688): FATAL EXCEPTION: main
11-16 15:04:33.525: E/AndroidRuntime(688): java.lang.RuntimeException: Unable to pause activity {com.commonsware.android.eu4you/com.commonsware.android.eu4you.EU4You}: java.lang.IllegalStateException: Content view not yet created
11-16 15:04:33.525: E/AndroidRuntime(688): at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3348)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3305)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.app.ActivityThread.handlePauseActivity(ActivityThread.java:3288)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.app.ActivityThread.access$2500(ActivityThread.java:125)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2040)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.os.Handler.dispatchMessage(Handler.java:99)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.os.Looper.loop(Looper.java:123)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.app.ActivityThread.main(ActivityThread.java:4627)
11-16 15:04:33.525: E/AndroidRuntime(688): at java.lang.reflect.Method.invokeNative(Native Method)
11-16 15:04:33.525: E/AndroidRuntime(688): at java.lang.reflect.Method.invoke(Method.java:521)
11-16 15:04:33.525: E/AndroidRuntime(688): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
11-16 15:04:33.525: E/AndroidRuntime(688): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
11-16 15:04:33.525: E/AndroidRuntime(688): at dalvik.system.NativeStart.main(Native Method)
11-16 15:04:33.525: E/AndroidRuntime(688): Caused by: java.lang.IllegalStateException: Content view not yet created
11-16 15:04:33.525: E/AndroidRuntime(688): at android.support.v4.app.ListFragment.ensureList(ListFragment.java:328)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.support.v4.app.ListFragment.getListView(ListFragment.java:222)
11-16 15:04:33.525: E/AndroidRuntime(688): at com.commonsware.android.eu4you.CountriesFragment.onSaveInstanceState(CountriesFragment.java:64)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.support.v4.app.FragmentManagerImpl.saveFragmentBasicState(FragmentManager.java:1574)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.support.v4.app.FragmentManagerImpl.saveAllState(FragmentManager.java:1644)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.support.v4.app.FragmentActivity.onSaveInstanceState(FragmentActivity.java:499)
11-16 15:04:33.525: E/AndroidRuntime(688): at com.actionbarsherlock.app.SherlockFragmentActivity.onSaveInstanceState(SherlockFragmentActivity.java:127)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.app.Activity.performSaveInstanceState(Activity.java:1036)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1180)
11-16 15:04:33.525: E/AndroidRuntime(688): at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3330)
11-16 15:04:33.525: E/AndroidRuntime(688): ... 12 more
I can stop this error from occurring, if I remove the following line from the code above:
ft.addToBackStack(null);
I do require to be able to add the replace transaction to the backstack, so I can’t remove this line (this sample application is the basis for my real application that has more stuff, but I used it to simplify my issue).
So two questions:
- Why is this happening?
- How can I fix it (perhaps another approach)?
One thing to note is that in my real application, this logic must all fall inside of tabs in an action bar (ActionBarSherlock since I must support back to version 2.3 (Froyo)).
UPDATE (2012-Nov-22 13:27 EST)
I’ve posted a sample project on GitHub. I’m new to Eclipse and Android development, so I may not have included everything I needed (workspace was not included and I don’t think the ActionBarSherlock project is included… it would need to be imported (can be found here)).
I cannot comment on your specific crash, as I have not seen that error, other than to surmise that it is tied to
replace()rather than usingattach()/detach()(just a guess).However,
getSupportFragmentManager().executePendingTransactions();to me is a code smell. My guess is that you are doing this for the benefit ofdetails.loadUrl(url);. Instead, please add a data member inDetailsFragment, something likeurlToBeApplied, and haveloadUrl()stash the value there if theWebViewdoes not yet exist. It can then apply the URL inonCreateView(). Whether this will help with respect to your crash, I cannot say.You may wish to consider posting a complete sample project that demonstrates the issue, as you are counting on people recognizing the error message.
UPDATE
Based on the sample app, I was able to better diagnose the problem.
Content view not yet created, on aListFragment, will mean that we are trying to callgetListView()for a fragment that has not yet gone throughonCreateView()or has gone throughonDestroyView(). This usually indicates a timing problem.In this case, as it turns out, the work that was crashing was superfluous, anyway.
onSaveInstanceState()is trying to hold onto the checked item position, so we can re-check it later (e.g., after a configuration change). However, we do not even need to be in checked mode in the first place when we are onnormaldisplays. The checked mode is for theactivatedstate, which is relevant when then list and details are side-by-side, but is irrelevant when they are not. So, the workaround is to simply not bother with theonSaveInstanceState()logic if we are not needing a persistent selection… which we can easily get by callingisPersistentSelection()on ourlistener.For the sake of argument, though, let’s pretend that we did need to do this work. In that case, we have a couple of options. The simplest probably is to override
onDestroyView()and cache the checked item position, using that inonSaveInstanceState()ifgetListView()returnsnull.