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.
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:
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 Composables 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
É 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.
- 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()
- 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)
}
})
}
- 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)
}
}
}
- 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)
}
}
}
- 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")
}
}
}
- 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)
}
- 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.
}
}
- 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
}
}
}
}
}
- 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:
- 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
}
- 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
}
- 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()
)
)
}
- 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.")
}
}
- 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
- Quando a
MainActivity
é criada, o viewModel é instanciado, e uma função do Compose chamadaMainNavHost
é carregada. MainNavHost
começa com uma variávelisBillingConnected
criada a partir do LivedatabillingConnectionSate
do viewModel e observada para alterações porque, quando o vieModel é instanciado, ele transmitebillingConnectionSate
para o método startBillingConnection doBillingClientWrapper
.
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()
- 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).