Vender assinaturas no app com a Biblioteca Play Faturamento 5

1. Introdução

O sistema de faturamento do Google Play é um serviço que permite vender produtos e conteúdos digitais no seu app Android. É a maneira mais direta de vender produtos no app para gerar receita com ele. Este codelab mostra como usar a Biblioteca Google Play Faturamento para vender assinaturas no seu projeto de forma a abranger os mínimos detalhes ao integrar compras ao restante do app.

Ele também apresenta conceitos relacionados a assinaturas, como planos básicos, ofertas, tags e planos pré-pagos. Para saber mais sobre as assinaturas no Google Play Faturamento, confira nossa Central de Ajuda.

O que você vai criar

Neste codelab, você vai adicionar a Biblioteca Faturamento mais recente (versão 5.0.0) a um app simples de perfil baseado em assinatura. O app já foi criado para você, então você vai adicionar apenas a parte do faturamento. Conforme mostrado na Figura 1, no app, o usuário assina qualquer um dos planos básicos e/ou ofertas oferecidos por dois produtos por assinatura renováveis (básico e premium) ou um pré-pago não renovável. Isso é tudo. Os planos básicos são, respectivamente, assinaturas mensais e anuais. O usuário pode fazer upgrade, downgrade ou converter uma assinatura pré-paga em uma assinatura renovável.

d7dba51f800a6cc4.png 2220c15b849d2ead.png

Para incorporar a Biblioteca Google Play Faturamento ao seu app, crie o seguinte:

  • BillingClientWrapper: um wrapper para a biblioteca BillingClient. O objetivo é encapsular as interações com o BillingClient da Biblioteca Play Faturamento, mas não é necessário na sua integração.
  • SubscriptionDataRepository: um repositório de faturamento do app com uma lista do inventário de produtos por assinatura do app (ou seja, o que está à venda) e uma lista de variáveis do ShareFlow que ajudam a coletar o estado das compras e os detalhes do produto.
  • MainViewModel: um ViewModel pelo qual o restante do app se comunica com o repositório de faturamento. Ele ajuda a iniciar o fluxo de faturamento na interface usando vários métodos de compra.

Quando terminar, a arquitetura do app vai ficar parecida com a figura abaixo:

c83bc759f32b0a63.png

O que você vai aprender

  • Como integrar a Biblioteca Play Faturamento
  • Como criar produtos por assinatura, planos básicos, ofertas e tags no Play Console.
  • Como recuperar planos básicos e ofertas disponíveis no app.
  • Como iniciar o fluxo de faturamento com os parâmetros apropriados
  • Como oferecer produtos por assinatura pré-pagos

Este codelab é focado no Google Play Faturamento. Conceitos e blocos de códigos sem relevância não serão abordados. Eles são incluídos somente para você copiar e colar.

O que é necessário

  • Uma versão recente do Android Studio (>= Arctic Fox | 2020.3.1)
  • Dispositivo com a versão 8.0 ou mais recente do Android
  • O exemplo de código, fornecido para você no GitHub (instruções em seções posteriores)
  • Conhecimento moderado sobre desenvolvimento no Android Studio.
  • Saber publicar um app na Google Play Store.
  • Experiência moderada com programação Kotlin
  • Biblioteca Google Play Faturamento versão 5.0.0

2. Etapas da configuração

Buscar o código do GitHub

Colocamos tudo o que você precisa para este projeto em um repositório Git. Para começar, abra o código no seu ambiente de desenvolvimento favorito. Neste codelab, recomendamos o uso do Android Studio.

O código inicial está armazenado em um repositório do GitHub. Clone o repositório com o seguinte comando:

git clone https://github.com/android/play-billing-samples.git
cd PlayBillingCodelab

3. O alicerce

Qual é nosso ponto de partida?

Nosso ponto de partida é um app básico de perfil de usuário projetado para este codelab. O código foi simplificado para mostrar os conceitos que queremos ilustrar e não foi projetado para uso em produção. Se você decidir reutilizar qualquer parte desse código em um app de produção, siga as práticas recomendadas e teste todo o código.

