3

So, I have a Search Criteria Fragment, where search criteria can be entered, and when the search is performed, the results of the search are shown in a Search Results Fragment. In landscape on a tablet, the two fragments naturally can be displayed side by side. This is straightforward to implement so far.

In portrait orientation, and on a phone, I'd like the Search Criteria fragment to be displayed like a popup dialog instead of beside the search results. In the Selecting Between Dialog or Embedding section of the DialogFragment, I see that I can either embed the fragment, or show it as a dialog. This might work for me if it weren't complicated by orientation changes.

If I am using <fragment> XML tags in the layout for the activity, I get into all sorts of trouble when I make orientation changes.

EDIT: Today I decided to try and see if I could get a simple workflow using a DialogFragment toggling between showing a dialog and embedded depending on the orientation of the device. This turns out to be quite difficult, and I still haven't found something that works. Here's what I have so far.

package org.nsdev.experiments;

import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

public class FragmentSearchTestActivity extends FragmentActivity
{
    private static SearchCriteriaFragment searchCriteriaFragment;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        View container = findViewById(R.id.search_criteria_container);

        android.support.v4.app.FragmentTransaction ft = getSupportFragmentManager().beginTransaction();

        if (searchCriteriaFragment == null)
        {
            searchCriteriaFragment = SearchCriteriaFragment.newInstance();
            searchCriteriaFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
        }

        android.support.v4.app.Fragment f = getSupportFragmentManager().findFragmentByTag("search_criteria");
        if (f != null)
        {
            ft.remove(f);
        }

        if (container == null)
        {
            searchCriteriaFragment.setCancelable(true);
            searchCriteriaFragment.setHasOptionsMenu(false);
            searchCriteriaFragment.show(getSupportFragmentManager(), "search_criteria");
        }
        else
        {
            ft.remove(searchCriteriaFragment);
            ft.add(R.id.search_criteria_container, searchCriteriaFragment, "search_criteria");
            ft.commit();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        if (item.getItemId() == R.id.menu_item_search)
        {
            searchCriteriaFragment.show(getSupportFragmentManager(), "search_criteria");
        }
        return super.onOptionsItemSelected(item);
    }
}

Specifically I was unable to get anything working unless I kept a static copy of the SearchCriteriaFragment and reused it whenever onCreate was called in the Activity. This seems very wrong to me.

SearchResultsFragment.java:

package org.nsdev.experiments;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class SearchResultsFragment extends android.support.v4.app.Fragment
{

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.results, container);
    }

}

SearchCriteriaFragment.java:

package org.nsdev.experiments;

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class SearchCriteriaFragment extends android.support.v4.app.DialogFragment
{

    private static final String TAG = "SearchCriteriaFragment";
    View v;

    static SearchCriteriaFragment newInstance()
    {
        Log.e(TAG, "newInstance");
        SearchCriteriaFragment d = new SearchCriteriaFragment();
        return d;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        if (v == null)
        {
            v = inflater.inflate(R.layout.criteria, container, false);
        } 
        else
        {
            if (v.getParent() instanceof ViewGroup) 
            {
                Log.e(TAG, "Removing from viewgroup");
                ((ViewGroup)v.getParent()).removeAllViews();
            }
        }

        return v;
    }

}

You can see, here, that I had to work around a problem with the view being reused. There was an error stating that the view already had a parent and would need to be removed from it before it could be added to another parent. Again, this seems like I must be doing it the wrong way.

res/layout/main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <fragment
        android:id="@+id/search_results"
        android:name="org.nsdev.experiments.SearchResultsFragment"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</RelativeLayout>

res/layout-land/main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <FrameLayout
        android:id="@+id/search_criteria_container"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent" />

    <fragment
        android:id="@+id/search_results"
        android:layout_toRightOf="@+id/search_criteria_container"
        android:name="org.nsdev.experiments.SearchResultsFragment"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</RelativeLayout>

You can see from these two layouts, what I am trying to do. In the normal orientation, the FrameLayout doesn't exist, so we can detect this and show the DialogFragment instead. Otherwise, we can embed the DialogFragment into the FrameLayout.

The final two layouts I've just put placeholders in for the results and criteria layouts:

res/layout/criteria.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" android:background="#300F">

    <CheckBox
        android:id="@+id/checkBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="21dp"
        android:paddingRight="30dp"
        android:text="Test Criteria" />

