Android Adventures - Static Fragments
Written by Mike James   
Monday, 16 January 2017
Article Index
Android Adventures - Static Fragments
Using the Fragment UI
Where Are My Fragments?
A Static Example

How Does It Work?

At this early stage it is worth asking exactly how a static Fragment is created by inflating it. Inflating a layout usually just means scanning though and finding each XML tag. Each tag is then used to create a corresponding View element. For example a button tag results in a Button View object. In the case of a <fragment> tag the inflater doesn't create a Fragment object because this isn't part of the view hierarchy. What is does is to create an instance of the Fragment that you have specified and then it calls that Fragment's onCreateView to get the View hierarchy that the Fragment contributes to the Activity's View. 

Let's look at this in just a little more detail. 

The setContentView method first gets a suitable inflater and the Resource that is the layout. It then scans though the layout creating and configuring View objects that correspond to the tags. Finally it reaches the <fragment> tag and eventually calls FragmentManger.onCreateView where you will find the following code:

if (fragment == null) {
 fragment = Fragment.instantiate(context, fname);
 fragment.mFromLayout = true;
 fragment.mFragmentId = id != 0 ? id : containerId;
 fragment.mContainerId = containerId;
 fragment.mTag = tag;
 fragment.mInLayout = true;
 fragment.mFragmentManager = this;
 fragment.mHost = mHost;
 fragment.onInflate(mHost.getContext(), attrs,
                  fragment.mSavedFragmentState);
 addFragment(fragment, true);

This sets various parameter but the first instuction is the most important it calls Fragment.instantiate to create the Fragement. This eventually calls your Fragment's parameterless constructor:

/**
* Create a new instance of a Fragment with the given
*  class name. This is
* the same as calling its empty constructor.
*
* @param context The calling context being used
* to instantiate the fragment.
* This is currently just used to get its ClassLoader.
* @param fname The class name of the fragment
* to instantiate.
* @param args Bundle of arguments to supply to
* the fragment, which it
* can retrieve with {@link #getArguments()}. May be null.
* @return Returns a new fragment instance.
* @throws InstantiationException If there is a failure
* in instantiating
* the given fragment class. This is a runtime exception;
* it is not
* normally expected to happen.
*/

Once this returns the code for the Fragment exists and is ready to be used.

The final instruction adds the Fragment to the list that that FragmentManager keeps of all Fragments that you create, no matter how you create them and calls moveToState to change it from the Initializing to active state.  This is also the place that the onAttach event is triggered.

In FragmentMangers moveToState we have

if (f.mFromLayout) {
// For fragments that are part of the content view
// layout, we need to instantiate the view immediately
// and the inflater will take care of adding it.
f.mView = f.performCreateView(
   f.getLayoutInflater(f.mSavedFragmentState),
   null, f.mSavedFragmentState);

This is where your Fragment's createView method is called to supply the View hierarchy that will be used by the Activity:

View performCreateView(LayoutInflater inflater,
  ViewGroup container,
   Bundle savedInstanceState) {
 if (mChildFragmentManager != null) {
  mChildFragmentManager.noteStateNotSaved();
 }
 return onCreateView(inflater, container,
                savedInstanceState);
}

That is when the moveToState is complete we have both the Fragment's code and its View hierarchy ready to be used. 

This is about all that is relevant to the way the static Fragment is handled. Notice that it is the FragmentManager that does most of the work but it's code handles static Fragments in slightly different ways to dynamic Fragments.

The key point is that when the inflater has done its work and the Activity's call to setContentView returns the Fragment is ready to use - both it's code and its UI. The situation with respect to dynamic Fragments is not so clear cut.  

Using The Fragment UI

The Fragment provides a Button and a TextField - how can we use them?

This is a more difficult question than you might expect. For a static Fragment there is a very simple answer but in most cases it isn't really the correct way to do things. 

For a static Fragment the UI that the Fragment supplies is ready to use straight after the call to setContentView in the OnCreate event handler. What this means is that you can use the View objects that the Fragment generates in the same way as the View objects the Activity creates - there is no real difference.

What is more the ids of the Fragment's View objects are also included in R.id and so you can write code like: 

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

  Button b1=(Button) findViewById(R.id.button);
  b1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    TextView tv1=(TextView) findViewById(R.id.textView);
    tv1.setText(((Button)v).getText());
  }
 });

 

This simply finds the Button in the Fragment's UI and sets its OnClick event handler to transfer the Button's text to the TextView when clicked. Not exciting but enough to show the general idea. If you try it you will find that it works as promised. 

This is easy but it isn't good design.

What is the point in using a Fragment if you are simply going to code what its UI does in the Activity? You might as well have added the Button and TextView in the usual way and not used a Fragment. 

The key idea is that a Fragment not only encapsulates part of the UI but its behaviour as well. If at all possible the code should have been part of the Fragment's Java file i.e. its behaviour. To do this is also easy but there are a few twists. The idea is that you should set the Button's OnClick event handler not in the Activity but in the Fragment's onCreateView event handler.

The first step is to create, in the Fragment, the View that the onCreateView will return so that we can work with it:

@Override
public View onCreateView(LayoutInflater inflater,
  ViewGroup container,
  Bundle savedInstanceState) {
View v=inflater.inflate(R.layout.fragment_testfrag,
                                   container, false);

Now we can use findViewById to find the Button:

Button b1=(Button) v.findViewById(R.id.button);

Now we can attach the onClick event handler to the Button:

 b1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        TextView tv1=(TextView) v.getRootView().
                    findViewById(R.id.textView);
    }
   });
 return v;
}

 

Putting this all together gives:

@Override
public View onCreateView(LayoutInflater inflater,
          ViewGroup container,
          Bundle savedInstanceState) {
 View v=inflater.inflate(R.layout.fragment_testfrag,
                                   container, false);
 Button b1=(Button) v.findViewById(R.id.button);
 b1.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
   TextView tv1=(TextView) v.getRootView().
                    findViewById(R.id.textView);
   tv1.setText(((Button)v).getText());
  }
 });
 return v;
}

The key is that you can use getRootView to return the entire View hierarchy that any particular View is in. In this case v is the Button and v.GetRootView returns the entire View hierarchy as displayed by the Activity including the portion supplied by the Fragment. 

Notice that we have to find both the Button and the TextView each time because it is possible that the View hierarchy will be destroyed for example when the device is rotated. Notice that you don't have to do anything to recreate the Fragment the system will call onCreateView. If you want to persist anything that the system doesn't handle automatically, such as the TextView, you need to use the Bundle to save and restore the state.

In general if you want to allow the Activity to modify the UI that the Fragment creates then you should implement access methods in the Fragment that hide the details of implementation. For example if you want to change the text in the Button then you need to create a setter something like:

public void setMainText(String s){
 Button b1=(Button)
       (getActivity().findViewById(R.id.button));
 b1.setText(s);
}

The important thing about the setter is that it hides all details from the Activity which doesn't have to know what the id of the Button is. Of course you do need to make sure that Fragment ids are unique. 

Also notice the use of getActivity to return the Activity that the Fragment is bound to and then the use of findViewById to locate the Button. 

This brings us to another problem. How does the Activity discover the Fragment's code? 

In other words, how can we call setMainText in the Activity?

 



Last Updated ( Monday, 16 January 2017 )