Aplicação Móvel
Arquitetura da Aplicação
Navegação, padrões de gestão de estado e camada de repositório
Arquitetura da Aplicação
Navegação — GoRouter
Todas as rotas estão declaradas em lib/src/core/router.dart. A aplicação usa um ShellRoute para a navegação de 5 separadores na barra inferior, com sub-rotas empilhadas por cima.
ShellRoute (navegação de 5 separadores)
├── / → HomeScreen
├── /meals → MealPlanScreen
├── /recipes → RecipesScreen
│ └── /recipes/:id → RecipeDetailScreen
├── /pantry → InventoryScreen
└── /groceries → ShoppingListScreen
/login → LoginScreen (fora da shell)Regras de navegação:
context.go('/path')— trocar de separador (substitui o histórico da shell)context.push('/path')— empilhar por cima do separador atual (sub-rotas)- Redireccionamento de login: utilizadores não autenticados são enviados para
/loginvia redireccionamento do GoRouter
Gestão de estado — Riverpod
Todos os ecrãs usam ConsumerWidget ou ConsumerStatefulWidget. Os dados são expostos como FutureProvider / StateProvider / StateNotifierProvider:
// Exemplo: provider de lista de receitas
final recipesProvider = FutureProvider.autoDispose<List<Recipe>>((ref) async {
final repo = ref.read(recipeRepositoryProvider);
return repo.getRecipes();
});
// No ecrã:
class RecipesScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final recipes = ref.watch(recipesProvider);
return recipes.when(
data: (data) => RecipeList(data),
loading: () => const CircularProgressIndicator(),
error: (e, _) => ErrorWidget(e.toString()),
);
}
}Camada de repositório
Cada funcionalidade tem uma classe de repositório que:
- Faz pedidos HTTP com Dio à API
- Analisa as respostas usando construtores factory
fromJson - Trata ambas as formas de resposta
{ "items": [...] }e[...]
class RecipeRepository {
final Dio _dio;
RecipeRepository(this._dio);
Future<List<Recipe>> getRecipes({String? query, String? category}) async {
final res = await _dio.get('/api/recipes', queryParameters: {
if (query != null) 'q': query,
if (category != null) 'category': category,
});
final raw = res.data;
final list = raw is List ? raw : (raw['items'] as List);
return list.map((j) => Recipe.fromJson(j as Map<String, dynamic>)).toList();
}
}Padrões de tratamento de erros
Todos os métodos fromJson dos modelos usam análise com segurança nula:
- IDs inteiros da base de dados são convertidos para
Stringvia?.toString() ?? '' - Campos opcionais recorrem a valores predefinidos:
json['field'] as String? ?? '' - Campos de lista usam conversão segura:
(json['list'] as List? ?? []).map(...) - Aliases de nomes de colunas:
json['location'] ?? json['storage_location']