Return different types of Object in kotlin

Viewed 3349

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!!!

3 Answers

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.

Related