[android] How to handle button clicks using the XML onClick within Fragments


Answers

I prefer using the following solution for handling onClick events. This works for Activity and Fragments as well.

public class StartFragment extends Fragment implements OnClickListener{

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View v = inflater.inflate(R.layout.fragment_start, container, false);

        Button b = (Button) v.findViewById(R.id.StartButton);
        b.setOnClickListener(this);
        return v;
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.StartButton:

            ...

            break;
        }
    }
}
Question

Pre-Honeycomb (Android 3), each Activity was registered to handle button clicks via the onClick tag in a Layout's XML:

android:onClick="myClickMethod"

Within that method you can use view.getId() and a switch statement to do the button logic.

With the introduction of Honeycomb I'm breaking these Activities into Fragments which can be reused inside many different Activities. Most of the behavior of the buttons is Activity independent, and I would like the code to reside inside the Fragments file without using the old (pre 1.6) method of registering the OnClickListener for each button.

final Button button = (Button) findViewById(R.id.button_id);
button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        // Perform action on click
    }
});

The problem is that when my layout's are inflated it is still the hosting Activity that is receiving the button clicks, not the individual Fragments. Is there a good approach to either

  • Register the fragment to receive the button clicks?
  • Pass the click events from the Activity to the fragment they belong to?



You might want to consider using EventBus for decoupled events .. You can listen for events very easily. You can also make sure the event is being received on the ui thread (instead of calling runOnUiThread.. for yourself for every event subscription)

https://github.com/greenrobot/EventBus

from Github:

Android optimized event bus that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality




You can define a callback as an attribute of your XML layout. The article Custom XML Attributes For Your Custom Android Widgets will show you how to do it for a custom widget. Credit goes to Kevin Dion :)

I'm investigating whether I can add styleable attributes to the base Fragment class.

The basic idea is to have the same functionality that View implements when dealing with the onClick callback.




I would rather go for the click handling in code than using the onClick attribute in XML when working with fragments.

This becomes even easier when migrating your activities to fragments. You can just call the click handler (previously set to android:onClick in XML) directly from each case block.

findViewById(R.id.button_login).setOnClickListener(clickListener);
...

OnClickListener clickListener = new OnClickListener() {
    @Override
    public void onClick(final View v) {
        switch(v.getId()) {
           case R.id.button_login:
              // Which is supposed to be called automatically in your
              // activity, which has now changed to a fragment.
              onLoginClick(v);
              break;

           case R.id.button_logout:
              ...
        }
    }
}

When it comes to handling clicks in fragments, this looks simpler to me than android:onClick.




Best solution IMHO:

in fragment:

protected void addClick(int id) {
    try {
        getView().findViewById(id).setOnClickListener(this);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public void onClick(View v) {
    if (v.getId()==R.id.myButton) {
        onMyButtonClick(v);
    }
}

then in Fragment's onViewStateRestored:

addClick(R.id.myButton);



Adding to Blundell's answer,
If you have more fragments, with plenty of onClicks:

Activity:

Fragment someFragment1 = (Fragment)getFragmentManager().findFragmentByTag("someFragment1 "); 
Fragment someFragment2 = (Fragment)getFragmentManager().findFragmentByTag("someFragment2 "); 
Fragment someFragment3 = (Fragment)getFragmentManager().findFragmentByTag("someFragment3 "); 

...onCreate etc instantiating your fragments

public void myClickMethod(View v){
  if (someFragment1.isVisible()) {
       someFragment1.myClickMethod(v);
  }else if(someFragment2.isVisible()){
       someFragment2.myClickMethod(v);
  }else if(someFragment3.isVisible()){
       someFragment3.myClickMethod(v); 
  }

} 

In Your Fragment:

  public void myClickMethod(View v){
     switch(v.getid()){
       // Just like you were doing
     }
  } 



I'd like to add to Adjorn Linkz's answer.

If you need multiple handlers, you could just use lambda references

void onViewCreated(View view, Bundle savedInstanceState)
{
    view.setOnClickListener(this::handler);
}
void handler(View v)
{
    ...
}

The trick here is that handler method's signature matches View.OnClickListener.onClick signature. This way, you won't need the View.OnClickListener interface.

Also, you won't need any switch statements.

Sadly, this method is only limited to interfaces that require a single method, or a lambda.




I've recently solved this issue without having to add a method to the context Activity or having to implement OnClickListener. I'm not sure if it is a "valid" solution neither, but it works.

Based on: https://developer.android.com/tools/data-binding/guide.html#binding_events

It can be done with data bindings: Just add your fragment instance as a variable, then you can link any method with onClick.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.example.testapp.fragments.CustomFragment">

    <data>
        <variable name="fragment" type="com.example.testapp.fragments.CustomFragment"/>
    </data>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_place_black_24dp"
            android:onClick="@{fragment.buttonClicked}"/>
    </LinearLayout>
</layout>

And the fragment linking code would be...

public class CustomFragment extends Fragment {

    ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_person_profile, container, false);
        FragmentCustomBinding binding = DataBindingUtil.bind(view);
        binding.setFragment(this);
        return view;
    }

    ...

}



Related