Streamlit: guia completo para criar web apps interativos rapidamente
Você criou sua lista de lembretes com Kivy e agora quer ir além? Perfeito! Chegou o momento de transformar aquele app funcional em algo verdadeiramente profissional utilizando Python e KivyMD, que é derivado do Kivy.
Atualmente, seu código Kivy provavelmente mistura lógica de programação com definições de interface. Além disso, a aparência ainda lembra os primeiros apps dos anos 2000. Isso é completamente normal no começo.
No entanto, quando você decide evoluir o app, duas coisas ficam evidentes: primeiro, o código se torna cada vez mais difícil de manter. Segundo, a interface simplesmente não impressiona ninguém.

A solução envolve duas ferramentas poderosas: Kivy Language Python para organizar seu código e KivyMD para criar interfaces modernas. Consequentemente, você terá o mesmo app, porém com código profissional e visual impactante.
Neste artigo, você descobrirá como migrar sua lista de lembretes para essa nova arquitetura. Portanto, prepare-se para dar um salto de qualidade significativo.
Todo desenvolvedor Kivy passa por essa fase. Primeiramente, você cria um app simples que funciona. Em seguida, adiciona mais funcionalidades. Posteriormente, o código começa a ficar bagunçado.
Reconhece algum desses sinais na sua aplicação Kivy?
Esses são sinais clássicos de que chegou a hora de organizar melhor sua aplicação.
Por um lado, Python funciona perfeitamente para apps simples. Por outro lado, conforme a complexidade aumenta, surgem problemas reais de manutenibilidade.
Considere este exemplo real de como o código cresce naturalmente:
# Versão inicial - 50 linhas
class MeuApp(App):
def build(self):
return Button(text="Clique")
# Após algumas funcionalidades - 200+ linhas
class MeuApp(App):
def build(self):
layout = BoxLayout(orientation="vertical")
# ... 150 linhas de definições de interface
# ... misturado com lógica de negócioEvidentemente, essa abordagem não escala bem.
A separação entre interface e lógica traz benefícios fundamentais:
✅ Vantagens da organização
⚠️ Sem organização
Felizmente, existe uma solução elegante: Kivy Language Python. Esta ferramenta permite separar completamente a definição da interface da lógica do aplicativo.
O Kivy Language Python funciona como HTML para web, mas específico para Kivy. Basicamente, você define toda a interface em arquivos .kv, enquanto mantém apenas a lógica nos arquivos .py.
A sintaxe do Kivy é surpreendentemente simples. Veja esta comparação prática:
Vamos migrar um pedaço da nossa lista de lembretes para Kivy. Primeiramente, observe como era no Python:
def build(self):
layout = BoxLayout(orientation="vertical")
titulo = Label(text="Meus Lembretes", font_size="24sp")
layout.add_widget(titulo)
return layoutAgora, veja como fica usando Kivy Language Python:
arquivo: main.py
class ListaLembretesApp(App):
def build(self):
return Builder.load_file("interface.kv")arquivo: interface.kv
BoxLayout:
orientation: "vertical"
Label:
text: "Meus Lembretes"
font_size: "24sp"
size_hint_y: 0.1Como você pode ver, a sintaxe Kivy é mais limpa e declarativa. Além disso, elimina a necessidade de criar variáveis para cada widget. Percebe a diferença? O arquivo Python agora tem apenas 3 linhas! Consequentemente, toda a complexidade visual foi movida para o arquivo Kivy.
Para projetos maiores, recomendo essa estrutura de organização:
meu_app/
├── main.py # Lógica principal
├── interfaces/
│ ├── main.kv # Interface principal
│ ├── lembretes.kv # Tela de lembretes
│ └── configuracoes.kv # Tela de configurações
└── models/
└── lembrete.py # Classes de dadosEsta organização permite que você mantenha cada tela em seu próprio arquivo. Portanto, fica muito mais fácil encontrar e modificar elementos específicos.
Durante a migração para Kivy Language Python, você provavelmente encontrará alguns erros típicos:
Problema 1: Arquivo Kivy não encontrado
FileNotFoundError: [Errno 2] No such file or directory: 'interface.kv'Solução: verifique se o arquivo .kv está na pasta correta e se está sendo carregado pelo Builder dentro de algum código Python.
Problema 2: Propriedades não reconhecidas
AttributeError: 'Button' object has no attribute 'texto'Solução: propriedades KV devem estar de acordo com a documentação, e nesse caso, o correto seria text em inglês.
Agora que você organizou o código, chegou a hora de elevar o nível visual. Apps Kivy padrão funcionam, mas definitivamente não impressionam visualmente.
Por padrão, Kivy usa elementos visuais que remetem aos anos 2000. Embora funcionais, esses elementos passam uma impressão amadora. Consequentemente, isso pode prejudicar a percepção do seu trabalho.
Compare estes elementos visuais:
Kivy Padrão
KivyMD Profissional
KivyMD implementa completamente o Material Design do Google. Como resultado, você obtém componentes visuais profissionais sem esforço adicional.
Primeiramente, instale o KivyMD:
pip install kivymdEm seguida, faça a migração básica da sua aplicação:
from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
class MeuApp(MDApp):
def build(self):
return MDLabel(text="Hello World!", halign="center")
MeuApp().run()Instantaneamente, você terá uma aplicação com visual Material Design. Além disso, todos os componentes seguirão automaticamente as diretrizes de design do Google.
Estes são os componentes que farão a maior diferença visual na sua aplicação:
MDCard – Para agrupar conteúdo:
MDCard:
elevation: 3
shadow_softness: 12
md_bg_color: app.theme_cls.surface_colorMDRaisedButton – Botões com Material Design:
MDRaisedButton:
text: "Clique aqui"
theme_icon_color: "Custom"
md_bg_color: app.theme_cls.primary_colorMDTextField – Campos de entrada modernos:
MDTextField:
hint_text: "Digite seu lembrete"
helper_text: "Máximo 100 caracteres"
max_text_length: 100O KivyMD permite personalizar facilmente toda a paleta de cores:
class MeuApp(MDApp):
def build(self):
self.theme_cls.primary_palette = "Blue"
self.theme_cls.theme_style = "Dark"
return Builder.load_file("interface.kv")Desta forma, você pode adaptar completamente o visual às suas preferências ou identidade da marca.
Vamos transformar sua lista de lembretes básica em uma aplicação profissional. Aplicaremos tudo que aprendemos sobre Kivy Language Python e KivyMD de forma incremental.