Importar o projeto para o Android Studio

O PlayBillingCodelab é o app de base que não contém uma implementação do Google Play Faturamento. Inicie o Android Studio e importe o codelab de faturamento escolhendo Open > billing/PlayBillingCodelab.

O projeto tem dois módulos:

  • start tem o esqueleto do app, mas não tem as dependências necessárias nem todos os métodos necessários para implementar.
  • finished tem o projeto concluído e pode servir como um guia quando você estiver travado.

O app consiste em oito arquivos de classe: BillingClientWrapper, SubscriptionDataRepository, Composables, MainState, MainViewModel, MainViewModelFactory e MainActivity.

  • BillingClientWrapper é um wrapper que isola os métodos [BillingClient] do Google Play Faturamento necessários para uma implementação simples e emite respostas ao repositório de dados para processamento.
  • SubscriptionDataRepository para abstrair a fonte de dados do Google Play Faturamento (ou seja, a biblioteca de cliente de faturamento) e converter os dados de StateFlow emitidos no BillingClientWrapper em fluxos.
  • ButtonModel é uma classe de dados usada para criar botões na interface.
  • Os elementos combináveis extraem todos os métodos combináveis da interface para uma classe.
  • MainState é uma classe de dados para gerenciamento de estado.
  • O MainViewModel é usado para armazenar dados e estados relacionados ao faturamento usados na interface. Ele combina todos os fluxos de SubscriptionDataRepository em um objeto de estado.
  • MainActivity é a classe de atividade principal que carrega os elementos combináveis para a interface do usuário.
  • Constantes é o objeto que tem as constantes usadas por várias classes.

Gradle

É preciso adicionar uma dependência do Gradle para adicionar o Google Play Faturamento ao seu app. Abra o arquivo build.gradle do módulo do app e adicione o seguinte:

dependencies {
    val billing_version = "5.0.0"

    implementation("com.android.billingclient:billing:$billing_version")
}

Google Play Console

Para os fins deste codelab, você precisa criar as duas ofertas de produto por assinatura a seguir na seção de assinaturas do Google Play Console:

  • Uma assinatura básica com o ID do produto up_basic_sub

O produto precisa ter três planos básicos (dois com renovação automática e um pré-pago) com tags associadas : uma assinatura básica mensal com a tag monthlybasic, uma assinatura básica anual com a tag yearlybasic e uma assinatura pré-paga com a tag prepaidbasic.

É possível adicionar ofertas aos planos básicos. As ofertas vão herdar as tags dos planos básicos associados.

  • Uma assinatura premium com o ID do produto up_premium_sub

O produto precisa ter três planos básicos(dois com renovação automática e um pré-pago) com tags associadas: uma assinatura básica mensal com a tag monthlypremium, uma assinatura básica anual com a tag yearlypremium e uma assinatura pré-paga com a tag prepaidpremium

a9f6fd6e70e69fed.png

É possível adicionar ofertas aos planos básicos. As ofertas vão herdar as tags dos planos básicos associados.

Para informações mais detalhadas sobre como criar produtos por assinatura, planos básicos, ofertas e tags, consulte a Central de Ajuda do Google Play.

4. A configuração do cliente de faturamento

Nesta seção, você vai trabalhar na classe BillingClientWrapper.

No final, você terá tudo o que é necessário para o cliente de faturamento ser instanciado e todos os métodos relacionados.

  1. Inicializar um BillingClient

Depois de adicionar uma dependência à Biblioteca Google Play Faturamento, precisamos inicializar uma instância BillingClient.

BillingClientWrapper.kt

private val billingClient = BillingClient.newBuilder(context)
   .setListener(this)
   .enablePendingPurchases()
   .build()
  1. Estabeleça uma conexão com o Google Play

