Theme | Artigo | Flutter
- Theme | Artigo | Flutter

Você já quis criar um aplicativo Flutter capaz de alternar entre diferentes temas, como Brasil, Estados Unidos e Índia, com apenas um toque? Neste artigo, você vai aprender passo a passo como estruturar seu projeto Flutter para oferecer suporte a múltiplos temas dinâmicos, mantendo seu código organizado, reativo e persistente.
🧠 Conceito: Por que usar temas dinâmicos?
Criar temas dinâmicos permite ao usuário personalizar a aparência do aplicativo de forma visual e interativa. Além de melhorar a experiência, facilita a internacionalização e branding da sua aplicação.
🧱 Arquitetura do Projeto
A estrutura do projeto é baseada em MVVM (Model-View-ViewModel) e separa as responsabilidades em camadas bem definidas:
/bootstrap
└── app_initializer.dart
/core
└── theme/
├── theme_controller.dart
├── theme_context.dart
├── theme_model.dart
├── theme_presets.dart
└── theme_type.dart
/view
└── home_view.dart
/view_model
└── theme_view_model.dart
main.dart
my_app.dart
🔨 Passo a Passo
1. Crie o modelo de tema
Arquivo: theme_model.dart
/// Este arquivo define a classe [ThemeModel], que representa um modelo de tema personalizado.
/// A responsabilidade da classe é encapsular as principais cores usadas na aplicação,
/// permitindo a criação de múltiplos temas com nomes distintos.
///
/// Essa abordagem é útil para aplicar temas dinâmicos ou alternar entre diferentes paletas
/// de cores na interface do aplicativo, mantendo o código mais organizado e reutilizável.
library;
import 'package:flutter/material.dart';
/// Modelo de dados para representar um tema personalizado da aplicação.
class ThemeModel {
/// Nome identificador do tema. Pode ser usado para seleção ou exibição.
final String name;
/// Cor primária do tema. Geralmente usada como cor principal da UI.
final Color primary;
/// Cor secundária do tema. Pode ser usada para botões, destaques ou outros elementos de suporte.
final Color secondary;
/// Cor terciária do tema. Utilizada como cor complementar ou decorativa.
final Color tertiary;
/// Cor de texto do tema. Geralmente usada para textos e elementos de interface.
final Color text;
/// Emoji associado ao tema. Pode ser usado para representar visualmente o tema.
final String emoji;
/// Construtor constante que permite a criação de um tema com nome e cores definidas.
const ThemeModel({
required this.name,
required this.primary,
required this.secondary,
required this.tertiary,
required this.text,
required this.emoji,
});
}
2. Defina os tipos de temas disponíveis
Arquivo: theme_type.dart
/// Este arquivo define a enumeração [ThemeType], que representa os diferentes tipos
/// de temas disponíveis na aplicação.
///
/// Cada valor da enum corresponde a um país específico e pode ser utilizado para
/// aplicar um conjunto de cores temáticas personalizado, associado ao país selecionado.
///
/// Essa enumeração é útil para identificar e alternar entre temas de forma clara e segura.
library;
/// Enumeração que define os tipos de temas disponíveis por país.
enum ThemeType {
/// Tema com cores e estilo inspirados no Brasil.
brazil,
/// Tema com cores e estilo inspirados nos Estados Unidos.
unitedStates,
/// Tema com cores e estilo inspirados na Índia.
india,
}
3. Crie os temas predefinidos
Arquivo: theme_presets.dart
/// Este arquivo define a classe [ThemePresets], responsável por armazenar os temas
/// predefinidos da aplicação.
///
/// Cada tema está associado a um valor da enum [ThemeType] e implementado como um
/// [ThemeModel], contendo cores específicas inspiradas em diferentes países.
///
/// A estrutura permite acessar rapidamente os temas disponíveis de forma organizada e segura,
/// promovendo reutilização e centralização da configuração de temas.
library;
import 'package:flutter/material.dart';
import 'theme_model.dart';
import 'theme_type.dart';
/// Classe utilitária que agrupa os temas predefinidos da aplicação.
class ThemePresets {
/// Mapa contendo todos os temas disponíveis, associados ao respectivo [ThemeType].
///
/// Essa estrutura facilita a seleção de temas com base em uma chave clara e semântica.
static final Map<ThemeType, ThemeModel> all = {
/// Tema do Brasil: verde, amarelo e azul.
ThemeType.brazil: const ThemeModel(
name: 'Brasil',
primary: Color(0xFF009739),
secondary: Color(0xFFFFCC29),
tertiary: Color(0xFF002776),
text: Color(0xFFFFFFFF),
emoji: '🇧🇷',
),
/// Tema dos Estados Unidos: azul escuro, branco e vermelho.
ThemeType.unitedStates: const ThemeModel(
name: 'Estados Unidos',
primary: Color(0xFF3C3B6E),
secondary: Color(0xFFFFFFFF),
tertiary: Color(0xFFB22234),
text: Color(0xFF000000),
emoji: '🇺🇸',
),
/// Tema da Índia: laranja, branco e verde.
ThemeType.india: const ThemeModel(
name: 'Índia',
primary: Color(0xFFFF9933),
secondary: Color(0xFFFFFFFF),
tertiary: Color(0xFF138808),
text: Color(0xFF000000),
emoji: '🇮🇳',
),
};
}
4. Gerencie o estado do tema
Arquivo: theme_controller.dart
/// Este arquivo define a classe [ThemeController], responsável pelo gerenciamento
/// dinâmico de temas da aplicação, incluindo a seleção, persistência e notificação de mudanças.
///
/// Ele usa o padrão de gerenciamento de estado com `ChangeNotifier`
/// e persiste a seleção de tema com `SharedPreferences`, garantindo que o tema
/// escolhido pelo usuário seja mantido entre sessões da aplicação.
library;
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'theme_model.dart';
import 'theme_type.dart';
import 'theme_presets.dart';
/// Controlador de temas da aplicação.
///
/// Responsável por:
/// - Gerenciar o tema atual selecionado.
/// - Persistir a escolha do usuário.
/// - Notificar widgets sobre mudanças de tema.
class ThemeController with ChangeNotifier {
/// Instância atual do tema em uso.
late ThemeModel _colors;
/// Tipo do tema atualmente selecionado.
ThemeType _currentType = ThemeType.brazil;
/// Chave usada para armazenar a seleção do tema localmente.
static const _storageKey = 'app_theme_type';
/// Mapa contendo todos os temas disponíveis.
final Map<ThemeType, ThemeModel> _themes = ThemePresets.all;
/// Inicializa o controlador, carregando o tema salvo no armazenamento local.
/// Caso não haja valor salvo, define o tema padrão como o do Brasil.
Future<void> init() async {
final prefs = await SharedPreferences.getInstance();
final stored = prefs.getString(_storageKey);
final type = ThemeType.values.firstWhere(
(e) => e.name == stored,
orElse: () => ThemeType.brazil,
);
// Garante que o tema existe no mapa antes de usar
if (_themes.containsKey(type)) {
_colors = _themes[type]!;
} else {
_colors = _themes[ThemeType.brazil]!;
}
_currentType = type;
notifyListeners(); // Notifica a UI para reagir à alteração
}
// Getters para expor as propriedades do tema atual
Color get primaryColor => _colors.primary;
Color get secondaryColor => _colors.secondary;
Color get tertiaryColor => _colors.tertiary;
Color get textColor => _colors.text;
String get name => _colors.name;
String get emoji => _colors.emoji;
ThemeType get currentType => _currentType;
/// Getter auxiliar para inspeção dos temas disponíveis (útil para testes ou seletores de UI)
Map<ThemeType, ThemeModel> get themes => _themes;
/// Define um novo tema e persiste a escolha do usuário localmente.
void setTheme(ThemeType type) async {
if (_themes.containsKey(type)) {
_currentType = type;
_colors = _themes[type]!;
notifyListeners(); // Notifica a UI para atualizar com o novo tema
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_storageKey, type.name);
}
}
}
5. Acesse o tema com extensões no contexto
Arquivo: theme_context.dart
/// Este arquivo define uma extensão sobre [BuildContext] chamada [ThemeContext].
///
/// A responsabilidade da extensão é facilitar o acesso ao [ThemeController] por meio
/// do contexto do Flutter, utilizando o pacote `provider`.
///
/// Ela fornece dois métodos de acesso: um reativo (`watch`) e um estático (`read`),
/// permitindo ao desenvolvedor escolher entre escutar mudanças no tema ou apenas
/// acessar o valor atual, sem escutar atualizações.
///
/// Isso torna o código mais limpo, legível e reduz a repetição ao lidar com temas dinâmicos.
library;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_controller.dart';
/// Extensão em [BuildContext] que facilita o acesso ao [ThemeController].
extension ThemeContext on BuildContext {
/// Acesso reativo ao [ThemeController], atualiza o widget ao detectar mudanças.
ThemeController get theme => watch<ThemeController>();
/// Acesso não reativo ao [ThemeController], útil quando não é necessário escutar mudanças.
ThemeController get themeStatic => read<ThemeController>();
}
6. Inicialize o app com o tema salvo
Arquivo: app_initializer.dart
/// Este arquivo define o widget [AppInitializer], responsável por inicializar
/// as configurações essenciais da aplicação antes de exibir a interface principal.
///
/// Ele cuida da carga assíncrona do [ThemeController], garantindo que o tema salvo
/// pelo usuário (persistido localmente) seja carregado corretamente antes da renderização
/// da aplicação. Após a inicialização, o controller e sua ViewModel são injetados
/// globalmente usando `MultiProvider`.
library;
import 'package:flutter/material.dart';
import 'package:flutter_exemplo_temas/view_model/theme_view_model.dart';
import 'package:provider/provider.dart';
import 'package:flutter_exemplo_temas/my_app.dart';
import 'package:flutter_exemplo_temas/core/theme/theme_controller.dart';
/// Widget responsável por carregar configurações iniciais da aplicação, como o tema.
class AppInitializer extends StatelessWidget {
const AppInitializer({super.key});
/// Carrega e inicializa o [ThemeController], buscando o tema salvo no armazenamento local.
Future<ThemeController> _loadTheme() async {
final controller = ThemeController();
await controller.init();
return controller;
}
Widget build(BuildContext context) {
return FutureBuilder<ThemeController>(
future: _loadTheme(),
builder: (context, snapshot) {
// Enquanto o tema ainda está sendo carregado, exibe um indicador de progresso.
if (!snapshot.hasData) return const CircularProgressIndicator();
final themeController = snapshot.data!;
// Injeta o ThemeController e a ThemeViewModel globalmente com MultiProvider.
return MultiProvider(
providers: [
ChangeNotifierProvider<ThemeController>.value(
value: themeController,
),
ChangeNotifierProvider<ThemeViewModel>(
create: (_) => ThemeViewModel(themeController),
),
],
child: const MyApp(),
);
},
);
}
}
7. Configure o widget principal
Arquivo: my_app.dart
/// Este arquivo define o widget [MyApp], que representa a raiz da árvore de widgets
/// da aplicação após a fase de inicialização.
///
/// A responsabilidade do [MyApp] é configurar o `MaterialApp`, definindo o título
/// da aplicação e a tela inicial a ser exibida.
///
/// Este widget pode ser estendido para configurar temas dinâmicos e rotas nominais
/// no futuro, tornando-o um ponto central para configurações globais da interface.
library;
import 'package:flutter/material.dart';
import 'package:flutter_exemplo_temas/view/home_view.dart';
/// Widget principal da aplicação que configura o `MaterialApp`.
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
/// Desativa o banner de modo de depuração.
debugShowCheckedModeBanner: false,
/// Define o título da aplicação.
title: 'Flutter Exemplo Temas',
/// Página inicial exibida ao iniciar o app.
home: HomeView(),
);
}
}
8. Ponto de entrada
Arquivo: main.dart
/// Este é o ponto de entrada da aplicação Flutter.
///
/// Sua responsabilidade principal é iniciar a execução do app chamando `runApp()`
/// com o widget raiz da aplicação, neste caso o [AppInitializer].
///
/// O [AppInitializer] é responsável por configurar dependências iniciais, como
/// o tema da aplicação, provedores de estado e qualquer outra configuração necessária
/// antes de renderizar a interface principal do usuário.
library;
import 'package:flutter/material.dart';
import 'package:flutter_exemplo_temas/bootstrap/app_initializer.dart';
/// Função principal que inicializa a aplicação.
void main() => runApp(const AppInitializer());
🎨 Como Usar no UI
1. Mostrar a cor primária como fundo de Container
Container(
color: context.theme.primaryColor,
padding: const EdgeInsets.all(16),
child: const Text(
'Tema Ativo',
style: TextStyle(fontSize: 20),
),
)
2. Mudar o tema ao clicar em um botão
context.theme.setTheme(ThemeType.india);
📦 Benefícios
- ✅ Suporte a múltiplos temas
- ✅ Persistência com
SharedPreferences - ✅ Extensível para temas escuros ou branding
🔗 Código Fonte e App de Exemplo
Acesse o repositório com o projeto completo e um app de exemplo funcional:

📌 Conclusão
Com essa estrutura, seu app Flutter estará pronto para oferecer múltiplos temas personalizados e persistentes. Esse tipo de experiência eleva o profissionalismo do app e mostra atenção aos detalhes visuais.
--