Getting Started With In-App Purchases
Learn how to get started with in-app purchases and implement this library inside your next project. By Mattia Ferigutti.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Getting Started With In-App Purchases
30 mins
- Getting Started
- Service Fees
- Terminology
- Content Types
- Concepts
- Setting-Up the Google Play Console
- Setting-Up the Android Project
- Loading the APK in the Play Console
- Back to the Google Play Developer Console
- Handling Purchases
- Connecting the Billing Client
- Implementing onBillingSetupFinished
- Adding Flow
- Implementing querySkuDetailsAsync
- Implementing restorePurchases
- Implementing onSkuDetailsResponse
- Implementing launchBillingFlow
- Implementing onPurchasesUpdated
- Implementing handlePurchase
- Implement setSkuStateFromPurchase
- Getting Product Details
- Implementing BillingHelper Inside The Project
- Where to Go From Here?
Implement setSkuStateFromPurchase
When a purchase changes, you need to update its state. Inside the BillingHelper class, implement the following code:
private fun setSkuStateFromPurchase(purchase: Purchase) {
if (purchase.skus.isNullOrEmpty()) {
Log.e(TAG, "Empty list of skus")
return
}
for (sku in purchase.skus) {
val skuState = skuStateMap[sku]
if (null == skuState) {
Log.e(
TAG,
"Unknown SKU " + sku + ". Check to make " +
"sure SKU matches SKUS in the Play developer console."
)
continue
}
when (purchase.purchaseState) {
Purchase.PurchaseState.PENDING ->
skuState.tryEmit(SkuState.SKU_STATE_PENDING)
Purchase.PurchaseState.UNSPECIFIED_STATE ->
skuState.tryEmit(SkuState.SKU_STATE_UNPURCHASED)
Purchase.PurchaseState.PURCHASED -> if (purchase.isAcknowledged) {
skuState.tryEmit(SkuState.SKU_STATE_PURCHASED_AND_ACKNOWLEDGED)
} else {
skuState.tryEmit(SkuState.SKU_STATE_PURCHASED)
}
else ->
Log.e(
TAG,
"Purchase in unknown state: " + purchase.purchaseState
)
}
}
}
This function only emits the new state of purchase through a StateFlow.
Getting Product Details
Finally, you have everything set up. But you need methods to get details about the price, title, and purchase description. Write this code inside the BillingHelper class:
/**
* The title of our SKU from SkuDetails.
* @param SKU to get the title from
* @return title of the requested SKU as an observable
* */
fun getSkuTitle(sku: String): Flow<String> {
val skuDetailsFlow = skuDetailsMap[sku]!!
return skuDetailsFlow.mapNotNull { skuDetails ->
skuDetails?.title
}
}
fun getSkuPrice(sku: String): Flow<String> {
val skuDetailsFlow = skuDetailsMap[sku]!!
return skuDetailsFlow.mapNotNull { skuDetails ->
skuDetails?.price
}
}
fun getSkuDescription(sku: String): Flow<String> {
val skuDetailsFlow = skuDetailsMap[sku]!!
return skuDetailsFlow.mapNotNull { skuDetails ->
skuDetails?.description
}
}
You take the correct skuDetailsFlow
based on the given sku
. Then mapNotNull
transforms skuDetailsFlow
in a String and returns its title using skuDetails?.title
.
Implementing BillingHelper Inside The Project
Finally, BillingHelper.kt is complete! Now you can implement this class inside your project.
Open MonsterFragment.kt and replace getMonster()
with:
fun getMonster() : LiveData<Monster> {
val monsters = MonsterData.getListOfMonsters(requireContext())
// combine 3 flows inside one
val monster = combine(
billingHelper.isPurchased(MONSTER_LEVEL_2),
billingHelper.isPurchased(MONSTER_LEVEL_3),
billingHelper.isPurchased(MONSTER_LEVEL_4)
) { level2, level3, level4 ->
when {
level4 -> {
return@combine monsters[3]
}
level3 -> {
return@combine monsters[2]
}
level2 -> {
return@combine monsters[1]
}
else -> {
return@combine monsters[0]
}
}
}.asLiveData()
return monster
}
combine()
waits for all the three flows to complete and then executes the code inside. This code returns the most powerful monster you have.
Open MonsterStoreFragment.kt and define these two methods inside the class:
fun getSkuPrice(sku: String?) : LiveData<String>? {
if (null == sku) return null
return billingHelper.getSkuPrice(sku).asLiveData()
}
fun makePurchase(sku: String?) {
if (null != sku) {
billingHelper.launchBillingFlow(requireActivity(), sku)
} else {
Toast.makeText(
requireContext(),
getString(R.string.item_not_available),
Toast.LENGTH_SHORT
).show()
}
}
Here:
-
getSkuPrice()
transformsbillingHelper.getSkuPrice()
from aFlow
to aLiveData
to be observed inside theMonstersAdapter
. -
makePurchase()
simply usesbillingHelper.launchBillingFlow()
to launch a billing flow.
Now, call makePurchase()
inside onItemClickListener
of the MonstersAdapter
and pass the SKU of the purchase
.
onItemClickListener = { purchase ->
makePurchase(purchase.sku)
}
With that, you’re done!
Where to Go From Here?
While the code in this tutorial isn’t straightforward, it’s handy once you understand it. Flow
keeps you updated whenever an event happens.
You finally have a class that can handle payments, but it’s essential to implement it using the best technologies and practices. To do so, you need to know how to create good architecture. Check out this book to learn more about this topic!
If you have any questions or comments, please join the forum below!