
Integração do programa de fidelidade BonifiQ em apps: login seguro por SecureToken (sem API key privada), widget WebView atualizável, componentes UI prontos e fitness opcional.
SDK para integrar o programa de fidelidade BonifiQ em apps Android e iOS sem chamar a API diretamente.
Usa a API Widget (/pub/widget), feita para o front: autentica um consumidor por vez com um SecureToken, sem
API key privada dentro do app.
| Artefato | O que tem |
|---|---|
br.com.bonifiq:bonifiq-core |
API headless: login seguro, saldo, extrato, recompensas, cupons, cashback no checkout, regulamento. |
br.com.bonifiq:bonifiq-ui |
Componentes prontos em Compose Multiplatform: hub "Meus pontos" (o widget web da BonifiQ num WebView), cashback do checkout, chip de saldo e login por código. Inclui o core. |
br.com.bonifiq:bonifiq-fitness |
Opcional. Passos, treinos e batimentos do Health Connect (Android) e do Apple Saúde (iOS), com o fluxo de conexão pronto em Compose. Veja Fitness. |
Versão atual: 0.4.0. Targets: Android (minSdk 24), JVM, iOS (arm64 e simulador arm64). Licença: Apache 2.0.
Android e Kotlin Multiplatform, pelo Maven Central:
// build.gradle.kts (módulo KMP ou Android); o repositório mavenCentral() já vem nos projetos novos
implementation("br.com.bonifiq:bonifiq-ui:0.4.0") // ou só bonifiq-core
implementation("br.com.bonifiq:bonifiq-fitness:0.4.0") // opcional: dados de atividade físicaiOS (Swift), pelo Swift Package Manager: no Xcode, File → Add Package Dependencies com a URL
https://github.com/bonifiq/bonifiq-sdk, ou no Package.swift:
.package(url: "https://github.com/bonifiq/bonifiq-sdk", from: "0.4.0")O pacote tem dois produtos (iOS 15 ou mais novo). Adicione um deles ao app, porque os dois juntos duplicam o SDK:
BonifiQ: core e UI.BonifiQFitness: core, UI e o módulo Fitness. No Swift, import BonifiQFitness no lugar de import BonifiQ.
Exige a capability HealthKit no app; veja Fitness.val bonifiq = BonifiQ(BonifiQConfig(tenant = "SEU_TENANT")) // uma instância por app
// 1) Login seguro: depois do login do cliente na sua loja VTEX
bonifiq.auth.loginWithVtex {
// chamado agora e a cada renovação (o SecureToken vale 60 min): devolva a sessão VTEX atual
VtexSessionTokens(sessionToken, segmentToken)
}
// ...ou, sem plataforma integrada: requestEmailCode(email) + loginWithEmailCode(email, código)
// 2) Hub de fidelidade pronto (Compose)
BonifiQLoyalty(bonifiq, cartId = orderFormId) { event ->
when (event) {
is LoyaltyEvent.ApplyCoupon -> carrinho.aplicarCupom(event.couponCode)
LoyaltyEvent.LoginRequired -> abrirLogin()
LoyaltyEvent.Close -> voltar()
}
}
// 3) Cashback no checkout
CashbackCheckoutCard(bonifiq, cartId = orderFormId, cartTotal = totalEmReais, onCartChanged = { recarregarCarrinho() })
// 4) Saldo na top bar
PointsBalanceChip(bonifiq, onClick = { abrirHub() })
// No logout do app
bonifiq.auth.logout()Sem UI pronta: use o core direto e desenhe as próprias telas. Todas as funções são suspend e lançam só BonifiQException:
val cliente = bonifiq.customer.info() // saldo, "1.500 Coins", tier
val recompensas = bonifiq.rewards.list() // canUse/useReason por cliente
val cupom = bonifiq.rewards.redeem(id, cartId = orderFormId).couponCode
val extrato = bonifiq.customer.history(page = 1)
val cashback = bonifiq.checkout.cashbackOptions(orderFormId, total)
bonifiq.checkout.applyCashback(orderFormId, valor, total) // RedeemOrigin = 3 (checkout mobile)
bonifiq.checkout.refreshCashback(orderFormId) // sempre que o carrinho mudar
bonifiq.customer.summary // StateFlow com o último saldo conhecidoiOS (Swift), com o hub pronto:
let config = BonifiQConfig(tenant: "SEU_TENANT", baseUrl: BonifiQConfig.companion.DEFAULT_BASE_URL,
enableLogging: false, widgetUrl: nil)
let bonifiq = BonifiQ(config: config, httpEngine: nil)
let vc = LoyaltyViewControllerKt.LoyaltyViewController(bonifiq: bonifiq, cartId: orderFormId) { event in
if let apply = event as? LoyaltyEventApplyCoupon { cart.apply(coupon: apply.couponCode) }
}As funções do core chegam ao Swift como async throws. O erro do Kotlin vem no userInfo["KotlinException"] do NSError:
@MainActor
func carregarSaldo() async {
do {
let cliente = try await bonifiq.customer.info()
saldo.text = cliente.pointsText
} catch {
if (error as NSError).userInfo["KotlinException"] is BonifiQExceptionUnauthorized {
abrirLogin()
} else {
mostrarAviso(error.localizedDescription)
}
}
}auth.state vira Expired e a UI pede login.cartId e devolve cupons. Quem aplica o cupom e recarrega o carrinho é o app.applyCashback ou removeCashback, recarregue o carrinho.
CashbackCheckoutCard tem duas versões: amount = CashbackAmount.All (padrão) usa todo o disponível num toque; CashbackAmount.Custom deixa o cliente digitar o valor, até o disponível.BonifiQException e declaram isso em @Throws, para o erro chegar ao Swift em vez de encerrar o app. Falhas do provedor de tokens VTEX e respostas que não são JSON também viram BonifiQException.
BonifiQException.Unauthorized: sem sessão ou token recusado;Api: mensagem exibível ao cliente;Network: falha de conexão.suspend pública nova precisa de @Throws(BonifiQException::class, CancellationException::class). O ThrowsDeclarationTest falha se faltar.LocalBonifiQStrings. A cor vem do painel BonifiQ (BonifiQTheme).BonifiQLoyalty mostra o widget web da BonifiQ num WebView, com todos os recursos do widget (missões, quiz, níveis,
indicação...) e atualizações sem release do app. O app só trata os LoyaltyEvent.
ClientURL da loja (GET /pub/widget/app/config). BonifiQConfig.widgetUrl troca por
outra URL, para testes.error): o hub mostra
"Não foi possível carregar o programa de fidelidade" com Tentar novamente.WEB_MESSAGE_LISTENER): o hub avisa que o programa não
está disponível no aparelho, e o botão Fechar emite LoyaltyEvent.Close.cartId: reservado; o widget ainda não associa os resgates ao carrinho.O protocolo entre o app e a página, para quem integra sem o SDK (React Native, Flutter), está em docs/webview-bridge.md.
Desafios com Strava pedem que o cliente conecte a conta dele. No widget web, o SDK abre esse login fora do WebView: o app do Strava, se estiver instalado, ou a tela de login do sistema por cima do app. Depois, o SDK devolve o resultado ao widget.
Android: não precisa de nada. O manifest do SDK declara o retorno bq.<applicationId>:// (e a permissão
INTERNET).
iOS: funciona sem configuração, com a tela de login do sistema (ASWebAuthenticationSession). Para abrir o
app do Strava, registre o esquema bq.<bundle id em minúsculas> e repasse as URLs ao SDK:
<!-- Info.plist -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array><string>bq.com.sualoja.app</string></array>
</dict>
</array>// SwiftUI (no UIKit: application(_:open:options:) ou scene(_:openURLContexts:))
.onOpenURL { url in _ = BonifiQAuthRedirect.shared.handle(url: url.absoluteString) }Um applicationId ou bundle id com _ não vira esquema de URL. Nesse caso o widget usa o navegador externo, como
antes.
Guia completo, com a cobertura por fabricante, os comportamentos de cada plataforma, a publicação nas lojas e como testar: docs/fitness.md.
O módulo bonifiq-fitness lê os dados de atividade que o aparelho já reúne. Ele só lê: nada é gravado na
plataforma de saúde nem enviado à BonifiQ.
| Plataforma | Fonte | O que chega |
|---|---|---|
| Android 9+ | Health Connect | Samsung Health, Fitbit, Mi Fitness (Xiaomi), Zepp (Amazfit), Garmin Connect, Google Fit, Strava e outros apps que sincronizam com o Health Connect |
| iOS 15+ | Apple Saúde (HealthKit) | iPhone, Apple Watch e apps conectados ao Saúde (Garmin, Strava, Zepp, Mi Fitness...) |
Huawei Health não grava no Health Connect e fica fora por enquanto. No Android em português, o Health Connect aparece como Conexão Saúde, e os textos do componente usam esse nome.
Fluxo pronto (Compose). Conectar a atividade tem mais passos do que parece: instalar o Health Connect
(Android 9 a 13), dar a permissão e, principalmente, ligar o envio de dados no app do relógio ou da pulseira.
O cliente raramente sabe fazer esse último. O FitnessConnect cuida de tudo isso e só mostra o conteúdo do app
quando há dados:
val fitness = BonifiQFitness() // uma instância por app; não precisa de Context
FitnessConnect(fitness) { dados -> // dados.steps (7 dias) e dados.workouts (30 dias)
MeuResumoDeAtividade(dados.steps, dados.workouts)
}| Situação | O que o FitnessConnect mostra |
|---|---|
| Sem plataforma de saúde (desktop, Android 8) | Aviso de indisponível |
| Android 9 a 13 sem Health Connect | Botão para instalar pela Play Store |
| Ainda não conectado | Botão Conectar, que abre o pedido do sistema |
| Recusou o pedido | Atalho para liberar no Health Connect (depois de duas recusas o Android não pergunta mais) |
| Conectado, mas sem dados | "Falta 1 passo": para cada app de saúde instalado (Samsung Health, Mi Fitness, Zepp, Garmin Connect, Fitbit...), o caminho para ligar o envio ao Health Connect e um botão que abre o app |
| Acesso parcial | Aviso acima do conteúdo, com Gerenciar permissões |
rememberFitnessConnectState(fitness), o app pode chamar refresh() (ex.: botão na top
bar) e ler refreshing.LocalFitnessStrings.Sem o componente, use a API direto:
when (fitness.availability()) {
FitnessAvailability.Available -> Unit
FitnessAvailability.NeedsInstall -> fitness.openHealthAppInstall() // Android 9 a 13: Health Connect na Play Store
FitnessAvailability.Unsupported -> return // ex.: Android 8, desktop
}
fitness.requestPermissions() // num gesto do cliente ("Conectar"); Granted, Partial ou NotGranted
val passos = fitness.dailySteps(lastDays = 7) // um item por dia, com 0 nos dias sem dado
val treinos = fitness.workouts(lastDays = 30) // do mais recente ao mais antigoO que vem em cada treino (Workout):
heartRate: média, mínima e máxima dos batimentos gravados pelo mesmo app/relógio durante o treino;hasRoute: se há trajeto de GPS;source: o app (appId, appName) e o aparelho (relógio, pulseira, celular...);recordingMethod: ActivelyRecorded (o cliente iniciou o treino), Automatic, Manual (digitado à mão) ou
Unknown.Quando a leitura volta vazia, installedHealthApps() lista os apps de saúde instalados (Android), cada um com o passo
a passo para ligar o envio ao Health Connect, e openHealthApp(app) abre o app.
Origem, GPS, batimentos e lançamento manual servem de base para validar desafios (anti-fraude).
Sobre os dados:
Passos por dia: a plataforma soma os passos sem contar em dobro o que vem de vários apps.
Sem permissão: a leitura volta vazia, com os passos em 0 e os campos do treino em null.
iOS: a Apple não informa se o cliente negou a leitura. Por isso, depois do pedido, permissionStatus() responde
Granted mesmo quando os dados vão chegar vazios.
Erros: as funções lançam só FitnessException:
Unavailable: sem plataforma de saúde;Platform: falha ou limite de consultas.Elas declaram isso em @Throws, como o core.
Visibilidade de pacotes: o manifest do módulo declara em <queries> os apps de saúde conhecidos. Sem isso, o
SDK não saberia quais estão instalados.
Manifest: o do módulo já declara as permissões de leitura (android.permission.health.READ_STEPS,
READ_EXERCISE, READ_DISTANCE, READ_ACTIVE_CALORIES_BURNED, READ_HEART_RATE) e as telas que o Health Connect
exige.
Política de privacidade: o app só precisa informar a URL dela, que o Health Connect mostra no pedido de permissão. Sem ela, o pedido não aparece:
<!-- AndroidManifest.xml do app, dentro de <application> -->
<meta-data
android:name="br.com.bonifiq.sdk.fitness.PRIVACY_POLICY_URL"
android:value="https://sualoja.com.br/privacidade" />minSdk: o módulo funciona com minSdk 24. Abaixo do Android 9, availability() responde Unsupported.
Inicialização: o contexto do app é capturado pelo androidx.startup. Se o app desliga o
InitializationProvider, availability() responde Unsupported.
Play Console: apps com permissões do Health Connect precisam preencher a declaração de apps de saúde (Conteúdo do app → Apps de saúde) e justificar cada permissão. Também precisam de política de privacidade publicada e da seção de Segurança dos dados preenchida.
Ative a capability HealthKit no target (Signing & Capabilities). Ela adiciona o entitlement
com.apple.developer.healthkit, e o App ID precisa ter HealthKit no portal da Apple.
No Info.plist, adicione os dois textos, mesmo que o app só leia:
NSHealthShareUsageDescription, com o motivo da leitura. Sem ele, o SDK lança FitnessException.Unavailable
em vez de deixar o HealthKit encerrar o app.NSHealthUpdateUsageDescription. Sem ele, a App Store Connect recusa o upload ("Missing purpose string").Apps Swift usam o produto BonifiQFitness do Swift Package Manager:
import BonifiQFitness
let fitness = BonifiQFitness()
let status = try await fitness.requestPermissions()
let passos = try await fitness.dailySteps(lastDays: 7)./gradlew :bonifiq-core:jvmTest :bonifiq-ui:jvmTest :bonifiq-fitness:jvmTest
./gradlew publishToMavenLocalbonifiq-ui/build/screenshots.BONIFIQ_SMOKE=1 (só endpoints públicos).publishToMavenLocal: publica a versão do libs.versions.toml no Maven local, sem assinar. Serve para testar o SDK num app (ex.: o app de demonstração) antes da release..github/workflows/ci.yml): em todo PR, roda os testes e a compilação Android e, num Mac, faz o link dos frameworks iOS (BonifiQ e BonifiQFitness).gradle/libs.versions.toml (bonifiq-sdk) e em BonifiQ.VERSION. O SDK segue
versionamento semântico; antes da 1.0, uma versão 0.x nova pode trazer mudanças incompatíveis.BonifiQ e BonifiQFitness, atualiza o Package.swift (versão e checksums), commita na
main e cria a tag;.xcframework.zip.Package.swift, tag e Release, e a versão já enviada segue no Central. A versão
aparece no Maven Central em alguns minutos.Credenciais (segredos do repositório): MAVEN_CENTRAL_USERNAME e MAVEN_CENTRAL_PASSWORD (token do Central Portal),
SIGNING_KEY e SIGNING_KEY_PASSWORD (chave PGP E232C526740A86B7, publicada em keyserver.ubuntu.com).
Copyright 2026 Bonifiq Desenvolvimento e Licenciamento LTDA. Licenciado sob a Apache License, Version 2.0. O uso da plataforma BonifiQ continua sujeito ao contrato e aos termos de uso de cada loja.
SDK para integrar o programa de fidelidade BonifiQ em apps Android e iOS sem chamar a API diretamente.
Usa a API Widget (/pub/widget), feita para o front: autentica um consumidor por vez com um SecureToken, sem
API key privada dentro do app.
| Artefato | O que tem |
|---|---|
br.com.bonifiq:bonifiq-core |
API headless: login seguro, saldo, extrato, recompensas, cupons, cashback no checkout, regulamento. |
br.com.bonifiq:bonifiq-ui |
Componentes prontos em Compose Multiplatform: hub "Meus pontos" (o widget web da BonifiQ num WebView), cashback do checkout, chip de saldo e login por código. Inclui o core. |
br.com.bonifiq:bonifiq-fitness |
Opcional. Passos, treinos e batimentos do Health Connect (Android) e do Apple Saúde (iOS), com o fluxo de conexão pronto em Compose. Veja Fitness. |
Versão atual: 0.4.0. Targets: Android (minSdk 24), JVM, iOS (arm64 e simulador arm64). Licença: Apache 2.0.
Android e Kotlin Multiplatform, pelo Maven Central:
// build.gradle.kts (módulo KMP ou Android); o repositório mavenCentral() já vem nos projetos novos
implementation("br.com.bonifiq:bonifiq-ui:0.4.0") // ou só bonifiq-core
implementation("br.com.bonifiq:bonifiq-fitness:0.4.0") // opcional: dados de atividade físicaiOS (Swift), pelo Swift Package Manager: no Xcode, File → Add Package Dependencies com a URL
https://github.com/bonifiq/bonifiq-sdk, ou no Package.swift:
.package(url: "https://github.com/bonifiq/bonifiq-sdk", from: "0.4.0")O pacote tem dois produtos (iOS 15 ou mais novo). Adicione um deles ao app, porque os dois juntos duplicam o SDK:
BonifiQ: core e UI.BonifiQFitness: core, UI e o módulo Fitness. No Swift, import BonifiQFitness no lugar de import BonifiQ.
Exige a capability HealthKit no app; veja Fitness.val bonifiq = BonifiQ(BonifiQConfig(tenant = "SEU_TENANT")) // uma instância por app
// 1) Login seguro: depois do login do cliente na sua loja VTEX
bonifiq.auth.loginWithVtex {
// chamado agora e a cada renovação (o SecureToken vale 60 min): devolva a sessão VTEX atual
VtexSessionTokens(sessionToken, segmentToken)
}
// ...ou, sem plataforma integrada: requestEmailCode(email) + loginWithEmailCode(email, código)
// 2) Hub de fidelidade pronto (Compose)
BonifiQLoyalty(bonifiq, cartId = orderFormId) { event ->
when (event) {
is LoyaltyEvent.ApplyCoupon -> carrinho.aplicarCupom(event.couponCode)
LoyaltyEvent.LoginRequired -> abrirLogin()
LoyaltyEvent.Close -> voltar()
}
}
// 3) Cashback no checkout
CashbackCheckoutCard(bonifiq, cartId = orderFormId, cartTotal = totalEmReais, onCartChanged = { recarregarCarrinho() })
// 4) Saldo na top bar
PointsBalanceChip(bonifiq, onClick = { abrirHub() })
// No logout do app
bonifiq.auth.logout()Sem UI pronta: use o core direto e desenhe as próprias telas. Todas as funções são suspend e lançam só BonifiQException:
val cliente = bonifiq.customer.info() // saldo, "1.500 Coins", tier
val recompensas = bonifiq.rewards.list() // canUse/useReason por cliente
val cupom = bonifiq.rewards.redeem(id, cartId = orderFormId).couponCode
val extrato = bonifiq.customer.history(page = 1)
val cashback = bonifiq.checkout.cashbackOptions(orderFormId, total)
bonifiq.checkout.applyCashback(orderFormId, valor, total) // RedeemOrigin = 3 (checkout mobile)
bonifiq.checkout.refreshCashback(orderFormId) // sempre que o carrinho mudar
bonifiq.customer.summary // StateFlow com o último saldo conhecidoiOS (Swift), com o hub pronto:
let config = BonifiQConfig(tenant: "SEU_TENANT", baseUrl: BonifiQConfig.companion.DEFAULT_BASE_URL,
enableLogging: false, widgetUrl: nil)
let bonifiq = BonifiQ(config: config, httpEngine: nil)
let vc = LoyaltyViewControllerKt.LoyaltyViewController(bonifiq: bonifiq, cartId: orderFormId) { event in
if let apply = event as? LoyaltyEventApplyCoupon { cart.apply(coupon: apply.couponCode) }
}As funções do core chegam ao Swift como async throws. O erro do Kotlin vem no userInfo["KotlinException"] do NSError:
@MainActor
func carregarSaldo() async {
do {
let cliente = try await bonifiq.customer.info()
saldo.text = cliente.pointsText
} catch {
if (error as NSError).userInfo["KotlinException"] is BonifiQExceptionUnauthorized {
abrirLogin()
} else {
mostrarAviso(error.localizedDescription)
}
}
}auth.state vira Expired e a UI pede login.cartId e devolve cupons. Quem aplica o cupom e recarrega o carrinho é o app.applyCashback ou removeCashback, recarregue o carrinho.
CashbackCheckoutCard tem duas versões: amount = CashbackAmount.All (padrão) usa todo o disponível num toque; CashbackAmount.Custom deixa o cliente digitar o valor, até o disponível.BonifiQException e declaram isso em @Throws, para o erro chegar ao Swift em vez de encerrar o app. Falhas do provedor de tokens VTEX e respostas que não são JSON também viram BonifiQException.
BonifiQException.Unauthorized: sem sessão ou token recusado;Api: mensagem exibível ao cliente;Network: falha de conexão.suspend pública nova precisa de @Throws(BonifiQException::class, CancellationException::class). O ThrowsDeclarationTest falha se faltar.LocalBonifiQStrings. A cor vem do painel BonifiQ (BonifiQTheme).BonifiQLoyalty mostra o widget web da BonifiQ num WebView, com todos os recursos do widget (missões, quiz, níveis,
indicação...) e atualizações sem release do app. O app só trata os LoyaltyEvent.
ClientURL da loja (GET /pub/widget/app/config). BonifiQConfig.widgetUrl troca por
outra URL, para testes.error): o hub mostra
"Não foi possível carregar o programa de fidelidade" com Tentar novamente.WEB_MESSAGE_LISTENER): o hub avisa que o programa não
está disponível no aparelho, e o botão Fechar emite LoyaltyEvent.Close.cartId: reservado; o widget ainda não associa os resgates ao carrinho.O protocolo entre o app e a página, para quem integra sem o SDK (React Native, Flutter), está em docs/webview-bridge.md.
Desafios com Strava pedem que o cliente conecte a conta dele. No widget web, o SDK abre esse login fora do WebView: o app do Strava, se estiver instalado, ou a tela de login do sistema por cima do app. Depois, o SDK devolve o resultado ao widget.
Android: não precisa de nada. O manifest do SDK declara o retorno bq.<applicationId>:// (e a permissão
INTERNET).
iOS: funciona sem configuração, com a tela de login do sistema (ASWebAuthenticationSession). Para abrir o
app do Strava, registre o esquema bq.<bundle id em minúsculas> e repasse as URLs ao SDK:
<!-- Info.plist -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array><string>bq.com.sualoja.app</string></array>
</dict>
</array>// SwiftUI (no UIKit: application(_:open:options:) ou scene(_:openURLContexts:))
.onOpenURL { url in _ = BonifiQAuthRedirect.shared.handle(url: url.absoluteString) }Um applicationId ou bundle id com _ não vira esquema de URL. Nesse caso o widget usa o navegador externo, como
antes.
Guia completo, com a cobertura por fabricante, os comportamentos de cada plataforma, a publicação nas lojas e como testar: docs/fitness.md.
O módulo bonifiq-fitness lê os dados de atividade que o aparelho já reúne. Ele só lê: nada é gravado na
plataforma de saúde nem enviado à BonifiQ.
| Plataforma | Fonte | O que chega |
|---|---|---|
| Android 9+ | Health Connect | Samsung Health, Fitbit, Mi Fitness (Xiaomi), Zepp (Amazfit), Garmin Connect, Google Fit, Strava e outros apps que sincronizam com o Health Connect |
| iOS 15+ | Apple Saúde (HealthKit) | iPhone, Apple Watch e apps conectados ao Saúde (Garmin, Strava, Zepp, Mi Fitness...) |
Huawei Health não grava no Health Connect e fica fora por enquanto. No Android em português, o Health Connect aparece como Conexão Saúde, e os textos do componente usam esse nome.
Fluxo pronto (Compose). Conectar a atividade tem mais passos do que parece: instalar o Health Connect
(Android 9 a 13), dar a permissão e, principalmente, ligar o envio de dados no app do relógio ou da pulseira.
O cliente raramente sabe fazer esse último. O FitnessConnect cuida de tudo isso e só mostra o conteúdo do app
quando há dados:
val fitness = BonifiQFitness() // uma instância por app; não precisa de Context
FitnessConnect(fitness) { dados -> // dados.steps (7 dias) e dados.workouts (30 dias)
MeuResumoDeAtividade(dados.steps, dados.workouts)
}| Situação | O que o FitnessConnect mostra |
|---|---|
| Sem plataforma de saúde (desktop, Android 8) | Aviso de indisponível |
| Android 9 a 13 sem Health Connect | Botão para instalar pela Play Store |
| Ainda não conectado | Botão Conectar, que abre o pedido do sistema |
| Recusou o pedido | Atalho para liberar no Health Connect (depois de duas recusas o Android não pergunta mais) |
| Conectado, mas sem dados | "Falta 1 passo": para cada app de saúde instalado (Samsung Health, Mi Fitness, Zepp, Garmin Connect, Fitbit...), o caminho para ligar o envio ao Health Connect e um botão que abre o app |
| Acesso parcial | Aviso acima do conteúdo, com Gerenciar permissões |
rememberFitnessConnectState(fitness), o app pode chamar refresh() (ex.: botão na top
bar) e ler refreshing.LocalFitnessStrings.Sem o componente, use a API direto:
when (fitness.availability()) {
FitnessAvailability.Available -> Unit
FitnessAvailability.NeedsInstall -> fitness.openHealthAppInstall() // Android 9 a 13: Health Connect na Play Store
FitnessAvailability.Unsupported -> return // ex.: Android 8, desktop
}
fitness.requestPermissions() // num gesto do cliente ("Conectar"); Granted, Partial ou NotGranted
val passos = fitness.dailySteps(lastDays = 7) // um item por dia, com 0 nos dias sem dado
val treinos = fitness.workouts(lastDays = 30) // do mais recente ao mais antigoO que vem em cada treino (Workout):
heartRate: média, mínima e máxima dos batimentos gravados pelo mesmo app/relógio durante o treino;hasRoute: se há trajeto de GPS;source: o app (appId, appName) e o aparelho (relógio, pulseira, celular...);recordingMethod: ActivelyRecorded (o cliente iniciou o treino), Automatic, Manual (digitado à mão) ou
Unknown.Quando a leitura volta vazia, installedHealthApps() lista os apps de saúde instalados (Android), cada um com o passo
a passo para ligar o envio ao Health Connect, e openHealthApp(app) abre o app.
Origem, GPS, batimentos e lançamento manual servem de base para validar desafios (anti-fraude).
Sobre os dados:
Passos por dia: a plataforma soma os passos sem contar em dobro o que vem de vários apps.
Sem permissão: a leitura volta vazia, com os passos em 0 e os campos do treino em null.
iOS: a Apple não informa se o cliente negou a leitura. Por isso, depois do pedido, permissionStatus() responde
Granted mesmo quando os dados vão chegar vazios.
Erros: as funções lançam só FitnessException:
Unavailable: sem plataforma de saúde;Platform: falha ou limite de consultas.Elas declaram isso em @Throws, como o core.
Visibilidade de pacotes: o manifest do módulo declara em <queries> os apps de saúde conhecidos. Sem isso, o
SDK não saberia quais estão instalados.
Manifest: o do módulo já declara as permissões de leitura (android.permission.health.READ_STEPS,
READ_EXERCISE, READ_DISTANCE, READ_ACTIVE_CALORIES_BURNED, READ_HEART_RATE) e as telas que o Health Connect
exige.
Política de privacidade: o app só precisa informar a URL dela, que o Health Connect mostra no pedido de permissão. Sem ela, o pedido não aparece:
<!-- AndroidManifest.xml do app, dentro de <application> -->
<meta-data
android:name="br.com.bonifiq.sdk.fitness.PRIVACY_POLICY_URL"
android:value="https://sualoja.com.br/privacidade" />minSdk: o módulo funciona com minSdk 24. Abaixo do Android 9, availability() responde Unsupported.
Inicialização: o contexto do app é capturado pelo androidx.startup. Se o app desliga o
InitializationProvider, availability() responde Unsupported.
Play Console: apps com permissões do Health Connect precisam preencher a declaração de apps de saúde (Conteúdo do app → Apps de saúde) e justificar cada permissão. Também precisam de política de privacidade publicada e da seção de Segurança dos dados preenchida.
Ative a capability HealthKit no target (Signing & Capabilities). Ela adiciona o entitlement
com.apple.developer.healthkit, e o App ID precisa ter HealthKit no portal da Apple.
No Info.plist, adicione os dois textos, mesmo que o app só leia:
NSHealthShareUsageDescription, com o motivo da leitura. Sem ele, o SDK lança FitnessException.Unavailable
em vez de deixar o HealthKit encerrar o app.NSHealthUpdateUsageDescription. Sem ele, a App Store Connect recusa o upload ("Missing purpose string").Apps Swift usam o produto BonifiQFitness do Swift Package Manager:
import BonifiQFitness
let fitness = BonifiQFitness()
let status = try await fitness.requestPermissions()
let passos = try await fitness.dailySteps(lastDays: 7)./gradlew :bonifiq-core:jvmTest :bonifiq-ui:jvmTest :bonifiq-fitness:jvmTest
./gradlew publishToMavenLocalbonifiq-ui/build/screenshots.BONIFIQ_SMOKE=1 (só endpoints públicos).publishToMavenLocal: publica a versão do libs.versions.toml no Maven local, sem assinar. Serve para testar o SDK num app (ex.: o app de demonstração) antes da release..github/workflows/ci.yml): em todo PR, roda os testes e a compilação Android e, num Mac, faz o link dos frameworks iOS (BonifiQ e BonifiQFitness).gradle/libs.versions.toml (bonifiq-sdk) e em BonifiQ.VERSION. O SDK segue
versionamento semântico; antes da 1.0, uma versão 0.x nova pode trazer mudanças incompatíveis.BonifiQ e BonifiQFitness, atualiza o Package.swift (versão e checksums), commita na
main e cria a tag;.xcframework.zip.Package.swift, tag e Release, e a versão já enviada segue no Central. A versão
aparece no Maven Central em alguns minutos.Credenciais (segredos do repositório): MAVEN_CENTRAL_USERNAME e MAVEN_CENTRAL_PASSWORD (token do Central Portal),
SIGNING_KEY e SIGNING_KEY_PASSWORD (chave PGP E232C526740A86B7, publicada em keyserver.ubuntu.com).
Copyright 2026 Bonifiq Desenvolvimento e Licenciamento LTDA. Licenciado sob a Apache License, Version 2.0. O uso da plataforma BonifiQ continua sujeito ao contrato e aos termos de uso de cada loja.