Introducing Android Fragments
Written by Mike James   
Thursday, 24 November 2016
Article Index
Introducing Android Fragments
Using a Fragment
The Activity
Summary and Conclusion

The Correct Way To Create A Fragment

The current onCreate method works up to a point, but it is inefficient because it manually creates the Fragment after the system has restored the old instance. 

A better way to code the onCreate method is:

@Override
protected void onCreate(Bundle savedInstanceState){
 super.onCreate(savedInstanceState);
 LinearLayout linLayout= new LinearLayout(this);
 linLayout.setId(100);
 setContentView(linLayout);
 if (savedInstanceState == null) {
  myFragment frag1=new myFragment();
  FragmentManager fm=getFragmentManager();
  FragmentTransaction ft=fm.beginTransaction();
  ft.add(100,frag1);
  ft.commit();
 }
}

By checking to see if there is any saved instance state we can avoid creating the Fragment when it isn't needed. Notice that this is slightly more subtle than you might think. The Actvities UI isn't automatically recreated by the system so you need to recreate it even if the app is being restarted. Part of the recreation is firing the onCreateView event which the existing Fragment object handles along with the new Fragment object created by the Activity. Each new Fragment renders its UI on demand and so the UI slowly fills up with Button objects.  

Test that this works by running the app and then rotating the device. 

We need to look more at how Fragments can retain their state when the system destroys and recreates them.

By now you might well be thinking that this is a lot of work to go to when you could have just added the Button to the layout in the Activity. This is true enough and the reason that Fragments and their use have been left until now. Android Studio does have a way of making all of this seem much easier but at some point you do have to understand what it is doing to get any value from using Fragments.

It is time to move on and look at how to make Fragments do something.

Fragment Events

The next topic we have to consider is the issue of how much work a Fragment should do.

For example, when a button is clicked what should handle the event? 

The idea of a Fragment is that it is a wrapped up chunk of UI that can be used by any Activity that needs it.

On this basis you have to ask yourself if the event that has to be handled is the same for every Activity or unique to each Activity. If it is the same then the event handler is better written within the Fragment. 

Let's see how to do this. 

The principle of getting a Fragment to handle an event is easy enough to understand. The Fragment doesn't have a UI of its own - it is displayed by an Activity  that the Fragment is associated with. The events are generated by objects in the View hierarchy, which is owned by the Activity. If you try to use Android Studio to add an event handler, for example, it will add it to the Activity and not to the Fragment. 

You can, however, define the EventListener that you want to handle the event in the Fragment and then hook it up to the View object in the Activity in which you want to generate the event.

Creating the event handler follows the same two common patterns that you used to create it in the Activity.

Starting in the Fragment you can either make it implement the necessary interface or you can use an anonymous class. The anonymous class approach is the more flexible so let's do it this way.

First let's add a TextView to be used in the event handler. The user can click the button and transfer the button's text to the TextView. Add the following code to the Fragment's onCreateView method:

final TextView tv=new TextView(getActivity());
tv.setText("Text Entry");
linLayout.addView(tv);

The reason for the use of the final keyword will become apparent.

Next we have to add an instance of an OnClickListener object: 

View.OnClickListener onclick=
                      new View.OnClickListener(){
  @Override
  public void onClick(View view){
   Button bt=(Button) view;
   tv.setText(bt.getText());
 }
};

If you recall Java uses objects to handle events. You create an object of the correct type using an anonymous class which avoids having to create a named class in a separate file. 

In this case the OnClickListener has just one method the onClick event handler that we have to override. It is assumed that the event handler will only be attached to a Button so we are safe in casting the view parameter to a Button object. Next we can use the button text to set the TextView's text. 

At this point you might be worried by the use of the local variable tv as it doesn't belong to the OnClickListener object but the containing Fragment. How can this class access a variable that isn't part of it?

In Java an inner class, like the OnClickListener has access to the local variables of its containing class but only if they are declared final. A variable that is declared final can only be assigned to once and in this case there is no reason not to declare the TextView as final - it isn't going to change the object it references.

