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.

3.5 (2) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

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() transforms billingHelper.getSkuPrice() from a Flow to a LiveData to be observed inside the MonstersAdapter.
  • makePurchase() simply uses billingHelper.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!