Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resource wrapper sealed class, error with databinding

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.

like image 376
Biscuit Avatar asked Apr 24 '26 14:04

Biscuit


2 Answers

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.

like image 73
CommonsWare Avatar answered Apr 27 '26 04:04

CommonsWare


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"/>
like image 43
Biscuit Avatar answered Apr 27 '26 03:04

Biscuit



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!