Extends SwitchMaterial and NPE happen

264 views Asked by At

I am trying to customize SwitchMaterial class in Android, like this

class TipSwitchMaterial @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.switchStyle
) : SwitchMaterial(context, attrs, defStyleAttr) {

}

I use TipSwitchMaterial in a activity xml like this:

<ScrollView 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:isScrollContainer="true"
    tools:context=".SidActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

...

        <com.omnieyes.omnilite.view.TipSwitchMaterial
            android:id="@+id/event_record_sort_switch"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:text="@string/sort"
            android:tooltipText="@string/tooltip_sort"
            app:layout_constraintBottom_toBottomOf="@+id/predict_button"
            app:layout_constraintEnd_toStartOf="@id/predict_button"
            app:layout_constraintHorizontal_chainStyle="packed"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@+id/predict_button" />
...

However a NPE would happen right after getting into that activity:

2023-03-23 15:21:36.630 17706-17706/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.omnieyes.omnilite, PID: 17706
    java.lang.NullPointerException: Attempt to invoke interface method 'int java.lang.CharSequence.length()' on a null object reference
        at android.text.StaticLayout.<init>(StaticLayout.java:455)
        at androidx.appcompat.widget.SwitchCompat.makeLayout(SwitchCompat.java:982)
        at androidx.appcompat.widget.SwitchCompat.onMeasure(SwitchCompat.java:905)
        at android.view.View.measure(View.java:25466)
        at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:811)
        at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measure(BasicMeasure.java:466)
        at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measureChildren(BasicMeasure.java:134)
        at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:278)
        at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:120)
        at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1594)
        at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1708)
        at android.view.View.measure(View.java:25466)
        at android.widget.ScrollView.measureChildWithMargins(ScrollView.java:1412)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.widget.ScrollView.onMeasure(ScrollView.java:452)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:145)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at com.android.internal.policy.DecorView.onMeasure(DecorView.java:747)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3397)
        at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2228)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2486)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8171)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
        at android.view.Choreographer.doCallbacks(Choreographer.java:796)
        at android.view.Choreographer.doFrame(Choreographer.java:731)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

I do this because I want to apply a click-like tooltip with this post, which does perfromLongClick() in a OnClickListener. I'm trying to alter his way a little bit. I planned to do it inside its init{} block like this:

init {
        // so we can click and show its tooltip; a long-click still does
        setOnClickListener { performLongClick() }
    }

Which works with a class that extends TextView. Please help, thanks.

I have listed what I've tried up there. And I can't find any post or tutorial related that extends SwitchMaterial. Maybe there's no need to do this?

Edit: add build.gradle file

My build.gradle file would be like:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
}

android {
    compileSdk 31

    defaultConfig {
        applicationId "xxx"
        minSdk 26
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        ndk {
            abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    packagingOptions {
        exclude 'AndroidManifest.xml'
    }
}


dependencies {
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    implementation project(path: ':opencv')
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
    implementation 'com.google.code.gson:gson:2.10'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation group: 'commons-io', name: 'commons-io', version: '2.6'
    implementation group: 'org.javatuples', name: 'javatuples', version: '1.2'
    implementation('com.squareup.okhttp3:okhttp:4.9.3')
    implementation('org.apache.commons:commons-csv:1.5')
    implementation files('libs/openCVLibrary330-v1.1-release.aar')

    //firebase, choose for kotlin and java or Both.
    implementation 'com.google.firebase:firebase-crashlytics-ktx:18.3.0'
    implementation 'com.google.firebase:firebase-crashlytics'
}
2

There are 2 answers

1
Diego Ferreira On

Can you post your themes.xml file (or styles.xml)?

I had a similar issue, and after investigation, I found that my main app theme was extending Material 2 styles. My main app theme was like below:

<!-- Base application theme. -->
<style name="Theme.AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
    ...
</style>

After changing to extend Material 3 styles I was able to fix it.

<!-- Base application theme. -->
<style name="Theme.AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
    ...
</style>
0
Cheticamp On

These types of errors that occur within system/library code can be hard to track down.

I am only able to reproduce your error by either 1) forcing the defStyleAttr to zero, or, 2) forcing the textOn attribute in the layout file to null (android:textOn="@null"). This tells me that your are losing the attribute or modifying the style such that the textOn and, probably, textOff comes up null. You may be able to satisfy yourself that this is the source of the problem by setting android:textOn="On" and android:textOff="Off" in the XML layout. (This is not the fix, however.)

So, why do I say this? If we take the top program reference lines in the stack trace in reverse order, we can see the code that is involved in the crash.

    at androidx.appcompat.widget.SwitchCompat.onMeasure(SwitchCompat.java:905)
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        if (mOnLayout == null) {
            mOnLayout = makeLayout(mTextOnTransformed); // Line 905
        }
        ...
    }

    at androidx.appcompat.widget.SwitchCompat.makeLayout(SwitchCompat.java:982)
    private Layout makeLayout(CharSequence transformedText) {
        return new StaticLayout(transformedText, mTextPaint,
                transformedText != null
                        ? (int) Math.ceil(Layout.getDesiredWidth(transformedText, mTextPaint)) : 0, // Line 982
                Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
    }



    at android.text.StaticLayout.<init>(StaticLayout.java:455)
            this(source, 0, source.length(), paint, width, align, // Line 455
         spacingmult, spacingadd, includepad);

If we set a breakpoint at line 455 of StaticLayout.java, we can see that source is null and that leads directly to the crash.

java.lang.NullPointerException: Attempt to invoke interface method 'int java.lang.CharSequence.length()' on a null object reference

Tracing back to the source of the source variable, we see that is comes from mTextOnTransformed (line 905 of SwitchCompat.java.) The only place where mTextOnTransformed is set is in the following code:

    In SwtichCompat.hava:
    private void setTextOnInternal(CharSequence textOn) {
        ...
        mTextOnTransformed = doTransformForOnOffText(textOn);
        ...
    }

And textOn is the text to display when the switch is "on" and it is null.

I can't tell you specifically how to correct this error, but take a close look at everything you have that touches the switch and its attributes including styles to try to identify what is going wrong.