Search code examples
androidandroid-appcompatandroid-button

Buttons onClick Force closes app on Android 4.1 device


I have an app with a simple layout with one button. I've tried implementing an onClick using AppCompatButton and AppCompatImageButton. But when I run it on a Android 4.1 device, when I click on it, my app crashes (Unfortunately, App Has Stopped).

I did the same test on a Android 5.0 device, and it worked. Then tried the same but using Button and ImageButton instead and it worked on both.

Those Buttons have no Style nor Theme were applied. Just bare Buttons with 30 x 30 dp. Here's the code:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        app:titleTextColor="@color/white" />

    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/adContainer"
        android:layout_width="match_parent"
        android:layout_height="90dp">

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btn"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:onClick="btnClick" />

    </androidx.appcompat.widget.LinearLayoutCompat>

</androidx.appcompat.widget.LinearLayoutCompat>

MainActivity.java

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Set ActionBar to Toolbar
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

    }


    public void btnClick(View v) {
        // DO SOMETHING
    }
}

For the moment I'm using ImageButton but I don't know if that will imply some compatibility issue on some Android version (I'm supporting API 16+).

Thanks.

EDIT: That's what LogCat shows when using AppCompatButton:

10-03 17:01:36.706 1313-1313/com.example.testappcompatbutton D/AndroidRuntime: Shutting down VM
10-03 17:01:36.706 1313-1313/com.example.testappcompatbutton W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0xa62ba288)

    --------- beginning of /dev/log/system
10-03 17:01:36.734 1313-1313/com.example.testappcompatbutton E/AndroidRuntime: FATAL EXCEPTION: main
    java.lang.IllegalStateException: Could not find a method btnClick(View) in the activity class androidx.appcompat.widget.TintContextWrapper for onClick handler on view class androidx.appcompat.widget.AppCompatButton with id 'btn'
        at android.view.View$1.onClick(View.java:3578)
        at android.view.View.performClick(View.java:4084)
        at android.view.View$PerformClick.run(View.java:16966)
        at android.os.Handler.handleCallback(Handler.java:615)
        at android.os.Handler.dispatchMessage(Handler.java:92)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:4745)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
        at dalvik.system.NativeStart.main(Native Method)
     Caused by: java.lang.NoSuchMethodException: btnClick [class android.view.View]
        at java.lang.Class.getConstructorOrMethod(Class.java:460)
        at java.lang.Class.getMethod(Class.java:915)
        at android.view.View$1.onClick(View.java:3571)
        at android.view.View.performClick(View.java:4084) 
        at android.view.View$PerformClick.run(View.java:16966) 
        at android.os.Handler.handleCallback(Handler.java:615) 
        at android.os.Handler.dispatchMessage(Handler.java:92) 
        at android.os.Looper.loop(Looper.java:137) 
        at android.app.ActivityThread.main(ActivityThread.java:4745) 
        at java.lang.reflect.Method.invokeNative(Native Method) 
        at java.lang.reflect.Method.invoke(Method.java:511) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553) 
        at dalvik.system.NativeStart.main(Native Method) 

Seems like it can't find the onClick method.


Solution

  • I've never actually seen this before, but it looks like a combination of two things:

    • On pre-lollipop devices (ie lower than version 5.0), AppCompatButton uses a TintContextWrapper as the context of the button view
    • The android:onClick attribute uses reflection to find the method to execute, looking inside the view's context

    Together, this means that the system is looking for btnClick() in the wrong place, and so the app crashes.

    You can solve this by setting the click listener through code rather than by using the android:onClick attribute. In your MainActivity's onCreate() method:

    View button = findViewById(R.id.btn);
    button.setOnClickListener(v -> btnClick(v));