Fragment And Activity Working Together
Written by Mike James   
Thursday, 27 March 2014
Article Index
Fragment And Activity Working Together
Using Bundles
Persisting State
An Interface Callback
Persisting Fragments

The Solution - An Interface Callback

If you think about it for a moment the way that you ensure that an object has a particular method is to insist that it implements an interface that contains that method. 

If you recall an interface is a bit like a class with definitions but no implementation. If another class implements the interface then it has to provide code for everything that the interface defines. One extra detail is that you can access the interface methods on the class by casting it to the interface. 

This is a subtle point so let's have an example.  If you already know enough about interfaces and how they relate to type then skip the next small section. 

If we define an interface:

public interface myInterface{
 public void myMethod(int a);
}

Then any class that implements the interface has to implement myMethod with the same signature and return type. 

Now if we have Class2 that inherits from Class1 we can implement the interface:

public  class Class2 extends Class1
          implements myInterface{
 
 public void myMethod(int a){
  return 2*a;
 }
}

Obviously you can now call the interface method on an object of type Class2:

Class2 class2=new class2();
int b=class2.myMethod(1);

However you can also call the interface methods on an object of type Class1 that happens to be at run time an object of Class2 as long as you cast it to the interface. That is:

Class2 class2=new class2();
int b=class2.myMethod(1);
Class1 class1=class2;
int c=(myInterface) class1.myMethod(1);

An interface allows you to call a method defined in the interface by casting an object that might have been passed to you as a base type to the interface type and not to a specific child class. 

Why is this useful?

If we insist that any Activity that uses the Fragment implements an interface with the callback defined then we can cast the Activity to the interface and this works no matter what the type of the Activity is. 

So our plan now is to:

1) Define an interface within the Fragment that defines the callbacks that the Activity has to implement to use the Fragment.

2) Make the Activity implement the interface and hence the callbacks

3) Use Fragment onAttach to get the callbacks and store them for later use. 

Time to see how the template does this.

The Fragment template generates the following code for the interface:

public interface OnFragmentInteractionListener {
 public void onFragmentInteraction(View view);
}

You are free to change the names of the interface and the method or methods it defines. You can define as many callback methods as you need to signal to the Activity that the Fragment needs attention. Also you can include whatever parameters you like to pass data from the Fragment to the Activity.

In this case we have modified the generated code to pass the Button as a View object to follow the pattern of the event handler used in the earlier examples.

The next step is to save a reference to the interface in a private field for later use each time the Fragment is attached to a new Activity:

First we need a private field to store the callback object in:

private OnFragmentInteractionListener mListener;

and we can now define the onAttach event handler:

@Override
public void onAttach(Activity activity) {
 super.onAttach(activity);
 try {
  mListener =
    (OnFragmentInteractionListener)  activity;
 } catch (ClassCastException e) {
  throw new ClassCastException(activity.toString()
 + " must implement
         OnFragmentInteractionListener");
 }
}

 

This simply casts the Activity to the interface type we have created. In theory every Activity that uses the Fragment should implement the interface but we need to check that it has - hence the try-catch.

One small detail - it is possible for the Activity to remove the Fragment without the Fragment being destroyed we have to remove the reference to the callback interface onDetach:

@Override
public void onDetach() {
 super.onDetach();
 mListener = null;
}

That's all we need to set up the persistence of the callback.

Now as long as the Activity implements the interface the callback will be hooked up and working whenever the Fragment is attached to an Activity. It can be destroyed and created by the system as many times as necessary and the callback will still work. 

To use the callback all the Fragment has to do is call it:

 mListener.onFragmentInteraction(v);

where v is the View object to pass to the Activity. 

For example; if you want to activate the callback when the Button is clicked you would write in the onCreateView method:

Button bt=(Button) v.findViewById(R.id.button);
bt.setOnClickListener(
  new View.OnClickListener() {
   @Override
   public void onClick(View view) {
    mListener.onFragmentInteraction(view);
   }
});

 

This creates an event handler for the Button which delegates what has to happen to the interface by calling onFragmentInteraction. Notice that you can't hook up the callback directly to the Button because it is the wrong type. In general it is better to create custom callbacks and use event handlers within the Fragment to call them.

 

This completes all the code needed to get the Fragment to use a callback provided by any Activity that wants to use it. The final part of the whole thing is getting the Activity to implement the interface. 

public class MainActivity extends Activity    
         implements OnFragmentInteractionListener{

You can get Android Studio to implement the interface for you by right clicking on its name and selecting Generate,Implement methods. In this case the following method is generated:

@Override
public void onFragmentInteraction(View view) {
}

This can now be filled out to do whatever you want. In our case transfer the text from the Button to the TextView. This isn't a good use of the callback because, as we have already discussed anything to do with the UI provided by the Fragment should be handled by the Fragment. It does make an easy example however:

@Override
public void onFragmentInteraction(View view) {
 TextView tv = (TextView)  findViewById(R.id.textView);
 Button bt = (Button) view;
 tv.setText(bt.getText());
}

 

If you run this program you will discover that when you click the Button the text is transferred and this still works after your rotate the device. 

Summary

This has been a long description and a short summery of exactly what happens might make it all seem simpler.

What you have to do to implement Fragment callbacks is:

  1. Define an interface in the Fragment which has all of the methods that you would like the Activity to implement as callbacks. You can include any parameters and return types as you need to pass the data involved.
  2. Make the Fragments onAttach event handler try to retrieve each of the interface methods defined in the Activity and store them in private fields of the correct type.
  3. Make use of the interface methods anywhere within the Fragment that you need to by calling them using the private fields.
  4. In the Activity you have to implement the interface that the Fragment defines and this means writing some code for each of the callbacks.
  5. Remember to add an onDetach method in the Fragment which sets each of the private fields to null.


Last Updated ( Saturday, 25 July 2015 )