Depois de criar um BillingClient, precisamos estabelecer uma conexão com o Google Play.

Para se conectar ao Google Play, chamamos startConnection(). O processo de conexão é assíncrono, e precisamos implementar um BillingClientStateListener para receber um callback assim que a configuração do cliente for concluída e estiver pronta para fazer outras solicitações.

BillingClientWrapper.kt

fun startBillingConnection(billingConnectionState: MutableLiveData<Boolean>) {

   billingClient.startConnection(object : BillingClientStateListener {
       override fun onBillingSetupFinished(billingResult: BillingResult) {
           if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
               Log.d(TAG, "Billing response OK")
               // The BillingClient is ready. You can query purchases and product details here
               queryPurchases()
               queryProductDetails()
               billingConnectionState.postValue(true)
           } else {
               Log.e(TAG, billingResult.debugMessage)
           }
       }

       override fun onBillingServiceDisconnected() {
           Log.i(TAG, "Billing connection disconnected")
           startBillingConnection(billingConnectionState)
       }
   })
}
  1. Consultar o Google Play Faturamento para compras existentes

Depois de estabelecer uma conexão com o Google Play, estamos prontos para consultar as compras que o usuário fez anteriormente chamando queryPurchasesAsync().

BillingClientWrapper.kt

fun queryPurchases() {
   if (!billingClient.isReady) {
       Log.e(TAG, "queryPurchases: BillingClient is not ready")
   }
   // Query for existing subscription products that have been purchased.
   billingClient.queryPurchasesAsync(
       QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build()
   ) { billingResult, purchaseList ->
       if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
           if (!purchaseList.isNullOrEmpty()) {
               _purchases.value = purchaseList
           } else {
               _purchases.value = emptyList()
           }

       } else {
           Log.e(TAG, billingResult.debugMessage)
       }
   }
}
  1. Mostrar produtos disponíveis para compra

Agora podemos consultar os produtos disponíveis e mostrá-los aos usuários. Para consultar detalhes do produto por assinatura no Google Play, vamos chamar queryProductDetailsAsync(). Consultar detalhes do produto é uma etapa importante antes de mostrar os produtos aos usuários, porque retorna informações localizadas do produto.

BillingClientWrapper.kt

fun queryProductDetails() {
   val params = QueryProductDetailsParams.newBuilder()
   val productList = mutableListOf<QueryProductDetailsParams.Product>()
   for (product in LIST_OF_PRODUCTS) {

       productList.add(
           QueryProductDetailsParams.Product.newBuilder()
               .setProductId(product)
               .setProductType(BillingClient.ProductType.SUBS)
               .build()
       )

       params.setProductList(productList).let { productDetailsParams ->
           Log.i(TAG, "queryProductDetailsAsync")
           billingClient.queryProductDetailsAsync(productDetailsParams.build(), this)
       }
   }
}
  1. Definir o listener para a consulta ProductDetails

Observação: esse método emite o resultado da consulta em um Map para _productWithProductDetails.

Observe também que a consulta precisa retornar ProductDetails. Se isso não acontecer, é provável que os produtos configurados no Play Console não tenham sido ativados ou você não tenha publicado um build com a dependência do cliente de faturamento em nenhuma das faixas de lançamento.

BillingClientWrapper.kt

override fun onProductDetailsResponse(
   billingResult: BillingResult,
   productDetailsList: MutableList<ProductDetails>
) {
   val responseCode = billingResult.responseCode
   val debugMessage = billingResult.debugMessage
   when (responseCode) {
       BillingClient.BillingResponseCode.OK -> {
           var newMap = emptyMap<String, ProductDetails>()
           if (productDetailsList.isNullOrEmpty()) {
               Log.e(
                   TAG,
                   "onProductDetailsResponse: " +
                           "Found null or empty ProductDetails. " +
                           "Check to see if the Products you requested are correctly " +
                           "published in the Google Play Console."
               )
           } else {
               newMap = productDetailsList.associateBy {
                   it.productId
               }
           }
           _productWithProductDetails.value = newMap
       }
       else -> {
           Log.i(TAG, "onProductDetailsResponse: $responseCode $debugMessage")
       }
   }
}
  1. Iniciar o fluxo de compra

