Skip to main content

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 state
  • LocationProvider - Location services
  • Other feature-specific providers

Services (lib/services/)

Business logic and API integration:

  • AuthService - Authentication
  • ProviderService - Provider operations
  • ServiceSeekerService - Service seeker operations
  • ChatService - 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');
},
)

Uses GoRouter for declarative routing.

Route Configuration

Routes are defined in the main app file, typically using GoRouter's route configuration.

// 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

  1. Separation of Concerns: Keep UI, business logic, and data separate
  2. Reusable Widgets: Extract common UI into reusable components
  3. Error Handling: Always handle errors gracefully
  4. Loading States: Show loading indicators during async operations
  5. State Management: Use Provider for shared state, local state for component-specific state