Android Adventures - Managing Fragments
Written by Mike James   
Thursday, 22 May 2014
Article Index
Android Adventures - Managing Fragments
Layout
Large Display
The Normal Screen
The BackStack

The Normal Screen

Everything looks good and very easy - all you have to do is create Fragments and place them in layouts targeting particular size screens. For the "big" screen there is nothing more to do.

For the normal screen we need to add some code that makes the second Fragment appear when appropriate i.e. we need to swap the Fragments in the one container. 

In this example we are using the Button to indicate that the Fragments should be swapped. This isn't particularly realistic as the need to swap Fragments usually arises from something more complicated that the user does - but its OK for an example.

What we need to do is write an event handler for the Button that will dynamically change which Fragment is displaying its UI in the container. We also need to initially add both Fragments to the FragmentManager and set the first one to display - after this the event handler can swap them over.

The initial XML layout for the normal screen is just going to be composed of the container and the button:

<LinearLayout  xmlns:tools="http://schemas.android.com/tools"
 android:orientation="vertical"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 xmlns:android="http://schemas.android.com/
    apk/res/android">
 <FrameLayout
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_weight="1"
  android:id="@+id/Container1">
 </FrameLayout>
 <Button
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="New Button"
  android:id="@+id/button"
  android:layout_gravity="center_horizontal" />
</LinearLayout>

This is essentially the same layout but now there is only Container1.

When the Activity starts up the first task is, as always, to load the layout and inflate it:

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);

Next we need to determine which layout file has been loaded. If it is the one for the large display then we don't have to do anything more. If it is the one for the normal layout then we have to setup the Fragments and so on. 

We could test for the current size of the screen but in this case there is a simple way to test for which layout has been inflated - the presence or absence of Container2. So all we need to do is:

if( findViewById(R.id.Container2)!=null)return;

It the return isn't taken there is no Container2 and both Fragments have to share Container1. 

As in the case of working with a single Fragment we don't want to create any Fragment if the system has done us the favour of recreating them following a restart of the Activity:

if (savedInstanceState == null) {
  BlankFragment2 bf2=BlankFragment2.
                newInstance("String1","String2");
  BlankFragment1 bf1=BlankFragment1.
                newInstance("String1","String2");

Now we have our two Fragments instantiated using the factory methods - don't worry about the String parameters they are just there to keep the default factory methods happy. In a real app we would have customized the factory method to accept useful initial parameters as explained in the previous chapter. 

The next step is to create a transaction to add the Fragments to the FragmentManager and detach the second one:

 getFragmentManager()
  .beginTransaction()
  .add(R.id.Container1, bf2, "frag2")
  .detach(bf2)
  .add(R.id.Container1, bf1, "frag1")
  .commit();
}

Notice that each Fragment has been tagged so that it can be retrieved from the FragmentManager later. 

When the transaction is complete we have both Fragments added to the FragmentManager with Fragment2 detached and Fragment1 attached.

We also need to setup the Button's event handler using an anonymous class as usual:

 

Button button1= (Button) findViewById(R.id.button);
View.OnClickListener Listener=
                     new View.OnClickListener(){
 @Override
 public void onClick(View view) {

 

Now we need to get the two Fragments so we can detach one and attach the other and this can most easily be done using the FragmentManager's findFragmentByTag method to get us a fresh reference to the instance:

BlankFragment1 bf1=(BlankFragment1)
 getFragmentManager().findFragmentByTag("frag1");
BlankFragment2 bf2=(BlankFragment2)
 getFragmentManager().findFragmentByTag("frag2");

Notice the need to cast to the correct sub-class of Fragment - the find method returns a Fragment. 

Now that we have the Fragments we can test to see which is detached and perform the appropriate detach, attach operations:

 FragmentTransaction ft = getFragmentManager().
                              beginTransaction();
 if (bf2.isDetached()) {
  ft.detach(bf1).attach(bf2);
 }
 if (bf1.isDetached()) {
  ft.detach(bf2).attach(bf1);
 }
 ft.commit();
}
};

Notice that as we need to do different things according to which Fragment is attached the "chained" style of creating a transaction isn't appropriate. 

Finally we set the event handler to the Button:

 button1.setOnClickListener(Listener);
}

 

Now if you run the app on a normal screen device you will discover that you can swap which Fragment is on display by clicking the button.

You can click the button as often as you like and you can check that the Fragments persist by adding some data to the text fields and then swapping them. You should also discover that the data you added is persisted even when the app is destroyed and recreated by a configuration change like a screen rotate. 

The only time the Fragment's state isn't preserved is if you kill the app by say using the back button when the app restarted as if for the first time.

This is the general method for using Fragments within an Activity. Add all the Fragments you want to use to the Fragment Manager and use attach and detach to add and remove them from the UI.  

So that you can see were all the code fits here is the entire listing of the onCreate method:

@Override
protected void onCreate(Bundle savedInstanceState){
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 if (findViewById(R.id.Container2) != null) return;
 if (savedInstanceState == null) {
  BlankFragment2 bf2 = BlankFragment2.
      newInstance("String1", "String2");
  BlankFragment1 bf1 = BlankFragment1.
      newInstance("String1", "String2");
   

 getFragmentManager()
  .beginTransaction()
  .add(R.id.Container1, bf2, "frag2")
  .detach(bf2)
  .add(R.id.Container1, bf1, "frag1")
  .commit();
 }
 Button button1 = (Button)
                   findViewById(R.id.button);
 View.OnClickListener Listener =
              new View.OnClickListener() {
  @Override
  public void onClick(View view) {
   BlankFragment1 bf1 = (BlankFragment1)  
    getFragmentManager().findFragmentByTag("frag1");
   BlankFragment2 bf2 = (BlankFragment2)    
    getFragmentManager().findFragmentByTag("frag2");
   FragmentTransaction ft =
    getFragmentManager().beginTransaction();
   if (bf2.isDetached()) {
      ft.detach(bf1).attach(bf2);
   }
   if (bf1.isDetached()) {
      ft.detach(bf2).attach(bf1);
   }
   ft.commit();
  }
 };
 button1.setOnClickListener(Listener);
}

Animations

A subject that to treat in full would take us too far away from Fragments is animation.

You can add custom and stock animations to changes in Fragments that you bring about using the FragmentManager. Mostly these are used when you add, remove or replace a Fragment but they also work when you attach and detach a Fragment.  

For example, if you add 

 ft.setTransition(
  FragmentTransaction.TRANSIT_FRAGMENT_OPEN);

to the start of the FragmentTransaction in the button onClick handler then when you change which Fragment is visible you will see an animation as the two swap over.

Also see setTransitionStyle and setCustomAnimations.



Last Updated ( Saturday, 25 July 2015 )