launchBillingFlow é o método chamado quando o usuário clica para comprar um item. Ela solicita que o Google Play inicie o fluxo de compra com o ProductDetails do produto.

BillingClientWrapper.kt

fun launchBillingFlow(activity: Activity, params: BillingFlowParams) {
   if (!billingClient.isReady) {
       Log.e(TAG, "launchBillingFlow: BillingClient is not ready")
   }
   billingClient.launchBillingFlow(activity, params)

}
  1. Definir o listener para o resultado da operação de compra

Quando o usuário sai da tela de compra do Google Play, seja tocando no botão "Comprar" para concluir a compra ou tocando no botão "Voltar" para cancelar a compra, o callback onPurchaseUpdated() envia o resultado do fluxo de compra de volta ao app. Com base na BillingResult.responseCode, é possível determinar se o usuário comprou o produto. Se for responseCode == OK, isso significa que a compra foi concluída.

onPurchaseUpdated() transmite uma lista de objetos Purchase que inclui todas as compras que o usuário fez pelo app. Entre muitos outros campos, cada objeto Purchase contém os atributos ID do produto, purchaseToken e isAcknowledged. Usando esses campos, para cada objeto Purchase, é possível determinar se é uma compra nova que precisa ser processada ou uma compra atual que não precisa de mais processamento.

Para compras de assinatura, o processamento é semelhante à confirmação da nova compra.

BillingClientWrapper.kt

override fun onPurchasesUpdated(
   billingResult: BillingResult,
   purchases: List<Purchase>?
) {
   if (billingResult.responseCode == BillingClient.BillingResponseCode.OK
       && !purchases.isNullOrEmpty()
   ) {
       // Post new purchase List to _purchases
       _purchases.value = purchases

       // Then, handle the purchases
       for (purchase in purchases) {
           acknowledgePurchases(purchase)
       }
   } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
       // Handle an error caused by a user cancelling the purchase flow.
       Log.e(TAG, "User has cancelled")
   } else {
       // Handle any other error codes.
   }
}
  1. Como processar compras (verificar e confirmar compras)

Depois que um usuário conclui uma compra, o app precisa confirmar a compra para processá-la.

Além disso, o valor de _isNewPurchaseAcknowledged é definido como verdadeiro quando a confirmação é processada.

BillingClientWrapper.kt

private fun acknowledgePurchases(purchase: Purchase?) {
   purchase?.let {
       if (!it.isAcknowledged) {
           val params = AcknowledgePurchaseParams.newBuilder()
               .setPurchaseToken(it.purchaseToken)
               .build()

           billingClient.acknowledgePurchase(
               params
           ) { billingResult ->
               if (billingResult.responseCode == BillingClient.BillingResponseCode.OK &&
                   it.purchaseState == Purchase.PurchaseState.PURCHASED
               ) {
                   _isNewPurchaseAcknowledged.value = true
               }
           }
       }
   }
}
  1. Encerrar conexão de faturamento

Por fim, quando uma atividade é destruída, é necessário encerrar a conexão com o Google Play. Portanto, endConnection() é chamado para fazer isso.

BillingClientWrapper.kt

fun terminateBillingConnection() {
   Log.i(TAG, "Terminating connection")
   billingClient.endConnection()
}

5. O SubscriptionDataRepository

Na BillingClientWrapper, as respostas de QueryPurchasesAsync e QueryProductDetails são postadas respectivamente em MutableStateFlow _purchases e _productWithProductDetails, que são expostas fora da classe com compras e productWithProductDetails.

Em SubscriptionDataRepository, as compras são processadas em três fluxos com base no produto da compra retornada: hasRenewableBasic, hasPrepaidBasic, hasRenewablePremium e hasPremiumPrepaid.

