Decisões Técnicas
1. Arquitetura de Estado
Decisão
Usar StatefulWidget
com setState
ao invés de gerenciamento de estado global (Provider, Bloc, Riverpod, etc.)
Justificativa
- Aplicação simples com estado local bem definido
- Comunicação entre componentes através de callbacks
- Evita complexidade desnecessária
- Performance adequada para casos de uso esperados
- Menos dependências externas
Trade-offs
Vantagens:
- Código mais simples e direto
- Menos boilerplate
- Fácil de entender para iniciantes
- Sem curva de aprendizado adicional
Desvantagens:
- Mais callbacks entre componentes
- Estado espalhado por múltiplos widgets
- Mais difícil de escalar para funcionalidades complexas futuras
- Testes mais desafiadores
Alternativas Consideradas
- Provider: Muito boilerplate para esta aplicação
- Bloc: Complexidade desnecessária
- Riverpod: Overkill para o escopo
2. Debouncing no Editor
Decisão
Implementar debounce de 300ms nas atualizações do editor.
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
final text = _codeController.text;
widget.onJsonChanged(text);
_validateJson(text);
});
Justificativa
- Reduz chamadas desnecessárias ao parser JSON
- Melhora performance durante digitação rápida
- Evita renderizações intermediárias de JSON inválido
- 300ms é imperceptível ao usuário mas significativo para performance
Medições
Delay | Chamadas/seg (digitação rápida) | Percepção do usuário |
---|---|---|
0ms | ~30-50 | Lag visível |
100ms | ~10 | Leve lag |
300ms | ~3-4 | Imperceptível |
500ms | ~2 | Lag perceptível |
Alternativas Consideradas
- 100ms: Ainda causa muitas chamadas
- 500ms: Usuário percebe atraso na resposta
3. Sincronização Bidirecional
Decisão
Implementar sincronização em tempo real usando flags de controle.
// HomePage
bool _isUpdatingFromCanvas = false;
// JsonEditorWidget
bool _isUpdatingFromExternal = false;
String _lastExternalData = '';
Justificativa
- Experiência de usuário fluida (mudanças imediatas)
- Mantém JSON e visual sempre sincronizados
- Flags previnem loops infinitos
- Cache evita atualizações redundantes
Mecanismo de Proteção
- HomePage Flag: Previne notificação circular quando canvas atualiza
- Editor Flag: Previne notificação quando recebe dados externos
- Cache: Evita atualizações com mesmo conteúdo
Complexidade
Risco: Condições de corrida se flags não forem gerenciadas corretamente
Mitigação: Future.microtask
para operações assíncronas controladas
4. Sistema de Escala
Decisão
Implementar coordenadas de design fixo com escala proporcional.
double _getElementScaleFactor() {
return math.min(canvasWidth / designWidth, canvasHeight / designHeight);
}
Justificativa
- Designers trabalham em resolução fixa (ex: 1080x1920)
- Canvas escala automaticamente para diferentes tamanhos
- Coordenadas no JSON são intuitivas e previsíveis
- Mantém proporções do design original
Exemplo
Design em 1080x1920
, renderizado em 360x640
:
- Scale factor:
0.333
- Elemento em
x: 540
renderiza em180
- Font size
24
renderiza como8
Alternativa Considerada
Coordenadas relativas (0.0 a 1.0): Menos intuitivo para designers
5. Parsing de Cores
Decisão
Suportar apenas formato hexadecimal.
Color _parseColor(String hexColor) {
hexColor = hexColor.replaceAll('#', '');
if (hexColor.length == 6) hexColor = 'FF$hexColor';
return Color(int.parse('0x$hexColor'));
}
Justificativa
- Formato mais comum em design/CSS
- Fácil de converter de/para
- Suporte nativo no Dart/Flutter
- Menos ambiguidade
Não Suportado
- Nomes de cores (“red”, “blue”)
- Formato
rgb()
/rgba()
- Formato
hsl()
/hsla()
Alternativa Considerada
Múltiplos formatos: Aumenta complexidade sem benefício significativo
6. Google Fonts Dinâmico
Decisão
Carregar fontes do Google Fonts em tempo de execução.
TextStyle textStyle = GoogleFonts.getFont(
fontFamily,
fontSize: fontSize,
fontWeight: fontWeight,
);
Justificativa
- Acesso a 1400+ fontes sem empacotar
- Tamanho do app reduzido (~15MB economizados)
- Fontes sempre atualizadas
- Simplicidade para usuários
Trade-offs
Vantagens:
- App mais leve
- Mais fontes disponíveis
- Sem necessidade de licenciamento
Desvantagens:
- Requer conexão à internet (primeira vez)
- Possível latência no carregamento
- Fallback para fonte padrão em caso de erro
7. Estrutura de Elementos como Map
Decisão
Usar List<Map<String, dynamic>>
ao invés de classes tipadas.
List<Map<String, dynamic>> elements = [];
Justificativa
- Flexibilidade para adicionar propriedades
- JSON parse/stringify direto
- Experimentação rápida com novos tipos
- Menos boilerplate
Trade-offs
Vantagens:
- Desenvolvimento mais rápido
- Fácil adicionar novos tipos de elementos
- Serialização trivial
Desvantagens:
- Sem type-safety (erros em runtime)
- IDE não oferece autocompletar
- Documentação é crítica
- Refatoração mais difícil
Alternativa Considerada
Classes tipadas com json_serializable: Muito boilerplate para a flexibilidade desejada
8. Renderização com Stack e Positioned
Decisão
Usar Stack
com Positioned
para layout.
Stack(
fit: StackFit.expand,
children: elements.map((el) => Positioned(
left: x,
top: y,
child: elementWidget,
)).toList(),
)
Justificativa
- Posicionamento absoluto corresponde a JSON (x, y)
- Controle total sobre z-index via ordem
- Performance adequada para número razoável de elementos
- Simplicidade de implementação
Limitações
- Não escala para centenas de elementos
- Cada mudança requer rebuild completo
- Sem virtualização/recycling
Performance
Número de Elementos | FPS | Memória | Experiência |
---|---|---|---|
0-20 | 60 | ~50MB | Excelente |
20-50 | 60 | ~80MB | Ótima |
50-100 | 45-60 | ~120MB | Boa |
100+ | 30-45 | ~200MB+ | Degradada |
9. Gesture Handling por Widget
Decisão
Envolver cada elemento com GestureDetector
individual.
Widget _wrapWithDragGesture(Widget child, int elementIndex) {
return GestureDetector(
onPanStart: (details) => _onPanStart(details, elementIndex),
onPanUpdate: (details) => _onPanUpdate(details, elementIndex),
onPanEnd: _onPanEnd,
child: child,
);
}
Justificativa
- Cada elemento gerencia próprias interações
- Fácil associar gesto a elemento específico
- Permite comportamentos customizados por tipo
Trade-offs
Vantagens:
- Código mais modular
- Fácil adicionar novos gestos
- Contexto do elemento disponível
Desvantagens:
- Overhead de múltiplos GestureDetectors
- Conflitos potenciais entre gestos sobrepostos
- Mais complexo coordenar gestos globais
10. Validação JSON Inline
Decisão
Validar através de try-catch no parse.
try {
const JsonDecoder().convert(jsonString);
// Válido
} catch (e) {
// Inválido
}
Justificativa
- Parser já faz validação completa
- Não duplicar lógica
- Mensagens de erro descritivas
- Performance: uma operação
Limitações
- Mensagens podem ser técnicas
- Não valida estrutura específica (canvas, elements)
- Sem validação de schema
Alternativa Considerada
JSON Schema Validation: Overhead significativo para pouco benefício
11. Sem Undo/Redo
Decisão
Não implementar sistema de undo/redo na versão inicial.
Justificativa
- Complexidade significativa
- Requer sistema de command pattern
- Estado precisa ser serializado
- JSON pode ser copiado manualmente
Trade-off
Usuário experiente: Pode usar Ctrl+Z no editor Usuário iniciante: Precisa copiar JSON antes de mudanças
Roadmap
Planejado para fase futura com:
- Command pattern
- History stack
- Limite de memória
- Serialização eficiente
12. Plataforma Web First
Decisão
Focar em web (Chrome) como plataforma primária.
Justificativa
- Deploy mais simples (GitHub Pages)
- Sem instalação necessária
- Compartilhamento fácil
- Melhor para editor de código
Limitações
- Desktop/Mobile não testado
- Interface não otimizada para touch
- Possíveis problemas de compatibilidade
Próximos Passos
- Veja Arquitetura para entender estrutura
- Explore Componentes para implementação
- Confira Instalação para começar