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.
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.
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:
BindingAdapter that could hide some of the logicFragment like in Solution 1Cons:
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"/>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With