0

In my fragment, I am displaying a list of items using a Custom View that contains a SwipeRefreshLayout.

When I swipe to refresh, I can see two refreshing indicators and one of them never disappears.

See it here

I obviously have only one SwipeRefreshLayout in my hierarchy and this appeared when I migrated my code to View Binding.


Here are the details of my code and of the behaviour.

I have a fragment that displays a list of items, I'm using a Custom View com.myapp.ItemsListView I built for this purpose.

<?xml version="1.0" encoding="utf-8"?>
<com.myapp.ItemsListView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/myItems"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

In order to access this custom view, I am using View Binding. In my fragment, that's what it looks like:

// ItemsFragment.kt
override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View? {
    binding = FragmentItemsBinding.inflate(inflater, container, false)
    return binding?.root
}

and I then access anything in this custom view via binding?.myItems.

And my custom view uses a SwipeRefreshLayout as its root and contains a RecyclerView:

<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/swipeToRefresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/items"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical" />

        <TextView
            android:id="@+id/missingContent"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:text="@string/missing_content_message"
            app:drawableTopCompat="@drawable/ic_missing_content_24dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

Following this question I'm using this piece of code to inflate the custom view:

// ItemsListView.kt
class ItemsListView(context: Context, attrs: AttributeSet) : SwipeRefreshLayout(context, attrs) {
  private var b: ItemsListBinding? = null

  init {
    b = ItemsListBinding.inflate(LayoutInflater.from(context), this, true)
  }
}

From the code, I customized my SwipeRefreshLayout to look different than the default:

b?.swipeToRefresh?.setSize(LARGE)
b?.swipeToRefresh?.setColorSchemeResources(
  R.color.colorPrimary,
  R.color.colorPrimaryDark
)

And I access b?.swipeToRefresh to set a listener, display or hide the progress indicator.

fun setListener(listener: OnRefreshListener) {
  b?.swipeToRefresh?.setOnRefreshListener(listener)
}
fun showProgress() {
  b?.swipeToRefresh?.isRefreshing = true
}
fun hideProgress() {
  b?.swipeToRefresh?.isRefreshing = false
}

However, as you can see on the video clip, there is a black progress indicator that sticks and never goes away. I have tried to access the SwipeRefreshLayout via b?.root and it results in the same behaviour.

In the end, I just do not understand why there are two SwipeRefreshLayout displayed on my screen. I noticed this problem when I introduced View Binding (I previously used Kotlin Synthetics). I'd like to inflate only one SwipeRefreshLayout in the hierarchy.

Vince
  • 1,570
  • 3
  • 27
  • 48
  • 1
    Which specific class does your `ItemsListView` extend? It'd probably help to post that class, or at least the declaration and initialization for it. – Mike M. Jan 17 '22 at 21:23
  • Yeah, that's why you're ending up with two `SwipeRefreshLayout`s: your `ItemsListView` is itself a `SwipeRefreshLayout`, and you're inflating another regular one into it. Since `SwipeRefreshLayout` only handles one child, all you need to do, basically, is to remove the `` from that layout. – Mike M. Jan 17 '22 at 22:58
  • @MikeM. good point. It's a `SwipeRefreshLayout`, and I wonder indeed if that's why there is this behaviour I don't understand. – Vince Jan 17 '22 at 22:59
  • How would you suggest solving this? using a `merge` tag as the root of the custom view? Just removing the `id` declaration in it? – Vince Jan 17 '22 at 23:01
  • You don't need `` tags, 'cause you're only going to have the one child, the `ConstraintLayout`. `` is for when you need multiple children in an include file without a root ``. Aside from that, everywhere that you're referring to the old `swipeToRefresh` should now be referring to `this` (if needed). As mentioned, your `ItemsListView` is itself the custom `SwipeRefreshLayout`. – Mike M. Jan 17 '22 at 23:04
  • @MikeM. Thank you! I got what you meant, I removed the `SwipeRefreshLayout` from the layout description. I'm still not completely sure I understood it all though, especially about whether it was wrong with Kotlin synthetics already. Feel free to post it as an answer if you have 5 minutes, I'll accept it. – Vince Jan 18 '22 at 03:09
  • 1
    No problem. Sorry if that comment was a bit ambiguous. Upon rereading it, I see that it's not very clear, but you got it. Yeah, it can take a minute to get your head around at first, but it'll make sense eventually. :-) It's not unlike creating a custom `Activity` class. In your `MainActivity`, for example, you don't have another `private val activity: Activity` to call `activity.setTitle()` or `activity.finish()`; you call those functions on the current `MainActivity` instance – `this.finish()`, or simply `finish()` – 'cause it is the `Activity`. The fact that layouts are involved here adds… – Mike M. Jan 18 '22 at 03:51
  • 1
    …confusion, but remember that inflating a layout always creates brand new objects. Just 'cause we're in a custom `View` doesn't mean that the root tag is now suddenly some description of our `View` instead; it's still a directive to create a brand new, plain `SwipeRefreshLayout`. I can't really know what was happening with your Synthetics setup without seeing it, but hopefully that clears up the current one a litte. Anyhoo, I don't post answers here any more, so please feel free to finish this up however you like. Thank you, though. I appreciate the offer. Glad you got it working. Cheers! – Mike M. Jan 18 '22 at 03:51

0 Answers0