Além disso, productWithProductDetails é processado nos respectivos fluxos basicProductDetails e premiumProductDetails.

6. MainViewModel

A parte difícil terminou. Agora, você definirá o MainViewModel, que é apenas uma interface pública para seus clientes, para que eles não precisem conhecer os componentes internos do BillingClientWrapper e do SubscriptionDataRepository.

Primeiro, no MainViewModel, iniciamos a conexão de faturamento quando o viewModel é inicializado.

MainViewModel.kt

init {
   billingClient.startBillingConnection(billingConnectionState = _billingConnectionState)
}

Em seguida, os fluxos do repositório são combinados, respectivamente, em productsForSaleFlows (para produtos disponíveis) e userCurrentSubscriptionFlow (para a assinatura atual e ativa do usuário), conforme processados na classe de repositório.

A lista de compras atuais também é disponibilizada para a interface com currentPurchasesFlow.

MainViewModel.kt

val productsForSaleFlows = combine(
   repo.basicProductDetails,
   repo.premiumProductDetails
) { basicProductDetails,
   premiumProductDetails
   ->
   MainState(
       basicProductDetails = basicProductDetails,
       premiumProductDetails = premiumProductDetails
   )
}

// The userCurrentSubscriptionFlow object combines all the possible subscription flows into one
// for emission.
private val userCurrentSubscriptionFlow = combine(
   repo.hasRenewableBasic,
   repo.hasPrepaidBasic,
   repo.hasRenewablePremium,
   repo.hasPrepaidPremium
) { hasRenewableBasic,
   hasPrepaidBasic,
   hasRenewablePremium,
   hasPrepaidPremium
   ->
   MainState(
       hasRenewableBasic = hasRenewableBasic,
       hasPrepaidBasic = hasPrepaidBasic,
       hasRenewablePremium = hasRenewablePremium,
       hasPrepaidPremium = hasPrepaidPremium
   )
}

// Current purchases.
val currentPurchasesFlow = repo.purchases

O userCurrentSubscriptionFlow combinado é coletado em um bloco init e o valor é publicado em um objeto MutableLiveData chamado _destinationScreen.

init {
   viewModelScope.launch {
       userCurrentSubscriptionFlow.collectLatest { collectedSubscriptions ->
           when {
               collectedSubscriptions.hasRenewableBasic == true &&
                       collectedSubscriptions.hasRenewablePremium == false -> {
                   _destinationScreen.postValue(DestinationScreen.BASIC_RENEWABLE_PROFILE)
               }
               collectedSubscriptions.hasRenewablePremium == true &&
                       collectedSubscriptions.hasRenewableBasic == false -> {
                   _destinationScreen.postValue(DestinationScreen.PREMIUM_RENEWABLE_PROFILE)
               }
               collectedSubscriptions.hasPrepaidBasic == true &&
                       collectedSubscriptions.hasPrepaidPremium == false -> {
                   _destinationScreen.postValue(DestinationScreen.BASIC_PREPAID_PROFILE_SCREEN)
               }
               collectedSubscriptions.hasPrepaidPremium == true &&
                       collectedSubscriptions.hasPrepaidBasic == false -> {
                   _destinationScreen.postValue(
                       DestinationScreen.PREMIUM_PREPAID_PROFILE_SCREEN
                   )
               }
               else -> {
                   _destinationScreen.postValue(DestinationScreen.SUBSCRIPTIONS_OPTIONS_SCREEN)
               }
           }
       }

   }
}

A MainViewModel também adiciona alguns métodos muito úteis:

  1. Recuperação de tokens de planos básicos e de oferta

A partir da versão 5.0.0 da Biblioteca Play Faturamento, todos os produtos por assinatura podem ter vários planos básicos e ofertas, exceto os planos pré-pagos, que não podem ter ofertas.

