I am working with a method which returns different types of objects, I have used the Any type return, but is there a better option to achieve this? This is my method:
override fun getNavItemById(dCSServiceContext: DCSServiceContext): Observable<Any> {
return scribeProvider.getNavItemById(dCSServiceContext).map { navItem ->
scribePresenter.presentNativeItem(navItem)
}.toObservable()
}
After I am doing a casting of the returned object, using the when operator, and I am doing something like this:
when (item) {
is NavItem -> {
if (parentItem.hasChildren) {
parentItem.items?.add(item)
recursiveItem = item
}
}
is Image -> {
if (parentItem.hasImages) {
parentItem.image = Image(item.image, item.selectedImage)
recursiveItem = parentItem
}
}
}
And my other doubt is how can I use this method and extract this type of object with another approach.
Thanks!!!
What you need is a co-product type, like the Either data type found in many popular FP libraries, for Kotlin, Arrow-kt already provide it, but the same can be done using sealed classes too.
Example (sealed class)
sealed class Result {
data class A(val value: Int) : Result()
data class B(val value: String) : Result()
}
fun intOrString(number: Int): Result =
if (number%2 == 0) Result.A(number)
else Result.B("~$number~")
fun main(args: Array<String>) {
(1..10).map(::intOrString).forEach(::println)
}
Output
B(value=~1~)
A(value=2)
B(value=~3~)
A(value=4)
B(value=~5~)
A(value=6)
B(value=~7~)
A(value=8)
B(value=~9~)
A(value=10)
Example (Either data type)
fun intOrString(number: Int): Either<Int, String> =
if (number%2 == 0) Left(number)
else Right("~$number~")
fun main(args: Array<String>) {
(1..10).map(::intOrString).forEach(::println)
}
Output
Right(b=~1~)
Left(a=2)
Right(b=~3~)
Left(a=4)
Right(b=~5~)
Left(a=6)
Right(b=~7~)
Left(a=8)
Right(b=~9~)
Left(a=10)
Kotlin introduce Sealed classes which is exactly what you need here. In your example it may look like this:
sealed class NavItem {
object Item : NavItem()
data class Image(val image: String, val selectedImage: String) : NavItem()
}
...
override fun getNavItemById(dCSServiceContext: DCSServiceContext): Observable<NavItem> { ... }
...
// I don't know what class declares getNavItemById function
navigation.getNavItemById(serviceContext).subscribe { item ->
when (item) {
is Item -> {
if (parentItem.hasChildren) {
parentItem.items?.add(item)
recursiveItem = item
}
}
is Image -> {
if (parentItem.hasImages) {
parentItem.image = Image(item.image, item.selectedImage)
recursiveItem = parentItem
}
}
}
}
Sealed classes may be used (in some degree) as Algebraic Data Types from functional programming languages. I mainly use them as enums, for model types (ChatType, ConnectionStatus), view states and network request results.
You could think about making your function generic like this:
override fun <T: Any> getNavItemById(dCSServiceContext: DCSServiceContext): Observable<T> {
return scribeProvider.getNavItemById(dCSServiceContext).map { navItem ->
scribePresenter.presentNativeItem(navItem)
}.toObservable()
}
but for your use-case it wouldn't make a difference. Using when to distinguish between different types is the correct way to go since item will be smart-casted appropriately for further usage.