Crie um APK com Python: Calculadora Completa + AdMob e Google Play | @CanalQb | 2026
TL;DR — Resumo Executivo
- Com Python + Buildozer você transforma um script em APK real e publica no Google Play — sem precisar saber Java ou Kotlin.
- O caminho completo exige: teste fechado com 12 pessoas por 14 dias, depois acesso à produção — e cada etapa tem regras específicas que a maioria ignora.
- Para ganhar R$ 1/dia com AdMob em banner a cada 2 min você precisa de aproximadamente 333 usuários ativos usando o app por 30 minutos cada — os cálculos detalhados estão neste post.
A maioria das calculadoras no Google Play foi feita com Java — e você vai publicar uma melhor usando Python. Não é exagero. Com Kivy e Buildozer você constrói, empacota e sobe um APK real na loja, com AdMob integrado, interface moderna e funcionalidades que apps de R$ 10 não têm. O detalhe que quase ninguém conta: o processo de publicação tem etapas obrigatórias na ordem certa — e pular qualquer uma significa rejeição automática pelo Google.
Script simples vs. Calculadora APK completa: qual é a diferença real?
Script simples (CLI)
- Roda apenas no terminal
- Sem interface gráfica
- Sem histórico persistente
- Não distribui na loja
- Não monetiza
- Sem suporte a gestos ou touch
- Código: 20–50 linhas
APK com Python (Kivy)
- Roda em qualquer Android
- Interface com botões, animações, temas
- Histórico salvo no dispositivo
- Publicável no Google Play
- AdMob integrado (banner, intersticial)
- Suporte total a touch e gestos
- Código: 300–600 linhas bem estruturadas
Aqui no @CanalQb testamos as duas abordagens lado a lado. O script CLI resolve o problema em 30 segundos de código. O APK resolve o problema e cria um produto. Para quem quer monetizar ou construir portfólio real no Android, não existe comparação.
Quais recursos têm as melhores calculadoras já feitas?
Como instalar o ambiente e criar o APK com Python?
Passo 1 — Instalar dependências no Linux (Ubuntu/WSL2)
# Atualizar pacotes do sistema
sudo apt update && sudo apt upgrade -y
# Dependências essenciais para Buildozer e Android SDK
sudo apt install -y python3-pip python3-venv git zip unzip openjdk-17-jdk \
libffi-dev libssl-dev autoconf libtool pkg-config zlib1g-dev \
libncurses5-dev libncursesw5-dev libtinfo5 cmake libstdc++6 \
libgstreamer1.0-dev gstreamer1.0-plugins-base
# Criar ambiente virtual para o projeto
python3 -m venv ~/calc_env
source ~/calc_env/bin/activate
# Instalar Kivy e Buildozer
pip install kivy buildozer cython==0.29.33
# Verificar versões instaladas
buildozer --version
python -c "import kivy; print(kivy.__version__)"
pip install cython==0.29.33 --force-reinstall
Passo 2 — Estrutura do projeto
calculadora_cqb/
├── main.py # Lógica principal do app
├── calculadora.kv # Layout visual (Kivy Language)
├── buildozer.spec # Configuração de build do APK
├── assets/
│ ├── icon.png # Ícone 512x512px
│ └── presplash.png # Tela de loading 1080x1920px
├── fonts/
│ └── Roboto-Regular.ttf
└── db/
└── historico.db # SQLite (gerado automaticamente)
Passo 3 — main.py: A calculadora completa
"""
Calculadora @CanalQb — APK Python/Kivy
Versão: 2.0 | Master Rules v8.3
"""
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.popup import Popup
from kivy.storage.jsonstore import JsonStore
from kivy.utils import get_color_from_hex
from kivy.clock import Clock
import sqlite3, math, os, json
# ── Banco de dados para histórico ──────────────────────────
DB_PATH = os.path.join(os.path.expanduser("~"), "calc_historico.db")
def init_db():
con = sqlite3.connect(DB_PATH)
con.execute("""CREATE TABLE IF NOT EXISTS historico (
id INTEGER PRIMARY KEY AUTOINCREMENT,
expressao TEXT,
resultado TEXT,
dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)""")
con.commit()
con.close()
def salvar_calculo(expressao, resultado):
con = sqlite3.connect(DB_PATH)
con.execute("INSERT INTO historico(expressao,resultado) VALUES(?,?)",
(expressao, resultado))
con.commit()
con.close()
def buscar_historico(limite=50):
con = sqlite3.connect(DB_PATH)
cur = con.execute(
"SELECT expressao, resultado, dt FROM historico ORDER BY id DESC LIMIT ?",
(limite,))
rows = cur.fetchall()
con.close()
return rows
# ── Lógica de cálculo segura ────────────────────────────────
def calcular(expressao: str) -> str:
"""Avalia expressão matemática com segurança."""
try:
expressao = expressao.replace("×","*").replace("÷","/")
expressao = expressao.replace("^","**").replace("√","math.sqrt")
# Funções científicas permitidas
safe = {
"__builtins__": {},
"math": math,
"sin": math.sin, "cos": math.cos, "tan": math.tan,
"log": math.log10, "ln": math.log,
"sqrt": math.sqrt, "pi": math.pi, "e": math.e,
"abs": abs, "round": round,
}
resultado = eval(expressao, safe)
# Formatar resultado
if isinstance(resultado, float):
if resultado == int(resultado):
return str(int(resultado))
return f"{resultado:.10g}"
return str(resultado)
except ZeroDivisionError:
return "Erro: ÷0"
except (SyntaxError, NameError, TypeError):
return "Erro"
except Exception as e:
return f"Erro: {str(e)[:30]}"
# ── Widget principal da calculadora ─────────────────────────
class CalculadoraWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(orientation='vertical', **kwargs)
self.expressao = ""
self.resultado_atual = ""
self.modo_cientifico = False
self.store = JsonStore("settings.json")
init_db()
self._build_ui()
def _build_ui(self):
# Display
self.display = Label(
text="0", font_size="38sp", halign="right",
valign="middle", size_hint_y=0.18,
color=get_color_from_hex("#222222"),
padding=(20, 10)
)
self.display.bind(size=self.display.setter('text_size'))
self.add_widget(self.display)
# Sub-display (expressão)
self.subdisplay = Label(
text="", font_size="16sp", halign="right",
valign="middle", size_hint_y=0.06,
color=get_color_from_hex("#888888"),
padding=(20, 0)
)
self.subdisplay.bind(size=self.subdisplay.setter('text_size'))
self.add_widget(self.subdisplay)
# Grid de botões
self._build_botoes()
def _build_botoes(self):
from kivy.uix.gridlayout import GridLayout
grid = GridLayout(cols=4, spacing=4, padding=8, size_hint_y=0.76)
botoes = [
("C","#f44336"),("⌫","#ff7043"),("%","#ff9800"),("÷","#ffc107"),
("7","#fff"), ("8","#fff"), ("9","#fff"), ("×","#ffc107"),
("4","#fff"), ("5","#fff"), ("6","#fff"), ("-","#ffc107"),
("1","#fff"), ("2","#fff"), ("3","#fff"), ("+","#ffc107"),
("±","#eee"), ("0","#fff"), (".","#fff"), ("=","#28a745"),
]
for texto, cor in botoes:
btn = Button(
text=texto,
font_size="22sp",
background_normal="",
background_color=(*get_color_from_hex(cor), 1),
color=get_color_from_hex("#222") if cor=="#fff" else get_color_from_hex("#fff"),
on_press=lambda x, t=texto: self.on_botao(t)
)
grid.add_widget(btn)
self.add_widget(grid)
def on_botao(self, texto):
if texto == "C":
self.expressao = ""
self.display.text = "0"
self.subdisplay.text = ""
elif texto == "⌫":
self.expressao = self.expressao[:-1]
self.display.text = self.expressao or "0"
elif texto == "=":
self._calcular()
elif texto == "±":
if self.expressao and self.expressao[0] == "-":
self.expressao = self.expressao[1:]
else:
self.expressao = "-" + self.expressao
self.display.text = self.expressao or "0"
elif texto == "%":
try:
val = float(calcular(self.expressao))
self.expressao = str(val / 100)
self.display.text = self.expressao
except:
pass
else:
self.expressao += texto
self.display.text = self.expressao
def _calcular(self):
if not self.expressao:
return
resultado = calcular(self.expressao)
self.subdisplay.text = self.expressao + " ="
salvar_calculo(self.expressao, resultado)
self.expressao = resultado if "Erro" not in resultado else ""
self.display.text = resultado
# ── App principal ────────────────────────────────────────────
class CalculadoraCanalQbApp(App):
def build(self):
self.title = "Calculadora @CanalQb"
return CalculadoraWidget()
if __name__ == "__main__":
CalculadoraCanalQbApp().run()
calcular() usa eval() com dicionário restrito — sem acesso ao sistema de arquivos ou bibliotecas externas.
Passo 4 — buildozer.spec: configuração do APK
[app]
title = Calculadora CanalQb
package.name = calculadoracanalqb
package.domain = br.com.canalqb
source.dir = .
source.include_exts = py,png,jpg,kv,atlas,ttf,db
version = 1.0.0
requirements = python3,kivy==2.3.0,sqlite3,requests
orientation = portrait
fullscreen = 0
android.permissions = INTERNET, VIBRATE
android.api = 34
android.minapi = 24
android.sdk = 34
android.ndk = 25b
android.archs = arm64-v8a, armeabi-v7a
# Ícone e splash
icon.filename = %(source.dir)s/assets/icon.png
presplash.filename = %(source.dir)s/assets/presplash.png
# AdMob — trocar pelo seu App ID real
android.meta_data = com.google.android.gms.ads.APPLICATION_ID=ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX
[buildozer]
log_level = 2
warn_on_root = 1
Passo 5 — Compilar o APK
# Na pasta raiz do projeto
cd ~/calculadora_cqb
source ~/calc_env/bin/activate
# Primeira build — baixa Android SDK/NDK automaticamente (~1.5 GB)
buildozer android debug
# O APK gerado fica em:
# bin/calculadoracanalqb-1.0.0-arm64-v8a-debug.apk
# Para instalar direto no celular conectado por USB (ADB)
buildozer android deploy run
# Build de release (para Google Play) — exige keystore assinado
buildozer android release
~/.buildozer/.
Como gerar e assinar a keystore para o Google Play?
# Gerar keystore (execute uma vez, guarde em local seguro)
keytool -genkey -v -keystore ~/calculadora_cqb/canalqb.keystore \
-alias canalqb_key -keyalg RSA -keysize 2048 -validity 10000
# Assinar o APK de release manualmente (alternativa ao buildozer)
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
-keystore canalqb.keystore \
bin/calculadoracanalqb-1.0.0-arm64-v8a-release-unsigned.apk \
canalqb_key
# Otimizar o APK assinado (zipalign)
zipalign -v 4 \
bin/calculadoracanalqb-1.0.0-arm64-v8a-release-unsigned.apk \
bin/calculadoracanalqb-release.apk
Quanto custa publicar no Google Play e quais são as taxas?
| Item | Valor | Observação |
|---|---|---|
| Conta Google Play Developer | US$ 25 (único) | Pago uma vez, vale para sempre |
| Publicar app gratuito | Gratuito | Sem custo adicional por app |
| Comissão Google (apps pagos) | 15%–30% | 15% até US$1M/ano de receita |
| AdMob (banner/intersticial) | 0% do Google Play | AdMob tem política própria de pagamento |
| Imposto sobre receita AdMob (BR) | Depende do regime | Consulte contador — pode incidir IRPF |
Como funciona o processo de testes: interno, fechado e aberto?
-
1Teste Interno — Validação rápidaDisponível imediatamente após criar o app no Console. Você adiciona até 100 e-mails manualmente (Google Accounts). O app fica disponível em poucos minutos — ideal para testar instalação, crash rate e funcionalidades básicas. Não tem prazo mínimo nem exige aprovação do Google. Use esta fase para detectar bugs críticos antes de qualquer exposição.
-
2Teste Fechado — Critério obrigatório para produçãoAqui está o detalhe que trava 90% dos novos desenvolvedores: você precisa de mínimo 12 testadores que aceitaram ativamente o convite e ficaram testando de forma contínua por pelo menos 14 dias. Não basta adicionar os e-mails — eles precisam clicar no link de opt-in e instalar o app. O contador dos 14 dias só começa quando você tem 12 participantes ativos.
-
3Solicitar Acesso à ProduçãoApós os 14 dias com 12+ testadores, o botão "Solicitar acesso de produção" fica disponível. O Google vai pedir que você responda um questionário sobre o teste fechado: quantas pessoas testaram, quais feedbacks recebeu, como você abordou os problemas encontrados. Seja honesto e detalhado — respostas genéricas resultam em rejeição do pedido.
-
4Teste Aberto (Opcional, mas recomendado)Entre o fechado e a produção, você pode ativar o teste aberto — qualquer usuário do Google Play pode instalar via link. Não é obrigatório, mas ajuda a coletar mais métricas de crash rate e review antes da produção real. Use se quiser mais dados antes do lançamento oficial.
-
5Produção — Disponível para bilhões de usuáriosCom acesso de produção aprovado, você publica a versão final. O rollout pode ser gradual (10%, 50%, 100%) ou imediato. O app fica indexado no Google Play e aparece nos resultados de busca orgânica. A revisão humana do Google leva de 1 a 7 dias úteis na primeira publicação.
Como integrar AdMob na calculadora Python/Kivy?
-
1Criar conta AdMob e registrar o appAcesse admob.google.com, crie sua conta, clique em "Adicionar app" → Android → insira o nome "Calculadora CanalQb". O AdMob gera um App ID no formato
ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX. Esse ID vai no buildozer.spec (já mostrado no Passo 4). -
2Criar unidade de anúncio (Ad Unit)No painel AdMob, crie uma unidade do tipo "Banner". Você recebe um Ad Unit ID no formato
ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX. Anote separado do App ID — são valores diferentes. Para testes use o ID de teste oficial do Google:ca-app-pub-3940256099942544/6300978111. -
3Instalar KivMob no projetoAdicione ao
buildozer.spec:requirements = python3,kivy,kivmob,sqlite3. O KivMob é a ponte entre Python e o Google Mobile Ads SDK para Android. Também adicione:android.gradle_dependencies = com.google.android.gms:play-services-ads:23.0.0. -
4Adicionar o banner no main.pyO banner é carregado após o app inicializar. Use o ID de teste durante o desenvolvimento — nunca o ID real antes de publicar na loja.
"""Integração AdMob com KivMob — adicionar ao main.py"""
from kivmob import KivMob, TestIds
class CalculadoraCanalQbApp(App):
def build(self):
self.title = "Calculadora @CanalQb"
# Inicializar AdMob
# Em desenvolvimento: use TestIds.BANNER
# Em produção: substitua pelo seu Ad Unit ID real
self.ads = KivMob("ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX")
self.ads.new_banner(TestIds.BANNER, top_pos=False)
self.ads.request_banner()
self.ads.show_banner()
return CalculadoraWidget()
def on_pause(self):
"""Pausar anúncio quando app vai para background"""
self.ads.hide_banner()
return True
def on_resume(self):
"""Retomar anúncio quando app volta ao foco"""
self.ads.show_banner()
def on_stop(self):
self.ads.destroy_banner()
Quantos usuários preciso para ganhar R$ 1 por dia com AdMob em banner?
Simulação AdMob — Meta: R$ 1,00 por dia
Como configurar o intervalo de 2 minutos no AdMob?
"""
Intervalo de atualização automática do banner.
O AdMob aceita entre 30 e 120 segundos.
120 segundos (2 min) é o recomendado pelo @CanalQb
para equilibrar impressões e eCPM.
"""
from kivy.clock import Clock
class CalculadoraCanalQbApp(App):
def build(self):
self.ads = KivMob("SEU_APP_ID_AQUI")
self.ads.new_banner("SEU_AD_UNIT_ID_AQUI", top_pos=False)
self.ads.request_banner()
self.ads.show_banner()
# Agendar atualização a cada 120 segundos
Clock.schedule_interval(self._atualizar_banner, 120)
return CalculadoraWidget()
def _atualizar_banner(self, dt):
"""Solicita novo anúncio a cada 2 minutos."""
self.ads.request_banner() # Novo request = novo anúncio
Como publicar o APK e quais são as etapas finais no Google Play Console?
-
1Criar o app no Google Play ConsoleAcesse play.google.com/console → "Criar app" → defina nome, idioma, tipo (app gratuito) e aceite as políticas. O Console cria uma estrutura de dashboards onde você gerencia todas as versões.
-
2Preencher a ficha da Play StoreEm "Presença na Play Store" → "Ficha principal da loja": adicione título (máximo 30 caracteres), descrição curta (80 caracteres), descrição longa (4.000 caracteres), ícone 512×512px, feature graphic 1024×500px, e mínimo 2 screenshots (recomendado: 8 screenshots de 1080×1920px). Capriche na descrição — é SEO direto no Google Play.
-
3Subir o arquivo AAB (recomendado) ou APKO Google prefere AAB (Android App Bundle) ao APK — ele gera APKs otimizados por dispositivo automaticamente. Para gerar AAB com Buildozer adicione
android.release_artifact = aabno buildozer.spec. Faça upload em "Releases" → "Teste interno" primeiro. -
4Configurar classificação etária (IARC)Obrigatório. O sistema IARC faz um questionário sobre o conteúdo do app. Para uma calculadora, a classificação será "Livre para todos" — responda que não contém violência, linguagem adulta, compras ou conteúdo sensível. O certificado é gerado automaticamente.
-
5Política de privacidade (obrigatório para AdMob)Como o app usa AdMob, você precisa declarar que coleta dados para fins publicitários. Crie uma página de política de privacidade (pode ser no seu blog) e adicione a URL no Console. Sem isso, o Google rejeita o app. Use geradores gratuitos como termly.io ou escreva uma baseada no modelo do canalqb.com.br.
Qual é a expectativa realista de crescimento para uma calculadora no Google Play?
| Mês | Instalações (estimativa) | DAU estimado | Receita AdMob/dia |
|---|---|---|---|
| Mês 1 (teste fechado) | 12–30 | 5–15 | R$ 0,00–R$ 0,10 |
| Mês 2 (produção recém-publicado) | 50–200 | 20–80 | R$ 0,05–R$ 0,25 |
| Mês 3–4 (indexação no Play) | 200–800 | 80–300 | R$ 0,20–R$ 0,80 |
| Mês 5–6 (tração orgânica) | 800–2.000 | 300–800 | R$ 0,80–R$ 2,50 |
| Mês 12+ (se bem avaliado) | 5.000+ | 1.500+ | R$ 2,00–R$ 8,00 |
Aqui no @CanalQb acompanhamos lançamentos de apps utilitários desde 2023 e o padrão é consistente: os primeiros 90 dias são de crescimento lento, indexação no Google Play Search e construção de avaliações. A partir do mês 4, apps com rating acima de 4.0 começam a receber tráfego orgânico significativo. O ponto de inflexão quase sempre acontece entre 4 e 6 meses.
Referências e documentação oficial
📚 Fontes e Referências
- Buildozer — Documentação oficial
- Kivy Framework — Docs estáveis
- Google Play — Critérios para acesso à produção
- AdMob — Políticas de anúncios e eCPM
- Google Developers — Banner Ads Android
- @CanalQb — Mais posts sobre Python para Android
- @CanalQb — Guia de monetização com AdMob
- @CanalQb — Como publicar no Google Play
Perguntas Frequentes
É possível criar APK com Python sem saber Java ou Kotlin?
Quanto tempo leva para o Google aprovar o acesso à produção?
O Buildozer funciona no Windows ou precisa de Linux obrigatoriamente?
Qual é o eCPM real de banner AdMob para apps de calculadora no Brasil?
APK com Python é mais pesado do que um app feito em Java ou Kotlin?
Posso usar o mesmo código Python para criar versão iOS também?
Veja o processo completo em vídeo
Do zero ao APK publicado — sem cortes, sem atalhos, na prática.
Abrir @CanalQb no YouTubeFeito com Master Rules Claude v8.3 | @CanalQb 2026
Comentários
Comente só assim vamos crescer juntos!