Search code examples
kotlingenericsdagger-2

Can I omit type in generics? - Kotlin


If I have a following interface:

interface BaseDataRemote<T, in Params> {
    fun getData(params: Params? = null): Single<T>
}

Would it be possible have implementation of this interface that does not take Params? To have effectively something like:

interface BaseDataRemote<T> {
    fun getData(): Single<T>
}

Implementation is as follows:

class RemoteSellerDataSource @Inject constructor(
    private val sellerApi: SellerApi,
    @Named("LANG") private val lang: String
) : BaseDataRemote<SellerEntity, Nothing> {
    override fun getData(params: Nothing?): Single<SellerEntity> {
        return sellerApi.getSeller(lang).map { it.fromApiEntity() }
    }
}

I use Dagger 2 to module to bind this implementation:

@Module
internal interface RemoteModule {

    @Binds
    @CoreScope
    fun bindsSellerRemote(remoteSellerDataSource: RemoteSellerDataSource): BaseDataRemote<SellerEntity, Nothing>
}

I tried using Nothing as second type parameter, but it does not seem to work (I'm getting required: class or interface without bounds error Full error message:

RemoteSellerDataSource.java:6: error: unexpected type
public final class RemoteSellerDataSource implements com.bigchangedev.stamps.business.sdk.data.base.data.BaseDataRemote<SellerEntity, ?> {
                                                                                                                       ^
  required: class or interface without bounds
  found:?

Thanks.


Solution

  • EDIT: the original answer was a pure Kotlin answer because the OP didn't mention Dagger.

    Using Nothing is correct and works in pure Kotlin. However, Dagger seems to convert your code to Java, and in doing so it uses wildcards for the generics (which it doesn't like because it wants exact type matches). To avoid this issue, you can try using @JvmSuppressWildcards on your generic type parameters:

    class RemoteSellerDataSource @Inject constructor(
        private val sellerApi: SellerApi,
        @Named("LANG") private val lang: String
    ) : BaseDataRemote<SellerEntity, @JvmSuppressWildcards Nothing> {
        override fun getData(params: Nothing?): Single<SellerEntity> {
            return sellerApi.getSeller(lang).map { it.fromApiEntity() }
        }
    }
    

    Although I'm not sure what will happen in Java with Nothing in that case. I guess this should have the same effect on the Java code as removing the in variance for the second type param in the interface declaration, but without weakening your Kotlin types.

    Another workaround would be to use Unit instead of Nothing, which Dagger will most likely convert to Void in this case. This is not great for your types, though.


    Original answer:

    You can technically already call getData() without arguments thanks to the default value. An implementation that doesn't care about the params argument can simply expect null all the time.

    The Kotlin type that only contains null and no other value is technically Nothing?, and since getData is defined with Params? (note the ?) as input, it should be correct to specify Nothing (even without ?) as second type argument. So you should be able to define an implementation like this:

    interface BaseDataRemote<T, in Params> {
        fun getData(params: Params? = null): Single<T>
    }
    
    class ImplementationWithoutParams<T> : BaseDataRemote<T, Nothing> {
        override fun getData(params: Nothing?): Single<T> {
            // params will always be null here
        }
    }
    

    To avoid confusion for the users, this implementation may additionally provide a getData() method without arguments at all:

    class ImplementationWithoutParams<T> : BaseDataRemote<T, Nothing> {
        override fun getData(params: Nothing?): Single<T> = getData()
    
        fun getData(): Single<T> {
            TODO("implementation")
        }
    }