Esse método ajuda a recuperar todas as ofertas e planos básicos para os quais um usuário está qualificado usando o novo conceito de tags, que são usadas para agrupar ofertas relacionadas.

Por exemplo, quando um usuário tenta comprar uma assinatura básica mensal, todos os planos básicos e ofertas associados a esse produto são marcados com a string monthlyBasic.

MainViewModel.kt

private fun retrieveEligibleOffers(
   offerDetails: MutableList<ProductDetails.SubscriptionOfferDetails>,
   tag: String
): List<ProductDetails.SubscriptionOfferDetails> {
   val eligibleOffers = emptyList<ProductDetails.SubscriptionOfferDetails>().toMutableList()
   offerDetails.forEach { offerDetail ->
       if (offerDetail.offerTags.contains(tag)) {
           eligibleOffers.add(offerDetail)
       }
   }

   return eligibleOffers
}
  1. Cálculo da oferta com o menor preço

Quando um usuário se qualifica para várias ofertas, o método leastPricedOfferToken() é usado para calcular a oferta mais baixa entre as retornadas por retrieveEligibleOffers().

O método retorna o token de ID da oferta selecionada.

Essa implementação simplesmente retorna as ofertas com o menor preço em termos do conjunto de pricingPhases e não considera as médias.

Outra implementação pode ser analisar a oferta com preço médio mais baixo.

MainViewModel.kt

private fun leastPricedOfferToken(
   offerDetails: List<ProductDetails.SubscriptionOfferDetails>
): String {
   var offerToken = String()
   var leastPricedOffer: ProductDetails.SubscriptionOfferDetails
   var lowestPrice = Int.MAX_VALUE

   if (!offerDetails.isNullOrEmpty()) {
       for (offer in offerDetails) {
           for (price in offer.pricingPhases.pricingPhaseList) {
               if (price.priceAmountMicros < lowestPrice) {
                   lowestPrice = price.priceAmountMicros.toInt()
                   leastPricedOffer = offer
                   offerToken = leastPricedOffer.offerToken
               }
           }
       }
   }
   return offerToken
}
  1. Criadores de BillingFlowParams

Para iniciar o fluxo de compra de um produto específico, O ProductDetails e o token da oferta selecionada precisam ser definidos e usados para criar um BilingFlowParams.

Há dois métodos para ajudar com isso:

upDowngradeBillingFlowParamsBuilder() cria os parâmetros para upgrades e downgrades.

MainViewModel.kt

private fun upDowngradeBillingFlowParamsBuilder(
   productDetails: ProductDetails,
   offerToken: String,
   oldToken: String
): BillingFlowParams {
   return BillingFlowParams.newBuilder().setProductDetailsParamsList(
       listOf(
           BillingFlowParams.ProductDetailsParams.newBuilder()
               .setProductDetails(productDetails)
               .setOfferToken(offerToken)
               .build()
       )
   ).setSubscriptionUpdateParams(
       BillingFlowParams.SubscriptionUpdateParams.newBuilder()
           .setOldPurchaseToken(oldToken)
           .setReplaceProrationMode(
               BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
           )
           .build()
   ).build()
}

billingFlowParamsBuilder() cria os parâmetros para compras normais.

MainViewModel.kt

private fun billingFlowParamsBuilder(
   productDetails: ProductDetails,
   offerToken: String
): BillingFlowParams.Builder {
   return BillingFlowParams.newBuilder().setProductDetailsParamsList(
       listOf(
           BillingFlowParams.ProductDetailsParams.newBuilder()
               .setProductDetails(productDetails)
               .setOfferToken(offerToken)
               .build()
       )
   )
}
  1. Método de compra

O método de compra usa o launchBillingFlow() do BillingClientWrapper e o BillingFlowParams para iniciar as compras.

MainViewModel.kt

