2

I'm stuck on a little issue using Resource wrapping around my data, I don't know how I can use it in my databinding.

Sealed class:

sealed class Resource<out T: Any> {
    data class Success<out T: Any>(val data: T): Resource<T>()
    data class Error(val exception: Throwable): Resource<Nothing>()
    object Loading: Resource<Nothing>()
}

I've got this val product: LiveData<Resource<NetworkProductDetails>>

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="viewModel"
            type="my.package.ProductDetailsViewModel" />
    </data>

    <TextView
        android:id="@+id/product_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{viewModel.product.productName}"
        android:textAppearance="?attr/textAppearanceBody1"
        android:gravity="center"/>

    ...
</layout>

I've got an issue because viewModel.product is not a NetworkProductDetails but Resource<NetworkProductDetails> and my XML/Databinding doesn't know how to process it.


I've found a way to make work but I was wondering if there was a more elegant way.

First Solution:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="viewModel"
            type="my.package.ProductDetailsViewModel" />
        <variable
            name="product"
            type="my.package.NetworkProductDetails" />
    </data>

    <TextView
        android:id="@+id/product_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{product.productName}"
        android:textAppearance="?attr/textAppearanceBody1"
        android:gravity="center"/>

    ...
</layout>
viewModel.product.observe(viewLifecycleOwner, Observer { it ->
    when(it) {
        is Resource.Success -> {
            binding.product = it.data
        }
    }
}

Second Solution:

In the comment I made.

Biscuit
  • 4,840
  • 4
  • 26
  • 54

2 Answers2

5

I've got an issue because viewModel.product is not a NetworkProductDetails but Resource and my XML/Databinding doesn't know how to process it.

You missed the data. Even if you wound up with a Resource.Success, productName is not a property of Resource or even Resource.Success. data is a property of Resource.Success, and I assume that productName is a property of NetworkProductDetails. Your expression has no data.

You also would need to teach data binding about your other two cases (Loading, Error) and what to do in them.

In the best case scenario, you might be able to pull off a binding expression like this:

android:text='@{viewModel.product instanceof Resource.Success ? viewModel.product.data.productName : "like, whatever"}'

However:

  • I don't know if instanceof will handle the generic well

  • I don't know if you can refer to the LiveData output twice in a single expression

Alternatively, you could try creating a binding adapter for Resource<NetworkProductDetails> that handles the three cases, though I have never tried that for a type that uses generics.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • I've found another way, I've updated it in my post, could you give me your insight ? – Biscuit Jul 09 '20 at 08:40
  • 1
    @Biscuit: If you do not mind having that `toData()` function, then that would work. You might want to pull that out of the question and post it as your own answer. – CommonsWare Jul 09 '20 at 10:50
2

After looking around, there is 2 solutions I've found, the first is in my original post and the second is the one below:

Creating a function in my sealed class to expose the data if it is Success and then be able to use it in my xml

Pros:

  • No need to create a special BindingAdapter that could hide some of the logic
  • No need to add more code in every Fragment like in Solution 1

Cons:

  • I feel that it breaks the point of having a wrapper
sealed class Resource<out T: Any> {
    data class Success<out T: Any>(val data: T): Resource<T>()
    data class Error(val exception: Throwable): Resource<Nothing>()
    object Loading: Resource<Nothing>()

    fun toData(): T? = if(this is Success) this.data else null
}
<TextView
    android:id="@+id/product_name"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text='@{viewModel.product.toData().productName}'
    android:textAppearance="?attr/textAppearanceBody1"
    android:gravity="center"/>
Biscuit
  • 4,840
  • 4
  • 26
  • 54