NullPointerException on Meizu devices in Editor.updateCursorPositionMz

Update (Aug. 8, 2019)

As @andreas-wenger, @waseefakhtar and @vadim-kotov mentioned, the fix is now included from com.google.android.material:material:1.1.0-alpha08 onwards.

Old answer

Finally I had the chance to put my hands on a Meizu. As I thought, the crash occurs every time the user clicks on a field to get the focus.

In my case, I had some android.support.design.widget.TextInputEditText inside TextInputLayouts. Simply replacing these TextInputEditTexts with AppCompatEditTexts fixed the problem, like so:

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="...">

    <android.support.v7.widget.AppCompatEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</android.support.design.widget.TextInputLayout>

The behavior remains the same (since TextInputEditText extends AppCompatEditText). I still haven't found the root cause of the problem though.


This was just fixed in the material components for Android lib, see: https://github.com/material-components/material-components-android/pull/358


I based my solution on the FixedTextInputEditText as mentioned in https://github.com/android-in-china/Compatibility/issues/11#issuecomment-427560370.

First off all I created a fixed TextInputEditText instance:

public class MeizuTextInputEditText extends TextInputEditText {
    public MeizuTextInputEditText(Context context) {
        super(context);
    }

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

    public MeizuTextInputEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public CharSequence getHint() {
        try {
            return getMeizuHintHack();
        } catch (Exception e) {
            return super.getHint();
        }
    }

    private CharSequence getMeizuHintHack() throws NoSuchFieldException, IllegalAccessException {
        Field textView = TextView.class.getDeclaredField("mHint");
        textView.setAccessible(true);
        return (CharSequence) textView.get(this);
    }
}

But then I would have to replace all of my TextInputEditText usages with MeizuTextInputEditText which is not something you can easily do on a bigger codebase. Also when creating future views you always need to consider using the MeizuTextInputEditText instead of the 'broken' one. Forgetting about it would easily introduce production issues again.

So the final fix consists of the custom view class and together with the ViewPump library (https://github.com/InflationX/ViewPump) we can easily do that. Just as explained in the docs you need to register a custom intercepter that looks like this one:

public class TextInputEditTextInterceptor implements Interceptor {
    @Override
    public InflateResult intercept(Chain chain) {
        InflateRequest request = chain.request();
        View view = inflateView(request.name(), request.context(), request.attrs());

        if (view != null) {
            return InflateResult.builder()
                    .view(view)
                    .name(view.getClass().getName())
                    .context(request.context())
                    .attrs(request.attrs())
                    .build();
        } else {
            return chain.proceed(request);
        }
    }

    @Nullable
    private View inflateView(String name, Context context, AttributeSet attrs) {
        if (name.endsWith("TextInputEditText")) {
            return new MeizuTextInputEditText(context, attrs);
        }
        return null;
    }
}

And registering that custom interceptor is done just as in the docs by setting up a ViewPump on the onCreate of your activity:

@Override
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ViewPump.Builder viewPumpBuilder = ViewPump.builder();
    if (isMeizuDevice()) {
        viewPumpBuilder.addInterceptor(new TextInputEditTextInterceptor());
    }
    ViewPump.init(viewPumpBuilder.build());
}

As you can see I only inflate the MeizuTextInputEditText if a Meizu device is detected. That way the reflection is not triggered for devices that do not need it. Also this method is a base Activity class that I have from which every other activity extends in my project so every activity that is started in my project AND where the device is a Meizu will have the fix automatically!