fun buy(
   productDetails: ProductDetails,
   currentPurchases: List<Purchase>?,
   activity: Activity,
   tag: String
) {
   val offers =
       productDetails.subscriptionOfferDetails?.let {
           retrieveEligibleOffers(
               offerDetails = it,
               tag = tag.lowercase()
           )
       }
   val offerToken = offers?.let { leastPricedOfferToken(it) }
   val oldPurchaseToken: String

   // Get current purchase. In this app, a user can only have one current purchase at
   // any given time.
   if (!currentPurchases.isNullOrEmpty() &&
       currentPurchases.size == MAX_CURRENT_PURCHASES_ALLOWED
   ) {
       // This either an upgrade, downgrade, or conversion purchase.
       val currentPurchase = currentPurchases.first()

       // Get the token from current purchase.
       oldPurchaseToken = currentPurchase.purchaseToken

       val billingParams = offerToken?.let {
           upDowngradeBillingFlowParamsBuilder(
               productDetails = productDetails,
               offerToken = it,
               oldToken = oldPurchaseToken
           )
       }

       if (billingParams != null) {
           billingClient.launchBillingFlow(
               activity,
               billingParams
           )
       }
   } else if (currentPurchases == null) {
       // This is a normal purchase.
       val billingParams = offerToken?.let {
           billingFlowParamsBuilder(
               productDetails = productDetails,
               offerToken = it
           )
       }

       if (billingParams != null) {
           billingClient.launchBillingFlow(
               activity,
               billingParams.build()
           )
       }
   } else if (!currentPurchases.isNullOrEmpty() &&
       currentPurchases.size > MAX_CURRENT_PURCHASES_ALLOWED
   ) {
       // The developer has allowed users  to have more than 1 purchase, so they need to
       /// implement a logic to find which one to use.
       Log.d(TAG, "User has more than 1 current purchase.")
   }
}

d726a27add092140.png

  1. Encerrar a conexão de faturamento

Por fim, o método terminateBillingConnection do BillingClientWrapper é chamado no onCleared() de um ViewModel.

Isso encerra a conexão de faturamento atual quando a atividade associada é destruída.

7. A interface

Agora, é hora de usar tudo o que você criou na interface. Para ajudar com isso, você vai trabalhar com as classes combináveis e MainActivity.

Composables.kt

A classe de elementos combináveis é totalmente fornecida e define todas as funções do Compose usadas para renderizar a interface e o mecanismo de navegação entre elas.

A função Subscriptions mostra dois botões: Basic Subscription e Premium Subscription.

Basic Subscription e Premium Subscription carregam novos métodos do Compose que mostram os três planos básicos: mensal, anual e pré-pago.

Existem três funções de composição de perfil possíveis, cada uma para uma assinatura específica que um usuário possa ter: um perfil básico renovável, um Premium renovável e um perfil pré-pago básico ou pré-pago Premium.

  • Quando um usuário tem uma assinatura Basic, o perfil básico permite que ele faça upgrade para uma assinatura premium mensal, anual ou pré-paga.
  • Por outro lado, quando um usuário tem uma assinatura premium, ele pode fazer downgrade para uma assinatura mensal, anual ou básica pré-paga.
  • Quando um usuário tem uma assinatura pré-paga, ele pode recarregar a assinatura com o botão de recarga ou converter a assinatura pré-paga em um plano básico com renovação automática correspondente.

Por fim, a função da tela de carregamento é usada quando a conexão com o Google Play é feita e um perfil de usuário é renderizado.

MainActivity.kt

  1. Quando a MainActivity é criada, o viewModel é instanciado, e uma função do Compose chamada MainNavHost é carregada.
  2. MainNavHost começa com uma variável isBillingConnected criada a partir do Livedata billingConnectionSate do viewModel e observada para alterações porque, quando o vieModel é instanciado, ele transmite billingConnectionSate para o método startBillingConnection do BillingClientWrapper.

isBillingConnected é definido como verdadeiro quando a conexão é estabelecida e falso quando não é.

Quando "false", a função de composição LoadingScreen() é carregada. Quando verdadeira, as funções Subscription ou de perfil são carregadas.