Now all we have to do is set the onClick hander of the button to the OnClickListener object onclick that we have created:

b.setOnClickListener(onclick);

This is all we have to do. Notice however that it is slightly more subtle than you might think as all of the View objects are only created when the Activity calls the onCreateView method. 

The complete Fragment class is now: 

public class myFragment extends Fragment {
@Override
public View onCreateView(
                 LayoutInflater inflater,
                 ViewGroup container,
                 Bundle savedInstanceState) {

 LinearLayout linLayout=
            new LinearLayout(getActivity());
 
 Button b = new Button(getActivity());
 b.setText("Hello Button");
 linLayout.addView(b);
 
 final TextView tv=new TextView(getActivity());
 tv.setText("Text Entry");
 linLayout.addView(tv);
 
 View.OnClickListener onclick=
                   new View.OnClickListener(){
  @Override
  public void onClick(View view){
   Button bt=(Button) view;
   tv.setText(bt.getText());
  }
 };
 b.setOnClickListener(onclick);
 return linLayout;}
}

 

If you now run the program you will find that when the button is clicked its text is transferred to the TextView. Notice that this would be true no matter what Activity used the Fragment. 

It is behavior inherent to the Fragment. 

You also need to be clear what is going on when the system destroys and recreates the Fragment. 

If you run the app and click the button you will see the TextView change. If you then rotate the device you will see that the TextView reverts to its original text. If you click the button you will again see that the text changes i.e. the event handler is still working. 

What happens is that the system calls the onCreateView when it recreates the Fragment - this sets up the UI and attaches the event handler. All previous state information is lost. 

If you want to keep the state information through a destroy-create cycle you have to use the savedInstanceState Bundle that is passed to the onCreateView method. This works in much the same way as for the Activity.

Saving State

Saving the state of a Fragment is very easy.

All you have to do is override the onSaveInstanceState event handler and use the savedInstanceState bundle to store any additional data you need to persist. For example to keep the TextView's current content all you have to do is add to the Fragment:

@Override
public void onSaveInstanceState(
                    Bundle savedInstanceState) {
 super.onSaveInstanceState(savedInstanceState);
 TextView tv=
       (TextView) getView().findViewById(110);
 savedInstanceState.putCharSequence(
                       "myText",tv.getText());
}

 

Notice the use of getView to retrieve the View hierarchy created by the Fragment and the usual findViewById. To make this work we also need to add an Id to the TextView in the onCreateView:

tv.setId(110);

This saves the state information but we still have to manually restore it in the onCreateView:

if (savedInstanceState != null) {
 tv.setText(savedInstanceState.getCharSequence(
                                    "myText"));
};

You can also use the onCreate event handler to restore values to variables. It is called before the onCreateView event handler so don't try to use it to modify the View hierarchy.

The complete Fragment is: 

public class myFragment extends Fragment {

 @Override
 public View onCreateView(
                  LayoutInflater inflater,
                  ViewGroup container,
                  Bundle savedInstanceState) {
  LinearLayout linLayout=
              new LinearLayout(getActivity());
  Button b = new Button(getActivity());
  b.setText("Hello Button");
  linLayout.addView(b);
  final TextView tv=new TextView(getActivity());
  tv.setText("Text Entry");
  tv.setId(110);
  linLayout.addView(tv);
  View.OnClickListener onclick=
                     new View.OnClickListener(){
   @Override
   public void onClick(View view){
    Button bt=(Button) view;
    tv.setText(bt.getText());
   }
  };
  b.setOnClickListener(onclick);
  if (savedInstanceState != null) {
   tv.setText(savedInstanceState.getCharSequence(
                                    "myText"));
  }
  return linLayout;
 }


 @Override
 public void onSaveInstanceState(
                   Bundle savedInstanceState) {
 super.onSaveInstanceState(savedInstanceState);
 TextView tv=
       (TextView) getView().findViewById(110);
 savedInstanceState.putCharSequence( 
                      "myText",tv.getText());
 }
}

<ASIN:187196251X>



Last Updated ( Sunday, 26 March 2017 )