</RelativeLayout>

res/layout/results.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" android:background="#f00">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="Results"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

I thought I had figured it out until I started rotating the device and checking the checkbox on an ICS device. Notice that I've set the color to be semi-transparent for the criteria.xml RelativeLayout background. What I noticed was that in landscape orientation, the embedded view was actually keeping old versions of the view around, and these would be overlaid with newer copies of the view each time I switched to portrait and back again. That's one of the reasons I tried to keep only one copy of the view returned in from the onCreateView of the SeachCriteriaFragment. This does not fix this problem.

I can only come to one conclusion: I must be doing this wrong. I suspect that the Fragments framework was never designed to do this kind of fragment reuse. How can I get a nice rotation working where in Portrait I show the DialogFragment as a dialog, and in Landscape it is embedded? Can I get the framework to do something like this more automatically?

I tried setting searchCriteriaFragment.setRetainInstance(true) and all hell broke loose, so I deleted that line immediately.

Thanks for any help getting me on the right track here.

Neal Sanche
  • 1,626
  • 16
  • 25
  • I think there may be something about your configChanges attribute or Activity code that might be causing your fragments to disappear on rotation. I think you would do well to share as much code as you can. – nmr Feb 11 '12 at 02:03

1 Answers1

0

I would say this.

On the phone make the SearchFormFragment an Activity and give it the theme Dialog. Then the SearchResultsFragment is also a separate Activity on it's own.

The SearchForm xml would look like:

 <LinearLayout >
   <fragment class="com.your.package.ui.fragment.SearchFormFragment" />
 </LinearLayout>

To get the Dialog theme:

 <manifest . . . >
      <application . . . >
         <activity 
            android:name=".ui.phone.SearchFormFragmentActivity"
            android:theme="@android:style/Theme.Dialog" . . . >
         <activity 
            android:name=".ui.phone.SearchResultsFragmentActivity" . . . >

This makes orientation change simple using onSaveInstanceState and onRestoreInstanceState and the activity lifecycle.

Then on the tablet you have another activity with your two fragments in.

         <activity 
            android:name=".ui.tablet.SearchFragmentActivity" . . . >

SearchFragmentActivity.xml:

   <LinearLayout >
     <fragment class="com.your.pacakage.ui.fragment.SearchFormFragment"/>
     <fragment class="com.your.package.ui.fragment.SearchResultsFragment" />
   </LinearLayout>

Simplified.

Community
  • 1
  • 1
Blundell
  • 75,855
  • 30
  • 208
  • 233
  • Ideally I would like to use the Android Compatibility Library, and actual Fragments for the UI elements. – Neal Sanche Feb 11 '12 at 00:33
  • Thats exactly what I mean? edited the fragment xml to clear it up (its just pseado anyway) – Blundell Feb 11 '12 at 00:36
  • But I want to know how to make one Activity that manages the two fragments appropriately, not three activities. – Neal Sanche Feb 11 '12 at 00:36
  • Please see my updated attempt and let me know if you have any ideas. – Neal Sanche Feb 12 '12 at 07:47
  • I think your over complicating it and my solution is not simple but the simplest. It's what google do themselves to differentiate between tablets and phones. I'd recommend you go look at their GoogleIO2011 app source code – Blundell Feb 12 '12 at 15:42
  • I downloaded it, looked at the running application for a while but I don't see any places where they are doing something similar to what I would like to do. – Neal Sanche Feb 12 '12 at 18:14
  • The source: http://code.google.com/p/iosched/source/browse/#hg%2Fandroid%2Fsrc%2Fcom%2Fgoogle%2Fandroid%2Fapps%2Fiosched%2Fui%2Ftablet%253Fstate%253Dclosed – Blundell Feb 12 '12 at 18:22
  • The rough time in the talk where they talk about multi-panes: http://www.youtube.com/watch?feature=player_detailpage&v=WGIU2JX1U5Y#t=1632s – Blundell Feb 12 '12 at 18:25
  • Thanks for the information, Blundell. I think, your solution may help someone else, so I'll mark it as answered even though I ended up not switching between dialog and embedded on rotation since I had enough screen real estate to remain embedded. – Neal Sanche Mar 07 '12 at 17:58