val isBillingConnected by viewModel.billingConnectionState.observeAsState()
  1. Quando uma conexão de cobrança é estabelecida:

O Compose navController é instanciado.

val navController = rememberNavController()

Em seguida, os fluxos em MainViewModel são coletados.

val productsForSale by viewModel.productsForSaleFlows.collectAsState(
   initial = MainState()
)

val currentPurchases by viewModel.currentPurchasesFlow.collectAsState(
   initial = listOf()
)

Por fim, a variável LiveData destinationScreen do viewModel é observada.

Com base no status atual da assinatura do usuário, a função de escrita correspondente é renderizada.

val screen by viewModel.destinationScreen.observeAsState()
when (screen) {
   // User has a Basic Prepaid subscription
   // the corresponding profile is loaded.
   MainViewModel.DestinationScreen.BASIC_PREPAID_PROFILE_SCREEN -> {
       UserProfile(
           buttonModels =
           listOf(
               ButtonModel(R.string.topup_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = null,
                           tag = PREPAID_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.convert_to_basic_monthly_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = MONTHLY_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.convert_to_basic_yearly_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = YEARLY_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
           ),
           tag = PREPAID_BASIC_PLANS_TAG,
           profileTextStringResource = null
       )
   }
   // User has a renewable basic subscription
   // the corresponding profile is loaded.
   MainViewModel.DestinationScreen.BASIC_RENEWABLE_PROFILE -> {
       UserProfile(
           buttonModels =
           listOf(
               ButtonModel(R.string.monthly_premium_upgrade_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = MONTHLY_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.yearly_premium_upgrade_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = YEARLY_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.prepaid_premium_upgrade_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = PREPAID_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               }
           ),
           tag = null,
           profileTextStringResource = R.string.basic_sub_message
       )
   }
   // User has a prepaid Premium subscription
   // the corresponding profile is loaded.
   MainViewModel.DestinationScreen.PREMIUM_PREPAID_PROFILE_SCREEN -> {
       UserProfile(
           buttonModels =
           listOf(
               ButtonModel(R.string.topup_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = null,
                           tag = PREPAID_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.convert_to_premium_monthly_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = MONTHLY_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.convert_to_premium_yearly_message) {
                   productsForSale.premiumProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = YEARLY_PREMIUM_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
           ),
           tag = PREPAID_PREMIUM_PLANS_TAG,
           profileTextStringResource = null
       )
   }
   // User has a renewable Premium subscription
   // the corresponding profile is loaded.
   MainViewModel.DestinationScreen.PREMIUM_RENEWABLE_PROFILE -> {
       UserProfile(
           listOf(
               ButtonModel(R.string.monthly_basic_downgrade_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = MONTHLY_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.yearly_basic_downgrade_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = YEARLY_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               },
               ButtonModel(R.string.prepaid_basic_downgrade_message) {
                   productsForSale.basicProductDetails?.let {
                       viewModel.buy(
                           productDetails = it,
                           currentPurchases = currentPurchases,
                           tag = PREPAID_BASIC_PLANS_TAG,
                           activity = activity
                       )
                   }
               }
           ),
           tag = null,
           profileTextStringResource = R.string.premium_sub_message
       )
   }
   // User has no current subscription - the subscription composable
   // is loaded.
   MainViewModel.DestinationScreen.SUBSCRIPTIONS_OPTIONS_SCREEN -> {
       SubscriptionNavigationComponent(
           productsForSale = productsForSale,
           navController = navController,
           viewModel = viewModel
       )
   }
}

8. Código da solução

O código completo da solução pode ser encontrado no módulo de solução.

9. Parabéns

Parabéns! Você integrou os produtos por assinatura da Biblioteca Google Play Faturamento 5.0.0 a um app muito simples.

Para acessar uma versão documentada de um app mais sofisticado com uma configuração de servidor seguro, consulte o exemplo oficial (link em inglês).

Leia mais