androidkotlingradleamazon-appstore

How to use gradle flavor to choose between two Kotlin imports (Google vs Amazon Java billing library)?


I have an existing Google Play Android app that uses the Google Billing Library and I'd like to use the recently released Amazon Appstore Billing Compatibility SDK that implements a compatible API.

Their guide suggests replacing Java/Kotlin lines as follows, and on first take it works well. Just replace:

import com.android.billingclient.api.*

with

import com.amazon.device.iap.billingclient.api.*

I already use the Gradle flavor mechanism to slightly tweak the build (e.g. different store & support URLs), so in an ideal world, I could leverage that. I.e. I have this in my app/build.gradle file:

flavorDimensions = ["appstore"]
productFlavors {
    google {
        dimension "appstore"
        buildConfigField "String", "ENCRYPTED_GOOGLE_IAP_KEY", "<removed>"
    }
    amazon {
        dimension "appstore"
        buildConfigField "String", "ENCRYPTED_GOOGLE_IAP_KEY", "ignored"
    }
}

and I've configured the dependencies in the same build.gradle so that only the appropriate library is available:

dependencies {
    ...

    googleImplementation 'com.android.billingclient:billing:6.1.0'
    amazonImplementation files('libs/appstore-billing-compatibility-4.1.0.jar')
}

So, what I'd really like to be able to do is to conditionally import the appropriate dependency, i.e. with a preprocessor, it would be something like this:

#if defined(amazon)
import com.android.billingclient.api.*
#endif
#if defined(google)
import com.amazon.device.iap.billingclient.api.*
#endif

but of course that's not a Java/Kotlin compatible approach. I could copy my existing implementation into two folders and tweak just the import line, i.e.:

src/
|-- google/
|   |-- java/
|       |-- com/
|           |-- example/
|               |-- MyPurchaseCode.kt
|-- amazon/
|   |-- java/
|       |-- com/
|           |-- example/
|               |-- MyPurchaseCode.kt

but the code is identical except for that one line, so it will be a pain to maintain (and, after all, isn't the point of the Amazon compatibility library that I can keep my existing code :) )

Any suggestions on how I might do this? I wondered about creating some sort of "wrapper" interface and then having 2 source files, located as above, that implement it. But they're big with many classes and my understanding is that I'd have to clone the declarations.

Perhaps I'm overthinking this and there's some simple answer! (I admit my Java / Kotlin knowledge has faded …)

Thanks in advance!


Solution

  • You can for instance define type aliases of the objects you're using in the main MyPurchaseCode.kt file. There will be two Kotlin files with such type alias, one for the google flavor and the second for the amazon one.

    Let's take for instance the Purchase object: the type alias definition for google should be located here google/java/com/example/ and will look something like:

    package com.example
    import com.android.billingclient.api.Purchase
    typealias Purchase = Purchase
    

    and the one for amazon should be located under amazon/java/com/example/ and look like:

    package com.example
    import com.amazon.device.iap.billingclient.api.Purchase
    
    typealias Purchase = Purchase
    

    You can now add additional typealias for all the other common objects.

    Your MyPurchaseCode.kt can be located under main/src/java/com/example.

    Hope this helps

    Response: This was great, thank you! (TIL Kotlin's typealias). The final solution has one gotcha but works well enough. Specifically, typealias doesn't (always?) allow subtypes per this report as referenced here, so for the Amazon file I end up with:

    typealias AcknowledgePurchaseParams = com.android.billingclient.api.AcknowledgePurchaseParams
    typealias AcknowledgePurchaseResponseListener = com.android.billingclient.api.AcknowledgePurchaseResponseListener
    typealias BillingClient = com.android.billingclient.api.BillingClient
    typealias BillingClientStateListener = com.android.billingclient.api.BillingClientStateListener
    typealias BillingFlowParams = com.android.billingclient.api.BillingFlowParams
    typealias BillingResult = com.android.billingclient.api.BillingResult
    typealias BillingResponseCode = com.android.billingclient.api.BillingClient.BillingResponseCode
    typealias QueryPurchasesParams = com.android.billingclient.api.QueryPurchasesParams
    typealias QueryProductDetailsParams = com.android.billingclient.api.QueryProductDetailsParams
    typealias Product = com.android.billingclient.api.QueryProductDetailsParams.Product
    typealias ProductDetails = com.android.billingclient.api.ProductDetails
    typealias ProductDetailsParams = com.android.billingclient.api.BillingFlowParams.ProductDetailsParams
    typealias ProductDetailsResponseListener = com.android.billingclient.api.ProductDetailsResponseListener
    typealias ProductType = com.android.billingclient.api.BillingClient.ProductType
    typealias Purchase = com.android.billingclient.api.Purchase
    typealias PurchasesResponseListener = com.android.billingclient.api.PurchasesResponseListener
    typealias PurchaseState = com.android.billingclient.api.Purchase.PurchaseState
    typealias PurchasesUpdatedListener = com.android.billingclient.api.PurchasesUpdatedListener
    

    and similar for Google, namely:

    typealias AcknowledgePurchaseParams = com.android.billingclient.api.AcknowledgePurchaseParams
    typealias AcknowledgePurchaseResponseListener = com.android.billingclient.api.AcknowledgePurchaseResponseListener
    typealias BillingClient = com.android.billingclient.api.BillingClient
    
    // etc
    

    The gotcha is shown here, where it is necessary to alias sub-types explicitly:

    typealias BillingClient = com.android.billingclient.api.BillingClient
    typealias BillingResponseCode = com.android.billingclient.api.BillingClient.BillingResponseCode
    
    

    This means my code in main had to change from:

    BillingClient.BillingResponseCode.OK
    

    to:

    BillingResponseCode.OK
    

    which can be a little less clear, but I can live with that!