I have a view that uses a sealed class ModelProp:
@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
class QuestionCard(...) : FrameLayout(...) {
@ModelProp
fun setData(data: ObservableData<ProfileQuestion>) {
this.viewModel.data = data
}
}
Here's the sealed class, for reference. It's basically just to demonstrate the different states we could have:
sealed class ObservableData<out T> {
data class Data<out T>(val value: T) : ObservableData<T>()
object Empty : ObservableData<Nothing>()
data class Error<out T>(val throwable: Throwable?) : ObservableData<T>()
}
If I try to build my project like this, it fails because the model prop ObservableData<> does not override hash code. I didn't think I would need it since each class inside is its own data class or object, but maybe the compiler isn't smart enough to notice. So in order for it to work, I had to override them even tho I could leave them empty:
sealed class ObservableData<out T> {
data class Data<out T>(val value: T) : ObservableData<T>()
object Empty : ObservableData<Nothing>()
data class Error<out T>(val throwable: Throwable?) : ObservableData<T>()
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
override fun hashCode(): Int {
return super.hashCode()
}
}
I'm uncertain if anything needs to be changed about Epoxy here, but I wanted to verify that I do in fact have to override these methods, even if all they do is call through to super. If that's the intended behavior of the library, I can at least make a note in my codebase if I chose to do it this way. I couldn't find anything in searching the Epoxy repo though so I wanted to close the loop.
Thanks for asking, I don't think this is specifically addressed anywhere else.
Epoxy does not do anything special for Sealed classes. It's possible the processor could be updated to recognize that the parameter type is a sealed class and then verify that all subclasses implement equals/hashcode or are objects (I don't know if that sort of metadata is accessible in the annotation processor though). I would likely not implement that myself, but if someone is interested in contributing that I would review it.
Otherwise the easiest way to let Epoxy know that these objects do in fact implement equals/hashcode is to use the IgnoreRequireHashCode in the model prop options parameter. That is probably a nicer solution than implementing empty equals/hashcode
If I use IgnoreRequireHashCode does that mean they're still used, though? Because if I used DoNotHash option then it won't update when the contents are actually different. I'm not sure if these two things are equivalent, though.
If I use IgnoreRequireHashCode does that mean they're still used, though?
Yes, that's the whole point of it and the difference with DoNotHash
Hey @elihart I just tested this out and verified I got the behavior I want without needing the empty methods on the sealed class.
I personally don't have enough knowledge on the annotation processing to tackle a way around this, so I'll leave it up to you to keep this open or close it. At least we'll have something searchable if someone else hits the same question. :)
Great. I'll close this since @ IgnoreRequireHashCode is the expected solution for now
@elihart Actually @IgnoreRequireHashCode is not an optimal solution IMO, it feels more like workaround and relies on discipline to not forget to check that all classes inheriting from this sealed class have proper implementation of equals/hashCode.
The optimal solution would be to explicitly specify this contract in sealed class and let the compiler catch errors. This could be done in the following way:
sealed class Entity {
abstract override fun equals(other: Any?): Boolean
abstract override fun hashCode(): Int
data class EntityImpl1(val v: Int): Entity() // OK
class EntityImple2(val v2: Int): Entity() // Compilation error: no equals/hashCode
}
I think that this would be better as an official advice in the wiki rather than @IgnoreRequireHashCode which could lead to broken behavior (diffing?) in some cases.
Most helpful comment
Yes, that's the whole point of it and the difference with DoNotHash