javaandroidandroid-fragments

Binding for android:onClick not being recognized inside a Fragment


In my Fragment layout I bind the Fragment Class:

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/state"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FragmentSwitch">

At some point in the Fragment I have an image with a android:onClick="toggleSwitch" now I see a warning informing me the method is missing from FragmentSwitch but it is not. In my Fragment Class I have it defined with the proper signature public void toggleSwitch(View view) { If I try to tap on the ImageView the app crashes.

it seems Android Studio is looking for void with the same signature, only inside Activities and not inside the Fragment. however if inside my:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {

I add the following:

ImageView ivSwitch = view.findViewById(R.id.ivSwitch);
ivSwitch.setOnClickListener(this::toggleSwitch);

In this case the app is not crashing and indee the method is called correctly. the fragment is correctly loaded into ActivityMain class but for some reason I cannot define methods inside the Fragment XML like I can do with activities.


Solution

  • Its because android:onClick works only for Activity methods, not for Fragment methods - as you have already discovered. This is because onClick is looking for a method in the current Context, and the Context is the Activity hosting the Fragment, not the Fragment itself. Fragments do not create Contexts - thay attach to Activity which create Context.

    https://developer.android.com/reference/android/view/View#attr_android:onClick

    Name of the method in this View's context to invoke when the view is clicked. This name must correspond to a public method that takes exactly one parameter of type View. For instance, if you specify android:onClick="sayHello", you must declare a public void sayHello(View v) method of your context (typically, your Activity).

    To handle click events in a Fragment, you should set the click listener programmatically in your Java or Kotlin code, as you did in your onViewCreated method.

    [edit]

    Actually you can acomplish what you want with data binding:

    enable databinding in build gradle:

    android {
        buildFeatures {
            dataBinding = true
        }
    }
    

    Use databinding in you fragment layout:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data>
            <variable
                name="fragment"
                type="biz.progmar.myapplication4.MyFragment" />
        </data>
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Button"
                android:layout_gravity="top|start"
                android:onClick="@{() -> fragment.onClick()}" />
        </FrameLayout>
    </layout>
    

    define fragment and load its layout with databinding:

    class MyFragment : Fragment() {
        private lateinit var binding: FragmentMyBinding
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            binding = DataBindingUtil.inflate(inflater, R.layout.fragment_my, container, false)
            binding.fragment = this // here you provide this fragment to data field in layout file
            return binding.root
        }
    
        fun onClick() {
            Toast.makeText(requireContext(), "Button clicked", Toast.LENGTH_SHORT).show()
        }
    }
    
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            if (savedInstanceState == null) {
                val myFragment = MyFragment()
                supportFragmentManager.beginTransaction()
                    .replace(R.id.fragment_container, myFragment)
                    .commit()
            }
        }
    }