How to set click listener to this Button

2.2k Views Asked by At

I cant get this to work I want the sign out Button on this preferences screen to have a ClickListener

This is how it looks like:

enter image description here

Here´s the code and the buttonView is always NULL:

class PreferencesFragment : PreferenceFragmentCompat() {

    lateinit var activity: Context

    private var prefs: SharedPreferences = BleApplication.getInstance().getDefaultSharedPreferences()

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        activity = requireActivity()
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val buttonView = view.findViewById<View>(R.id.btn_sign_out)

        if (buttonView != null) {
            buttonView.setOnClickListener {
                Toast.makeText(getActivity(), "You clicked me.", Toast.LENGTH_SHORT).show()
            }
        }
        // Hide the divider
/*        setDivider(ColorDrawable(Color.TRANSPARENT))
        setDividerHeight(0)*/
    }

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        addPreferencesFromResource(R.xml.app_prefs)
    }
}

I also tried the kotlinx.android.synthetic but same problem there

Here´s the xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceCategory
        android:layout="@layout/pref_category_text"
        android:title="@string/pref_category_remote_battery_title">

        <SwitchPreferenceCompat
            android:key="@string/pref_key_category_remote_battery_switch"
            android:title="@string/pref_category_remote_battery_switch_title"
            android:summary="@string/pref_category_remote_battery_switch_summ"/>

    </PreferenceCategory>

    <PreferenceCategory
        android:layout="@layout/pref_category_text"
        android:title="@string/pref_category_sign_out_title">

        <Preference
            android:key="@string/pref_key_category_signed_out"
            android:widgetLayout="@layout/pref_category_sign_out_button"
            android:title="@string/pref_category_sign_out_button_title"
            android:summary="@string/pref_category_sign_out_buttom_summ"/>

    </PreferenceCategory>

</PreferenceScreen>

Here is the "@layout/pref_category_sign_out_button" layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_sign_out"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/buttonshape"
        android:text="@string/pref_category_sign_out_title" />
</LinearLayout>
3

There are 3 best solutions below

0
On BEST ANSWER

Since your Fragment extends from PreferenceFragmentCompat, you should not try to set a View.OnClickListener but override PreferenceFragmentCompat.onPreferenceTreeClick() instead. According to the documentation, this method is ...

Called when a preference in the tree rooted at this PreferenceScreen has been clicked.

Code example in Java:

@Override
onPreferenceTreeClick(Preference preference){
    if(preference.getKey().equals(getContext().getString(R.string.pref_key_category_signed_out))){
       // user clicked signout "button"
       // take appropriate actions
       // return "true" to indicate you handled the click
       return true;
    }
    return false;
}

Code example in Kotlin (I hope I can trust Android Studio :P)

override fun onPreferenceTreeClick(preferenceScreen: PreferenceScreen, preference: Preference): Boolean {
    return if (preference.key == context.getString(R.string.pref_key_category_signed_out)) {
        // user clicked signout "button"
        // take appropriate actions
        // return "true" to indicate you handled the click
        true
    } else false
}

This will enable you to catch click events for the Preference but not for the Button.

In order to do that as well, one can use a custom Preference and override onBindViewHolder(PreferenceViewHolder). Since PreferenceViewHolder - similar to RecyclerView.ViewHolder - has a field itemView which contains the inflated layout, here is a good opportunity to set our own View.OnClickListener.

SignOutPreference extends from TwoStatePreference (in the com.android.support:preference-v7 library) because replacing the CheckBox widget with your custom Button requires only to set the android:widgetLayout attribute, just like you do in your code snippet:

<PreferenceCategory
    android:layout="@layout/pref_category_text"
    android:title="@string/pref_category_sign_out_title">

    <your.package.name.SignOutPreference
        android:key="@string/pref_key_category_signed_out"
        android:widgetLayout="@layout/pref_category_sign_out_button"
        android:title="@string/pref_category_sign_out_button_title"
        android:summary="@string/pref_category_sign_out_buttom_summ"/>

</PreferenceCategory>

SignOutPreference.java

public class SignOutPreference extends TwoStatePreference {
    public SignOutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public SignOutPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SignOutPreference(Context context) {
        super(context);
    }

    @Override
    public void onBindViewHolder(final PreferenceViewHolder holder) {
        super.onBindViewHolder(holder);
        Button button = holder.itemView.findViewById(R.id.btn_sign_out);
        if(button != null){
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(holder.itemView.getContext(), "CLICKED!", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
}
0
On

I was looking for a simple answer and found a way. Just set the onClick attribute for the button in the xml file, and implement the method in the parent activity of the preference fragment. (It's important to implement it in the Activity, not in the Preference Fragment. Or else it will give you crashes)
I wanted to make my onClick method to work only when the button is touched(clicked), and not respond to clicks in the area outside of the button. So far this is the only way that works just like I wanted it to.

My code is in Kotlin, but the logic is simple so it won't be hard to write it in java.

This is my button.xml used for the preference's widgetLayout. Look at how I set the android:onClick= attribute.

<?xml version="1.0" encoding="utf-8"?>
<Button
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/btn"
    android:text="reset"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    style="@style/rippleEffect" //my custom ripple effect
    android:onClick="onClickMethod">
</Button>

Then I implemented the onClickMethod in the parent activity of the preference fragment. This callback method should be public, and have View as input parameter.
(For more info read this -> How exactly does the android:onClick XML attribute differ from setOnClickListener? )

fun onClickMethod(view: View) {
    //do something
}

Below is my preference.xml.

<Preference
    android:key="pref_key"
    android:title="Reset"
    android:summary="summary.."
    app:iconSpaceReserved="false"
    android:widgetLayout="@layout/button"/>

Also I tried to set the ClickListener programmatically, but the only way that worked without errors was when I set the button's ClickListener inside the PreferenceClickListener. This only worked half way, since I need to touch(click) the preference item first to init the button's ClickListener.

val view = findPreference<androidx.preference.Preference>("pref_key")
var isFirst = true
view?.setOnPreferenceClickListener {
    if (isFirst) {
        btn.setOnClickListener {
            Toast.makeText(requireContext(), "button clicked!", Toast.LENGTH_SHORT).show()
        }
        isFirst = false
        it.summary = "unlocked"
    } else {
        isFirst = true
        it.summary = "locked - tap to unlock"
    }
    true
}



Anyway this answer is working well for me, but I'm still looking for a way to use my preference key since this method does not fully uses the preference attribute, but just as a layout. But for now I hope this is helpful for those who want to use buttons in android preference.

0
On

As @0X0nosugar mentioned you can use the onPreferenceTreeClicked method to handle all clicks in a convenient way like this:

@Override
onPreferenceTreeClick(Preference preference){
    if ((preference.getKey().equals(getContext().getString(R.string.pref_key_category_signed_out))){
       // user clicked signout "button"
       // take appropriate actions
       // return "true" to indicate you handled the click
       return true;
    }
    return false;
}

The problem when using a custom button via widgetLayout is that when the button click is not a Preference click, so the handler doesn't catch it. One way to circumvent this problem is to just disable the built-in click for the button, like this:

<Button
    android:id="@+id/btn_sign_out"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/buttonshape"
    android:text="@string/pref_category_sign_out_title"
    android:clickable="false"/>

See the last line. This way you don't need to create an extra class just for the button and you can easily access whatever methods and variables you have in your PreferencesFragment class.

I'm sure there's a better way to somehow trigger the Preference click from the button click, but at least this works as pretty well.