mm2001
mm2001

Reputation: 6999

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!

Upvotes: 1

Views: 155

Answers (1)

Lino
Lino

Reputation: 6160

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!

Upvotes: 2

Related Questions