material-components / material-components-android

Modular and customizable Material Design UI components for Android
Apache License 2.0
16.23k stars 3.05k forks source link

[Carousel] NullPointerException in CarouselLayoutManager.java when TalkBack is On #3372

Closed bartzwk closed 1 year ago

bartzwk commented 1 year ago

Description: When using TalkBack accesibility option and Carousel adapter has no items an error occurs. It is due keylineStateList in CarouselLayoutManager is uninitialized. When TalkBack is off there is no problem.

Stacktrace: FATAL EXCEPTION: main Process: io.material.catalog, PID: 30650 java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.android.material.carousel.KeylineState com.google.android.material.carousel.KeylineStateList.getDefaultState()' on a null object reference at com.google.android.material.carousel.CarouselLayoutManager.computeHorizontalScrollExtent(CarouselLayoutManager.java:1024) at androidx.recyclerview.widget.RecyclerView.computeHorizontalScrollExtent(RecyclerView.java:2158) at android.view.View.canScrollHorizontally(View.java:21461) at androidx.recyclerview.widget.RecyclerView$LayoutManager.onInitializeAccessibilityNodeInfo(RecyclerView.java:10661) at androidx.recyclerview.widget.RecyclerView$LayoutManager.onInitializeAccessibilityNodeInfo(RecyclerView.java:10631) at androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate.onInitializeAccessibilityNodeInfo(RecyclerViewAccessibilityDelegate.java:74) at androidx.core.view.AccessibilityDelegateCompat$AccessibilityDelegateAdapter.onInitializeAccessibilityNodeInfo(AccessibilityDelegateCompat.java:91) at android.view.View.onInitializeAccessibilityNodeInfo(View.java:9095) at android.view.View.createAccessibilityNodeInfoInternal(View.java:9056) at android.view.View$AccessibilityDelegate.createAccessibilityNodeInfo(View.java:32397) at android.view.View.createAccessibilityNodeInfo(View.java:9039) at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1520) at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1543) at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1543) at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchAccessibilityNodeInfos(AccessibilityInteractionController.java:1309) at android.view.AccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdUiThread(AccessibilityInteractionController.java:416) at android.view.AccessibilityInteractionController.-$$Nest$mfindAccessibilityNodeInfoByAccessibilityIdUiThread(Unknown Source:0) at android.view.AccessibilityInteractionController$PrivateHandler.handleMessage(AccessibilityInteractionController.java:1713) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8757) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

Steps to reproduce: Activate TalkBack accesibility option In MultiBrowseDemoFragment.java change last line of onViewCreated() from: adapter.submitList(CarouselData.createItems(), updateSliderRange(positionSlider, adapter)); to: adapter.submitList(new ArrayList<>(), updateSliderRange(positionSlider, adapter)); Open that fragment inside Catalog app.

Android API version: 33

Material Library version: 1.9.0-rc01

Devices: Pixel 7 Pro, Galaxy Tab S6 Lite

hunterstich commented 1 year ago

Hey @bartzwk,

I can't reproduce this on my end. Can you check that this is still happening on 1.10.0-alpha03

hunterstich commented 1 year ago

Closing this but feel free to re-open if this is still an issue

bartzwk commented 1 year ago

Hi @hunterstich, Sorry for the delay. The problem still happens on latest version. I even changed the phone language to English to check if that was the issue but it is the same.

I currently use a workaround of setting android:importantForAccessibility (setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);) in the recyclerView. This prevent the error but is not a proper solution.

What happens is:

@Override
  public void onLayoutChildren(Recycler recycler, State state) {
    if (state.getItemCount() <= 0) {
      removeAndRecycleAllViews(recycler);
      currentFillStartPosition = 0;
      return;
    }

    // more code

    // when items == 0 keylineStateList is unitialized
    keylineStateList = KeylineStateList.from(this, isRtl ? KeylineState.reverse(keylineState) : keylineState);

    // more code

}

A possible solution might be changing:

@Override
  public int computeHorizontalScrollExtent(@NonNull State state) {
    return (int) keylineStateList.getDefaultState().getItemSize();
  }

to

@Override
  public int computeHorizontalScrollExtent(@NonNull State state) {
    return keylineStateList == null ? 0 : (int) keylineStateList.getDefaultState().getItemSize();
  }

in CarouselLayoutManager but I don't know if this would break something else.

If you need more info feel free to ask me.

chrissen0814 commented 1 year ago

@hunterstich I got the same issue too.

Stacktrace:

java.lang.NullPointerException

Attempt to invoke virtual method 'com.google.android.material.carousel.KeylineState com.google.android.material.carousel.KeylineStateList.getDefaultState()' on a null object reference

com.google.android.material.carousel.CarouselLayoutManager.computeHorizontalScrollExtent(Unknown Source:2)

androidx.recyclerview.widget.RecyclerView.computeHorizontalScrollExtent(SourceFile:17)

android.view.View.canScrollHorizontally(Unknown Source:8)

androidx.recyclerview.widget.RecyclerView$LayoutManager.onInitializeAccessibilityEvent(SourceFile:5)

androidx.recyclerview.widget.RecyclerView$LayoutManager.onInitializeAccessibilityEvent(SourceFile:1)

com.google.android.material.carousel.CarouselLayoutManager.onInitializeAccessibilityEvent(SourceFile:1)

androidx.recyclerview.widget.a0.onInitializeAccessibilityEvent(SourceFile:26)

androidx.core.view.a$a.onInitializeAccessibilityEvent(Unknown Source:2)

android.view.View.onInitializeAccessibilityEvent(Unknown Source:4)

android.view.View.sendAccessibilityEventUncheckedInternal(Unknown Source:40)

android.view.View$AccessibilityDelegate.sendAccessibilityEventUnchecked(Unknown Source:0)

androidx.core.view.a.sendAccessibilityEventUnchecked(Unknown Source:2)

androidx.core.view.a$a.sendAccessibilityEventUnchecked(Unknown Source:2)

android.view.View.sendAccessibilityEventUnchecked(Unknown Source:4)

androidx.recyclerview.widget.RecyclerView.sendAccessibilityEventUnchecked(SourceFile:8)

Android API version: 33 (Android 11 doesn't have the issue with the same app)

Material Library version: 1.9.0

Devices: XiaoMi/MIUI (Android 13)

imhappi commented 1 year ago

Thanks for the detailed explanation @bartzwk ! This should be fixed in

https://github.com/material-components/material-components-android/commit/ff528621b3b550979c64f41aaec47438de30f061