Mobile App Architecture
Overview of the mobile app's architecture and design patterns.
Architecture Pattern
The app follows a layered architecture with clear separation of concerns:
┌─────────────────────────────────┐
│ UI Layer │
│ (Screens, Widgets, Components) │
└──────────────┬───────────────────┘
│
┌──────────────▼───────────────────┐
│ State Management │
│ (Provider) │
└──────────────┬───────────────────┘
│
┌──────────────▼───────────────────┐
│ Service Layer │
│ (API calls, Business Logic) │
└──────────────┬───────────────────┘
│
┌──────────────▼───────────────────┐
│ Data Layer │
│ (Models, Local Storage) │
└───────────────────────────────────┘
Key Components
Screens (lib/screens/)
UI screens for different app features:
- Authentication flows
- Provider dashboard
- Service seeker home
- Job management
- Messaging
- Profile management
Providers (lib/providers/)
State management using Provider pattern:
AuthProvider- User authentication stateLocationProvider- Location services- Other feature-specific providers
Services (lib/services/)
Business logic and API integration:
AuthService- AuthenticationProviderService- Provider operationsServiceSeekerService- Service seeker operationsChatService- Messaging- API configuration
Models (lib/models/)
Data models representing entities:
- User models
- Job models
- Message models
State Management
The app uses Provider for state management.
Example Usage
// Accessing provider
final authProvider = Provider.of<AuthProvider>(context, listen: false);
// Updating state
authProvider.setPhoneNumber(phoneNumber);
// Listening to changes
Consumer<AuthProvider>(
builder: (context, authProvider, child) {
return Text(authProvider.phoneNumber ?? 'No phone');
},
)
Navigation
Uses GoRouter for declarative routing.
Route Configuration
Routes are defined in the main app file, typically using GoRouter's route configuration.
Navigation Example
// Navigate to route
context.go('/dashboard');
// Push route
context.push('/job-details');
// Pop route
context.pop();
API Integration
API Configuration
Base API configuration in lib/config/api_config.dart:
class ApiConfig {
static const String baseUrl = 'https://api.thepluggnamibia.com';
static String get sendOtpEndpoint => '$baseUrl/api/auth/send-otp';
// ... other endpoints
}
Making API Calls
Services use the http package:
final response = await http.post(
Uri.parse(ApiConfig.sendOtpEndpoint),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'phone': phone, 'role': role}),
);
Data Persistence
SharedPreferences
Used for storing:
- User session data
- Authentication tokens
- User preferences
- Onboarding status
Example
final prefs = await SharedPreferences.getInstance();
await prefs.setString('userId', userId);
final storedUserId = prefs.getString('userId');
Error Handling
- Try-catch blocks in async operations
- Error messages displayed via toast notifications
- Loading states for async operations
Best Practices
- Separation of Concerns: Keep UI, business logic, and data separate
- Reusable Widgets: Extract common UI into reusable components
- Error Handling: Always handle errors gracefully
- Loading States: Show loading indicators during async operations
- State Management: Use Provider for shared state, local state for component-specific state