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.