Tuesday, April 2, 2013

Android Fragment transaction: FragmentManager and Backstack

Topics covered

Android Fragment

Fragment manager (FragmentManager): add, replace, remove fragment

Fragment backstack

Fragments are useful when we want to support multiple screen size. To manage fragments we need a
FragmentManager that help us to handle trasaction between fragments.  With transaction we mean a sequence of steps to add, replace or remove fragments. In the last post we showed how to support multiple screen size and orientation using fragments. Now we want to go a bit further.
In Android there are two different methods to create fragment:
  • static method
  • dynamic method
Static method is when we “write” directly our fragment in XML file. This is an example:
<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
android:orientation="horizontal" >

<fragment android:id="@+id/listFragment"
android:layout_width="0dp"
android:layout_height="wrap_content"
class="com.survivingwithandroid.fragment.LinkListFragment"
android:layout_weight="2"/>
</LinearLayout>

In this case our layout isn’t dynamic because we can’t manage the fragment at runtime. So if we want to make our layout dynamic (and this happen very often) we need to do it in another way. We have to use FrameLayout. With FrameLayout we can handle fragments as we need at runtime, but to do it we need a manager, in other words a component that can handle fragments. This is FragmentManager. This component can add, replace and remove fragments at runtime. The layout above becomes:

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >


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

</RelativeLayout>

So now we have all the freedom to “inject” in the FrameLayout our fragments.



FragmentManager


As we said before FragmentManager is the key component. Using FragmentManager we can discover (find) fragment inside our layout using findFragmentById or findFragmentByTag. While the first method is very simple and we use the common android id to discover the component, the second method (that uses tag) is unsual. A tag in a fragment is simply a “name” we give to the fragment so that we can find it later using that name. We are more interested in some other methods.

All the operation that are made by the FragmentManager happens inside a “transaction” like in a database operation. First, we can get the FragmentManger using the Activity method getFragmentManager(). Once we have the reference to this component we have to start the transaction in this way:
FragmentManager fm = getFragmentManager();

// Transaction start
FragmentTransaction ft = fm.beginTransaction();

.......

// Transaction commint
ft.commit();

At the end when we finished and we are ready to show our fragment we have to call the commit method that marks the end of the transaction. For example, if we remember the example we showed in the last post, and using a FrameLayout we can “insert” the link list in this way:
public class MainActivity extends Activity implements ChangeLinkListener{

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();

LinkListFragment llf = new LinkListFragment();
ft.replace(R.id.listFragment, llf);
ft.commit();
}
...


}

But…What kind of operation we can perform inside the transaction?…Well we can:
  • add a new fragment
  • replace an existing fragment
  • remove a fragment
If we remember the example we used last time every time an user clicks on a link the interface method onLinkChange is called. So in this method we want to show how to perform the operation listed above.
if (findViewById(R.id.fragPage) != null) {
WebViewFragment wvf = (WebViewFragment) getFragmentManager().findFragmentById(R.id.fragPage);

// Part 1: Tablet and so on
if (wvf == null) {
System.out.println("Dual fragment - 1");
wvf = new WebViewFragment();
wvf.init(linkData.getLink());
// We are in dual fragment (Tablet and so on)
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
//wvf.updateUrl(link);
ft.add(R.id.fragPage, wvf);
ft.commit();

}
else {
Log.d("SwA", "Dual Fragment update");
wvf = new WebViewFragment();
wvf.init(linkData.getLink());
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragPage, wvf);
ft.commit();

//wvf.updateUrl(linkData.getLink());
}
}
else {
Log.d("SwA", "replace");
FragmentTransaction ft = getFragmentManager().beginTransaction();
WebViewFragment wvf = new WebViewFragment();
wvf.init(linkData.getLink());
ft.replace(R.id.listFragment, wvf);
ft.commit();
}

If you compare this piece of code with the last example in the previous post you notice some differences. First in the green part we don’t start an activity anymore when user clicks on a link but we simply replace the FrameLayout with a fragment showing the web page. More over, in the yellow part we don’t update the current fragment inside the FrameLayout but we create a new fragment and we replace the existing fragment with the one just created. The app behaviour is always the same but we obtained this behaviour in a different way, using dynamic fragments inside our layout.

If you run the app and start using it you can notice a “wired” behaviour when you press back button. We’d expect that the back button would bring us to the last web page visited but it isn’t like we supposed. When you press back button you come to the home page.

android_fragment_tutorial1android_fragment_tutorial2android_fragment_tutorial3

Why?

Fragment Backstack


Well the behaviour described above is normal because the back button acts at activity level not at the fragment level. So our activity is the same while we replace fragments as the user interacts with the app. In this way when we tap on the back button we select the first activity on the activity stack, in our case the home. We don’t want this behaviour but we want that when we click on the back button we go back in the fragments stack. We can achieve it adding the fragment to the backstack. We do it in this way:
@Override
public void onLinkChange(LinkData linkData) {
System.out.println("Listener");
// Here we detect if there's dual fragment
if (findViewById(R.id.fragPage) != null) {
WebViewFragment wvf = (WebViewFragment) getFragmentManager().findFragmentById(R.id.fragPage);

if (wvf == null) {
System.out.println("Dual fragment - 1");
wvf = new WebViewFragment();
wvf.init(linkData.getLink());
// We are in dual fragment (Tablet and so on)
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
//wvf.updateUrl(link);
ft.add(R.id.fragPage, wvf);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
// Add to backstack
ft.addToBackStack(linkData.getName());
ft.commit();

}
else {
Log.d("SwA", "Dual Fragment update");
wvf = new WebViewFragment();
wvf.init(linkData.getLink());
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragPage, wvf);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
// Add to backstack
ft.addToBackStack(linkData.getName());
ft.commit();

//wvf.updateUrl(linkData.getLink());
}
}
else {
/*
System.out.println("Start Activity");
Intent i = new Intent(this, WebViewActivity.class);
i.putExtra("link", link);
startActivity(i);
*/
Log.d("SwA", "replace");
FragmentTransaction ft = getFragmentManager().beginTransaction();
WebViewFragment wvf = new WebViewFragment();
wvf.init(linkData.getLink());

ft.addToBackStack(linkData.getName());
ft.replace(R.id.listFragment, wvf);
ft.commit();
}

}

We use the addToBackStack method of the FragmentTrasaction and we add every fragment to the backstack. In this way when we tap on the back button we have the correct behaviour.

Source code available soon


Android Fragment transaction: FragmentManager and Backstack Rating: 4.5 Diposkan Oleh: Unknown

0 comments:

Post a Comment