eu parei de chamar page.update() o tempo todo
Como usar estado observável no Flet para criar temas reativos sem controle manual da interface

O app até funcionava… mas a UI não acompanhava
Em um dos meus projetos, precisei montar um sistema de temas no Flet — e foi aí que comecei a perceber um padrão estranho:
- o usuário troca o tema
- eu atualizo variáveis
- chamo
page.update() - alguns controles atualizam, outros não
- e você não sabe exatamente por quê
Começa aquela caça:
- qual controle não atualizou?
- qual referência ficou pra trás?
- onde eu deveria chamar
update()de novo?
Chega um ponto em que você não está mais construindo a interface.
Está tentando manter ela sincronizada.
O problema com mutação manual
A abordagem padrão no Flet é imperativa:
- guardar referência de controle
- escutar evento
- alterar propriedade
- chamar
update()
Funciona — até o estado crescer.
Com tema, isso escala rápido:
- cor muda → vários componentes impactados
- múltiplos pontos de atualização
- inconsistência visual
E o pior:
você começa a depender de lembrar onde atualizar.
A virada
Foi aí que eu parei de tentar atualizar a interface…
e comecei a mudar só o estado.
Estado observável
Com @ft.observable e @ft.component, a lógica muda completamente:
@ft.observable
@dataclass
class AppState:
theme_name: str = "Aurora"
dark_mode: bool = False
Qualquer componente que usa esse estado re-renderiza automaticamente.
Sem page.update()
Sem referência manual
a UI passou a ser só uma consequência
Como o tema funciona
Cada tema é um conjunto de tokens:
THEMES = {
"Aurora": {
"primary": "#22D3EE",
"surface": "#FFFFFF",
},
}
E uma função resolve o tema ativo:
def th(state: AppState) -> dict:
return THEMES[state.theme_name]
Os componentes só fazem isso:
- leem o estado
- aplicam as cores
Quando theme_name muda, tudo re-renderiza automaticamente.
Dark mode sem gambiarra
Em vez de sobrescrever cor por cor, eu pareei temas:
DARK_PAIR = {
"Aurora": "Midnight",
"Lime": "Obsidian",
}
def on_dark_change(e):
state.dark_mode = e.control.value
state.theme_name = DARK_PAIR.get(state.theme_name, state.theme_name)
Resultado:
- sem estado intermediário quebrado
- sem UI metade clara / metade escura
- troca consistente
Logotipo contextual (Opcional)
O estado guarda duas versões:
logo_light: str = ""
logo_dark: str = ""
E a escolha é feita assim:
def active_logo(state: AppState) -> str:
if state.dark_mode:
return state.logo_dark or state.logo_light
return state.logo_light or state.logo_dark
Simples, previsível e com fallback.
*Exemplo ilustrativo de uma lógica útil
Uma ressalva
Esse padrão funciona muito bem enquanto o estado é local.
Se a aplicação crescer:
- múltiplas telas
- múltiplos estados compartilhados
vale pensar em como distribuir isso melhor.
Outro ponto:
@ft.component re-renderiza o componente inteiro.
Para componentes pequenos, irrelevante.
Para listas grandes, pode exigir atenção.
Conclusão
No fim, o problema não era o tema.
Era a forma como eu estava tentando controlar a interface.
Quando parei de atualizar tudo manualmente
e deixei o estado guiar a UI
o código ficou mais simples — e mais previsível.