Antes de começarmos a codar, veja exatamente o que vamos criar:

Teremos uma aplicação com:
Problema real: nossa lista atual usa componentes Kivy básicos que parecem desatualizados;
Solução: migrar para KivyMD mantendo a funcionalidade, mas com visual moderno;
Como funciona: é como reformar uma casa – mantemos a estrutura (funcionalidade), mas trocamos o acabamento (visual).
Implementação passo a passo:
Passo 1 – App base com KivyMD:
from kivymd.app import MDApp
from kivy.lang import Builder
class ListaLembretesApp(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "DeepPurple"
return Builder.load_file("interfaces/main.kv")
ListaLembretesApp().run()Passo 2 – Interface KV com cabeçalho estilizado:
MDScreen:
MDBoxLayout:
orientation: "vertical"
spacing: dp(8)
padding: dp(16)
# Cabeçalho com gradiente visual
MDCard:
size_hint_y: None
height: dp(100)
elevation: 4
md_bg_color: app.theme_cls.primary_color
radius: [15, 15, 15, 15]
MDBoxLayout:
orientation: "horizontal"
padding: dp(20)
spacing: dp(10)
MDIcon:
icon: "playlist-check"
size_hint_x: None
width: dp(40)
theme_icon_color: "Custom"
icon_color: 1, 1, 1, 1
pos_hint: {"center_y": 0.5}
MDLabel:
text: "To-Do List"
font_style: "H4"
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
bold: True
valign: "center"
MDIconButton:
icon: "theme-light-dark"
theme_icon_color: "Custom"
icon_color: 1, 1, 1, 1
pos_hint: {"center_y": 0.5}
on_press: app.alternar_tema()Problema real: campo de texto simples não condiz com design moderno.
Solução: implementar campo com ícone e Floating Action Button.
Implementação passo a passo:
Passo 3 – Adicionar campo estilizado ao Kivy:
MDScreen:
MDBoxLayout:
MDCard:
...
# Adicionar após o cabeçalho
# Campo de entrada estilizado
MDCard:
size_hint_y: None
height: dp(80)
elevation: 2
radius: [12, 12, 12, 12]
MDBoxLayout:
orientation: "horizontal"
padding: dp(16)
spacing: dp(12)
MDTextField:
id: campo_lembrete
hint_text: "Add New Task"
multiline: False
font_size: dp(16)
on_text_validate: app.adicionar_lembrete()
MDFloatingActionButton:
icon: "plus"
md_bg_color: app.theme_cls.primary_color
size_hint: None, None
size: dp(45), dp(45)
pos_hint: {"center_y": 0.5}
elevation: 3
on_press: app.adicionar_lembrete()Passo 4 – Conectar lógica de adição:
class ListaLembretesApp(MDApp):
...
# Adicionar método à classe ListaLembretesApp
def adicionar_lembrete(self):
campo = self.root.ids.campo_lembrete
lista = self.root.ids.lista_lembretes
texto = campo.text.strip()
if texto:
item = LembreteItem(texto=texto)
lista.add_widget(item)
campo.text = ""
self.salvar_lembretes()Problema real: precisamos exibir e gerenciar os lembretes de forma visual.
Solução: implementar lista com ícones e botões de remoção usando componentes KivyMD.
Implementação passo a passo:
Passo 5 – Criar classe para itens da lista:
from kivymd.uix.list import OneLineAvatarIconListItem, IconLeftWidget, IconRightWidget
class LembreteItem(OneLineAvatarIconListItem):
def __init__(self, texto="", feito=False, **kwargs):
super().__init__(**kwargs)
self.texto_original = texto
self.feito = feito
# Ícone de tarefa à esquerda (checkbox)
self.icone = IconLeftWidget(
icon="checkbox-marked-circle-outline" if self.feito else "checkbox-blank-circle-outline"
)
self.icone.bind(on_release=self.toggle_feito)
self.add_widget(self.icone)
# Botão de lixeira (caso ainda queira manter exclusão manual)
botao = IconRightWidget(icon="delete-outline")
botao.bind(on_release=self.remover_item)
self.add_widget(botao)
self.atualizar_texto()Passo 6 – Adicionar área da lista ao Kivy:
MDScreen:
MDBoxLayout:
MDCard:
...
MDCard:
...
# Adicionar Lista de lembretes com estilo
MDCard:
elevation: 2
radius: [12, 12, 12, 12]
ScrollView:
MDList:
id: lista_lembretes
spacing: dp(2)Passo 7 – Completar lógica de adição e remoção:
# Adicionar método de remover item
class LembreteItem(OneLineAvatarIconListItem):
...
def atualizar_texto(self):
"""Atualiza o texto com ou sem risquinho"""
if self.feito:
self.text = f"[s]{self.texto_original}[/s]"
self.markup = True # permite usar tags [s][/s]
else:
self.text = self.texto_original
self.markup = False
def toggle_feito(self, *args):
"""Alterna entre concluído/não concluído"""
self.feito = not self.feito
self.icone.icon = (
"checkbox-marked-circle-outline" if self.feito else "checkbox-blank-circle-outline"
)
self.atualizar_texto()
MDApp.get_running_app().salvar_lembretes()
def remover_item(self, *args):
"""Remove completamente a tarefa"""
if self.parent:
self.parent.remove_widget(self)
MDApp.get_running_app().salvar_lembretes()Problema real: dados se perdem ao fechar o app, falta alternância de tema.
Solução: salvar dados em JSON e implementar toggle de tema.
Implementação passo a passo:
Passo 8 – Adicionar persistência:
import json
import os
class ListaLembretesApp(MDApp):
...
# Adicionar métodos à classe principal
def salvar_lembretes(self):
lista = self.root.ids.lista_lembretes
lembretes = [
{"texto": item.texto_original, "feito": item.feito}
for item in reversed(lista.children)
]
with open("lembretes.json", "w", encoding="utf-8") as f:
json.dump(lembretes, f, ensure_ascii=False, indent=2)
def on_start(self):
"""Carrega os lembretes salvos, se existirem"""
if os.path.exists("lembretes.json"):
with open("lembretes.json", "r", encoding="utf-8") as f:
for dic in json.load(f):
self.root.ids.lista_lembretes.add_widget(
LembreteItem(texto=dic["texto"], feito=dic["feito"])
)Passo 9 – Botão de alternância de tema:
class ListaLembretesApp(MDApp):
...
# Método para alternar tema
def alternar_tema(self):
self.theme_cls.theme_style = (
"Dark" if self.theme_cls.theme_style == "Light" else "Light"
)Validação: execute o app, adicione lembretes, feche e reabra – os dados devem persistir. O botão de tema deve alternar entre claro e escuro.
Agora você tem uma aplicação completamente profissional que:
O mesmo conceito de lista de lembretes, mas com implementação de nível profissional.
Parabéns! Você transformou com sucesso um app básico em uma aplicação profissional. Atualmente, você domina Kivy Language Python e KivyMD, duas ferramentas fundamentais para desenvolvimento Kivy sério.
Neste momento, você sabe como:
Para aprofundar ainda mais seus conhecimentos:
Documentação oficial:
Próximos desafios técnicos:
Automatize tudo com Python e torne seu escritório muito mais eficiente! Manipule arquivos, crie relatórios automatizados e construa sistemas 100% personalizados usando apenas Python.
Comece agoraLembre-se: a jornada de um desenvolvedor Python nunca para. Cada projeto ensina algo novo, e cada desafio superado abre portas para possibilidades ainda maiores.
Arquivo main.py completo:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivymd.uix.list import OneLineAvatarIconListItem, IconLeftWidget, IconRightWidget
import json
import os
class LembreteItem(OneLineAvatarIconListItem):
def __init__(self, texto="", feito=False, **kwargs):
super().__init__(**kwargs)
self.texto_original = texto
self.feito = feito
# Ícone de tarefa à esquerda (checkbox)
self.icone = IconLeftWidget(
icon="checkbox-marked-circle-outline" if self.feito else "checkbox-blank-circle-outline"
)
self.icone.bind(on_release=self.toggle_feito)
self.add_widget(self.icone)
# Botão de lixeira (caso ainda queira manter exclusão manual)
botao = IconRightWidget(icon="delete-outline")
botao.bind(on_release=self.remover_item)
self.add_widget(botao)
self.atualizar_texto()
def atualizar_texto(self):
"""Atualiza o texto com ou sem risquinho"""
if self.feito:
self.text = f"[s]{self.texto_original}[/s]"
self.markup = True # permite usar tags [s][/s]
else:
self.text = self.texto_original
self.markup = False
def toggle_feito(self, *args):
"""Alterna entre concluído/não concluído"""
self.feito = not self.feito
self.icone.icon = (
"checkbox-marked-circle-outline" if self.feito else "checkbox-blank-circle-outline"
)
self.atualizar_texto()
MDApp.get_running_app().salvar_lembretes()
def remover_item(self, *args):
"""Remove completamente a tarefa"""
if self.parent:
self.parent.remove_widget(self)
MDApp.get_running_app().salvar_lembretes()
class ListaLembretesApp(MDApp):
# Método para alternar tema
def alternar_tema(self):
self.theme_cls.theme_style = (
"Dark" if self.theme_cls.theme_style == "Light" else "Light"
)
def salvar_lembretes(self):
lista = self.root.ids.lista_lembretes
lembretes = [
{"texto": item.texto_original, "feito": item.feito}
for item in reversed(lista.children)
]
with open("lembretes.json", "w", encoding="utf-8") as f:
json.dump(lembretes, f, ensure_ascii=False, indent=2)
def on_start(self):
"""Carrega os lembretes salvos, se existirem"""
if os.path.exists("lembretes.json"):
with open("lembretes.json", "r", encoding="utf-8") as f:
for dic in json.load(f):
self.root.ids.lista_lembretes.add_widget(
LembreteItem(texto=dic["texto"], feito=dic["feito"])
)
def adicionar_lembrete(self):
campo = self.root.ids.campo_lembrete
lista = self.root.ids.lista_lembretes
texto = campo.text.strip()
if texto:
item = LembreteItem(texto=texto)
lista.add_widget(item)
campo.text = ""
self.salvar_lembretes()
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "DeepPurple"
return Builder.load_file("interfaces/main.kv")
ListaLembretesApp().run()Arquivo interfaces/main.kv completo:
MDScreen:
MDBoxLayout:
orientation: "vertical"
spacing: dp(8)
padding: dp(16)
# Cabeçalho com gradiente visual
MDCard:
size_hint_y: None
height: dp(100)
elevation: 4
md_bg_color: app.theme_cls.primary_color
radius: [15, 15, 15, 15]
MDBoxLayout:
orientation: "horizontal"
padding: dp(20)
spacing: dp(10)
MDIcon:
icon: "playlist-check"
size_hint_x: None
width: dp(40)
theme_icon_color: "Custom"
icon_color: 1, 1, 1, 1
pos_hint: {"center_y": 0.5}
MDLabel:
text: "To-Do List"
font_style: "H4"
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
bold: True
valign: "center"
MDIconButton:
icon: "theme-light-dark"
theme_icon_color: "Custom"
icon_color: 1, 1, 1, 1
pos_hint: {"center_y": 0.5}
on_press: app.alternar_tema()
# Campo de entrada estilizado
MDCard:
size_hint_y: None
height: dp(80)
elevation: 2
radius: [12, 12, 12, 12]
MDBoxLayout:
orientation: "horizontal"
padding: dp(16)
spacing: dp(12)
MDTextField:
id: campo_lembrete
hint_text: "Add New Task"
multiline: False
font_size: dp(16)
on_text_validate: app.adicionar_lembrete()
MDFloatingActionButton:
icon: "plus"
md_bg_color: app.theme_cls.primary_color
size_hint: None, None
size: dp(45), dp(45)
pos_hint: {"center_y": 0.5}
elevation: 3
on_press: app.adicionar_lembrete()
MDCard:
elevation: 2
radius: [12, 12, 12, 12]
ScrollView:
MDList:
id: lista_lembretes
spacing: dp(2)
Aprenda a programar e desenvolva soluções para o seu trabalho com Python para alcançar novas oportunidades profissionais. Aqui na Asimov você encontra:
Comentários
30xp