Fragment with retainInstance = true, but onCreate is called - android

I have a layout containing a fragment :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<fragment
android:id="#+id/ID"
class="com.teovald.app.MyFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<include
android:id="#+id/toolbar"
layout="#layout/toolbar" />
</FrameLayout>
I set this use setRetainInstance(true) in this fragment onCreate method :
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setRetainInstance(true);
....}
And finally I recuperate a reference to this fragment in its activity onCreate as well :
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
FragmentManager fragmentManager = getSupportFragmentManager();
mFragment = (MyFragment) fragmentManager.findFragmentById(R.id.ID);
...
}
However, each time I rotate my device, onCreate of the activity is called, then onCreate of the fragment is called as well ! Since I seted up setRetainInstance to true, it should not happen.
Is there a reason for this behavior ?

I had this issue recently and was fighting with it for a couple of hours until I discovered that in the code (that I copied from some third party library) of the Activity which contains the retained non-ui-fragment in onSaveInstanceState there was no call to super.onSaveInstanceState()
It was like that:
#Override
protected void onSaveInstanceState(Bundle outState) {
// Save the mapview state in a separate bundle parameter
final Bundle mapviewState = new Bundle();
mMapFragment.onSaveInstanceState(mapviewState);
outState.putBundle(BUNDLE_STATE_MAPVIEW, mapviewState);
}
So I added the missing call to be like:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the mapview state in a separate bundle parameter
final Bundle mapviewState = new Bundle();
mMapFragment.onSaveInstanceState(mapviewState);
outState.putBundle(BUNDLE_STATE_MAPVIEW, mapviewState);
}
And now onCreate() is not called twice in the retained fragment.
I hope this will help somebody :)

Related

Why is my onCreateView method being called twice?

While debugging another issue, I realized that the onCreateView method of one of my activities was being called twice. I'm new to programming and I don't fully understand how android calls these methods when the activity loads, but it doesn't seem right to me that it would be called twice. Eliminating most of my code, I still see my System.out message twice.
public class AddCourse extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_course);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new AddCourseFragment()).commit();
}
}
public static class AddCourseFragment extends Fragment {
View rootView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_add_course,
container, false);
System.out.println("I see this TWICE!!!!");
return rootView;
}
}
}
This is almost exactly like my main activity implementation, but that one doesn't go through onCreateView twice. Thoughts?
My activity_add_course xml was requested...
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.NsouthProductions.gradetrackerpro.AddCourse$AddCourseFragment"
android:id="#+id/AddCourseFrag"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Looks like you're adding the fragment twice. If you declare it in the xml then you don't need to add it programmatically as well.
You can remove this from your Activity's onCreate():
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new AddCourseFragment()).commit();
}

FragmentActivity pass bundle to FragmentList specified in xml

Overview of what I need:
Pass String from FragmentActivity to FragmentList. Said FragmentList is specified in the xml as below:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MultiContactsFragmentList" >
...
<fragment
android:id="#+id/multiContactsList_fragment"
android:name="com.sf.lidgit_android.content.MultiContactsFragmentList"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
The problem in the code lies here (by the way, I'm using Support Library because I target API level 10), in my class that extends from FragmentActivity:
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
...
inviteByEmail();
}
public void inviteByEmail(){
setContentView(R.layout.activity_multi_contacts_email_picker);
MultiContactsFragmentList fragmentList =
(MultiContactsFragmentList)
getSupportFragmentManager().findFragmentById(R.id.multiContactsList_fragment);
Bundle extras = new Bundle();
extras.putString(MultiContactsFragmentList.DATA_TO_PICK, MultiContactsFragmentList.EMAIL_DATA);
fragmentList.setArguments(extras);
}
Here is the thing, fragmentList is null. I figure it's because of the life cycle (I'm not aware to the FragmetnActivity-FragmentList life cycle). The FragmentList is yet to be created. Is that it? I know the other way around would be to have my FragmentActivity "save" the data, and have the FragmentList access it (since FragmentActivity is available to it's Fragments, I believe).
EDIT
My FragmentList class (the fragment I'm trying to pass a Bundle to), implements LoaderManager.LoaderCallbacks<Cursor> . I don't know if that influences or not, but I tried the first answer below and got an error on my FragmentList caused by a null pointer Exception, it appears that the onLoadFinished(..) method was called before the onCreate(..), because the null Pointer Exception happened on the onLoadFinished(..), stating the adapter was null.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("invite", "oncreate called");
String[] fromColumns = {ContactsContract.Contacts.DISPLAY_NAME, this.view_ID_Data};
int[] toViews = {R.id.contact_name, R.id.contact_data};
getLoaderManager().initLoader(MULTI_CONTACTS_LIST_LOADER, null, this);
this.adapter = new SimpleCursorAdapter(
getActivity().getApplicationContext(), R.layout.custom_list_contacts_multiple_choice,
null, fromColumns, toViews,
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
setListAdapter(adapter);
}
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
adapter.swapCursor(cursor);
}
Change your layout to this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
And in your activity:
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
...
inviteByEmail();
}
public void inviteByEmail(){
setContentView(R.layout.activity_multi_contacts_email_picker);
MultiContactsFragmentList fragmentList = new MultiContactsFragmentList();
Bundle extras = new Bundle();
extras.putString(MultiContactsFragmentList.DATA_TO_PICK, MultiContactsFragmentList.EMAIL_DATA);
fragmentList.setArguments(extras);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.content, fragmentList);
ft.commit();
}
The method setArguments(Bundle args) for the fragment can only be called just after creating the fragment, so if you embed you fragment in the XML the argumments setting may no be happening at all even if you call it. This way you have to attach the fragment yourself but you will have the arguments when you access it.

Nested Fragments. How to know when all of them are attached to the activity?

My application uses multiple nested fragments. Some of these fragments are placeholders. When I try to replace placeholders with initial set of fragments I get
java.lang.IllegalStateException: Activity has been destroyed
I am sure that activity is fine and the issue is caused by fragments not being attached to the activity. So how do know when all nested fragments are attached to the activity?
Additional information about my setup:
I have an Activity that has fragment declaration in it's layout file:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainLayout"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/rightPane"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.company.PagerFragment"/>
</LinearLayout>
The PagerFragment is the host for view pager.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
I put two Fragments into the pager. They serve as placeholders.
I want to replace placeholders with initial fragments but when I initiate the replacement in activity's onCreate method I get the aforementioned exception.
Everything works fine if I use new Handler().postDelayed(..) in activity's onCreate method. I would rather to not use the delay approach for obvious reasons.
I tried to replace placeholders in other activity's methods like onStart(), onResume(), onResumeFragments(), onPostResume() but that didn't help.
EDIT:
public class PagerFragment extends Fragment
{
..
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_right_pane, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null)
{
m_primary = new PlaceholderFragment();
m_secondary = new PlaceholderFragment();
}
else
{
FragmentManager fm = getChildFragmentManager();
m_primary = (PlaceholderFragment)fm.getFragment(savedInstanceState, FRAGMENT_TAG_PRIMARY);
m_secondary = (PlaceholderFragment)fm.getFragment(savedInstanceState, FRAGMENT_TAG_SECONDARY);
}
Fragment[] fragments = new Fragment[] { m_primary, m_secondary };
PagerAdapter pagerAdapter = new FragmentPagerAdapter(getChildFragmentManager(), fragments);
m_viewPager = (ViewPager)view.findViewById(R.id.pager);
m_viewPager.setAdapter(pagerAdapter);
m_viewPager.setOnPageChangeListener(this);
m_viewPager.setCurrentItem(0, true);
}
..
}

findFragmentById is always null

Trying to find a ListFragment in the xml layout is always returning null. I'm using SherlockFragmentActivity with a SherlockListFragment. Here is the code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_custom_layout);
adapter = new My_Custom_Adapter(this, My_Object, My_Object_2);
}
// Create the list fragment and add it as the list content.
if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
mListFragment list = new mListFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, list).commit();
}
}
public static class mListFragment extends SherlockListFragment {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(adapter);
setListShown(true);
}
}
This is a shortened version of the xml layout:
<RelativeLayout
android:id="#+id/top_control_bar">
<!-- Text Views -->
</RelativeLayout>
<RelativeLayout >
<!-- Button -->
</RelativeLayout>
<LinearLayout
android:id="#+id/start_data"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_below="#id/top_control_bar"
android:orientation="vertical" >
<fragment
android:name="android.support.v4.app.ListFragment"
android:id="#android:id/content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
findFragmentById(android.R.id.content)
always returns null. Why can't it find the fragment within my layout?
The problem that you have is that you declared in the xml the Fragment as a ListFragment.
getSupportFragmentManager().findFragmentById(android.R.id.content) uses Support fragment manager, so you will need a SupportMapFragment
Try this:
<fragment
android:name="com.google.android.gms.maps.SupportMapFragment"
android:id="#android:id/content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
Shouldn't
android:id="#android:id/content"
be
android:id="#+id/content"
?
From the documentation it seems like you are using the wrong syntax.
I'm also not sure your chained methods will work correctly as written. Change
getSupportFragmentManager().beginTransaction().add(android.R.id.content, list).commit();
to
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(android.R.id.content, list);
transaction.commit();
even then, I honestly don't know what you'd be trying to do there. You are aware that FragmentTransaction's add method as you're using it is trying to add a fragment to a container which, in your if, already was established as having a null value?
Try this in mListFragment class override onCreate() and call setRetainInstance(true);
public static class mListFragment extends SherlockListFragment {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(adapter);
setListShown(true);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}

Fragment not re-added after orientation change

The Fragments in my app are not re-added to the activity's view when the device is rotated. If i understand the documentation (and similar questions here on SO) correctly, this should be handled be the fragment manager (without having to specify android:configChanges in my manifest).
My Activity looks like:
public class MainActivity extends SherlockFragmentActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null){
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment_placeholder, new DashboardFragment(),"dashboard")
.commit();
}
}
}
And a Fragment looks like (omitted clicklisteners and so on for clarity):
public class DashboardFragment extends SherlockFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
}
}
The layout files look like (activity_main.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/main.main">
<FrameLayout android:id="#+id/main_fragment_placeholder"
android:layout_height="fill_parent"
android:layout_weight="1"
android:layout_width="0px"
/>
</LinearLayout>
And fragment_layout.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main.wrapper"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<my.layout.DashboardLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/main.dashboard">
<!-- some buttons here -->
</my.layout.DashboardLayout>
</LinearLayout>
However, when i rotate my device the screen always becomes blank.
Turns out I did a Stupid Thing. I overrode the onSaveInstanceState(...) method in my activity, without calling super.onSaveInstanceState(), which resulted in the fragments not being recreated. Posting the answer here in hopes that I can save somebody else the time!
Fragments usually get recreated on configuration change. If you don't wish this to happen, use
setRetainInstance(true); in the Fragment's constructor(s)
This will cause fragments to be retained during configuration change.
http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)

Categories

Resources