Gauteng Wellbeing Mapper - Developer Guide

Overview

Gauteng Wellbeing Mapper is a privacy-focused Flutter mobile application that enables users to map their mental wellbeing in environmental & climate context. As part of the Planet4Health project case study, the app facilitates research into how environmental and climate factors impact psychological health by allowing users to correlate location data with mental wellbeing indicators.

This version is specifically configured for the Gauteng research study with end-to-end encryption for secure data transmission to research servers in Gauteng, South Africa.

App Configuration

Planet4Health Integration

This application supports the Planet4Health research initiative “Mental wellbeing in environmental & climate context”, which addresses the growing recognition that environmental and climate changes contribute to mental health challenges including climate-related psychological distress.

Table of Contents

  1. Architecture Overview
  2. Research Participation System
  3. Encryption & Security
  4. Flutter Background Geolocation - Critical Implementation Notes
  5. Core Components
  6. Data Flow
  7. File Structure
  8. Getting Started
  9. Development Workflow
  10. Server Setup
  11. Testing
  12. Screenshots & Visual Documentation
  13. Deployment

Architecture Overview

Wellbeing Mapper follows a modular Flutter architecture with clear separation of concerns:

┌─────────────────────────────────────────┐
│                 UI Layer                │
│  (Views, Widgets, User Interface)       │
├─────────────────────────────────────────┤
│              Business Logic             │
│   (Encryption, State, Controllers)      │
├─────────────────────────────────────────┤
│               Data Layer                │
│ (Models, Databases, Encrypted Uploads)  │
├─────────────────────────────────────────┤
│             Platform Layer              │
│  (Background Services, Native Plugins)  │
└─────────────────────────────────────────┘

Key Technologies Used

App Mode System

Current Beta Configuration

The app uses a flexible mode system that allows switching between different operational modes. During beta testing, the following modes are available:

enum AppMode {
  private,      // Personal use only
  appTesting,   // Beta testing of research features
  // research,  // Real research participation (disabled in beta)
}

Mode Configuration

File: lib/models/app_mode.dart

The beta/release status is controlled by a single boolean:

static const bool _isBetaPhase = true; // Set to false for research release

This automatically controls:

Mode Behaviors

Feature Private Mode App Testing Mode Research Mode (Future)
Location Tracking ✅ Local only ✅ Local only ✅ Encrypted upload
Surveys ✅ Local only ✅ Local testing ✅ Encrypted upload
Notifications ✅ Personal ✅ Testing intervals ✅ Research schedule
Data Upload ❌ Disabled ❌ Disabled ✅ Encrypted
Participant Code ❌ Not needed ❌ Auto-generated ✅ Required
Consent Flow ❌ Skipped ❌ Skipped ✅ Required

App Mode Service

File: lib/services/app_mode_service.dart

Provides centralized mode management:

// Get current mode
final mode = await AppModeService.getCurrentMode();

// Change mode
await AppModeService.setCurrentMode(AppMode.appTesting);

// Check capabilities
final hasResearchFeatures = await AppModeService.hasResearchFeatures();
final sendsData = await AppModeService.sendsDataToResearch();

Mode Switching UI

Users can switch modes through:

  1. Initial Welcome Screen: First-time mode selection
  2. Settings → Change Mode: Switch between available modes
  3. App Mode Service: Programmatic mode changes

Beta Testing Benefits

App Testing Mode allows users to:

Preparing for Research Release

To enable research mode:

  1. Update configuration:
    static const bool _isBetaPhase = false;
    
  2. This automatically enables:
    • Research mode in place of App Testing mode
    • Participant code requirements
    • Consent form workflows
    • Encrypted data uploads
    • Real research server connections

See Beta Testing Guide for complete release preparation instructions.

Encryption & Security

Hybrid Encryption System

The app uses a two-layer encryption approach for maximum security:

  1. AES-256-GCM: Encrypts the actual data (fast, efficient)
  2. RSA-4096-OAEP: Encrypts the AES key (secure key exchange)

Data Flow

Participant Data → JSON → AES Encrypt → RSA Encrypt Key → Base64 → HTTPS → Server
      ↓                      ↓              ↓               ↓        ↓
Survey Responses      Random AES Key   Research Team    Network   Secure Storage
Location Tracks       Per Upload       Public Key       Transit   Private Key Decrypt

Security Features

Key Management

Public keys are embedded in the app at build time:

// In lib/services/data_upload_service.dart
static const Map<String, ServerConfig> _serverConfigs = {
  'gauteng': ServerConfig(
    baseUrl: 'https://gauteng-server.com', 
    publicKey: '''-----BEGIN PUBLIC KEY-----
    [RSA-4096 PUBLIC KEY FOR GAUTENG]
    -----END PUBLIC KEY-----''',
  ),
};

Flutter Background Geolocation - Critical Implementation Notes

⚠️ IMPORTANT WARNINGS

DO NOT execute any API method which will require accessing location-services until the .ready(config) method has been called. This is a critical requirement for proper initialization of the background geolocation plugin.

License Key Configuration

The Flutter Background Geolocation plugin requires a license key that is tied to your app’s bundle ID. For this app:

<meta-data android:name="com.transistorsoft.locationmanager.license" 
           android:value="[LICENSE_KEY_HERE]" />

Why it’s safe to commit: The license key is cryptographically tied to the bundle ID com.github.activityspacelab.wellbeingmapper.gauteng and will not function with any other app package name, making it safe to include in version control.

Required Import Pattern

Always import the plugin with the bg namespace to avoid class name conflicts:

import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg;

Why this matters: The plugin uses common class names such as Location, Config, State that will conflict with other packages and Flutter’s built-in classes. You must access every flutter_background_geolocation class with the bg prefix (i.e., bg.Location, bg.Config, bg.State).

Proper Initialization Sequence

// ✅ CORRECT - Wait for ready() before any other API calls
await bg.BackgroundGeolocation.ready(bg.Config(
  // your configuration
)).then((bg.State state) {
  // Now safe to call other API methods
  if (!state.enabled) {
    bg.BackgroundGeolocation.start();
  }
});

// ❌ WRONG - Don't call API methods before ready()
bg.BackgroundGeolocation.start(); // This will fail!

Common Pitfalls to Avoid

  1. Calling API methods before ready(): Always wait for .ready() to complete
  2. Missing namespace: Import without as bg causes class name conflicts
  3. State access without prefix: Use bg.State, not State
  4. Location object confusion: Use bg.Location, not Flutter’s Location

For more details, see the official plugin documentation.

Core Components

1. Main Application (main.dart)

2. Home View (ui/home_view.dart)

3. Map View (ui/map_view.dart)

4. Database Management

Multiple SQLite databases handle different data types:

Unpushed Locations Database (db/database_unpushed_locations.dart)

5. Location Management (models/custom_locations.dart)

8. Data Sharing Preferences Management (ui/data_sharing_preferences_screen.dart)

9. Web Integration (ui/web_view.dart)

Data Flow

Location Tracking Flow

graph TD
    A[User Enables Tracking] --> B[Background Geolocation Service]
    B --> C[Location Event Triggered]
    C --> D[Create CustomLocation Object]
    D --> E[Geocode Address Data]
    E --> F[Store in Local Database]
    F --> G[Update Map View]
    G --> H[Display on Map]
    
    C --> I[Send to Research API]
    I --> J{API Success?}
    J -->|Yes| K[Mark as Synced]
    J -->|No| L[Store in UnpushedLocations DB]
    L --> M[Retry Later]

App Mode Usage Flow

graph TD
    A[App Launch] --> B[Check Participation Settings]
    B -->|None Set| C[Participation Selection Screen]
    B -->|Settings Found| D[Load App Mode]
    
    C --> E{User Selects Mode}
    E -->|Private Mode| F[Private Use]
    E -->|App Testing Mode| G[Beta Testing]
    E -->|Research Mode| H[Research Participation]
    
    F --> I[Local Location Tracking Only]
    I --> J[Personal Data Analysis]
    
    G --> K[Full Feature Testing]
    K --> L[Mock Research Workflows]
    K --> M[Feedback Collection]
    
    H --> N[Consent Process]
    N --> O[Real Research Participation]
    O --> P[Encrypted Data Upload]
graph TD
    A[Research Participant Completes Survey] --> B[Trigger Data Upload]
    B --> C[ConsentAwareDataUploadService]
    C --> D[Load Location Data & Create Clusters]
    D --> E[Show DataSharingConsentDialog]
    E --> F[User Sees Data Summary]
    F --> G{User Selects Sharing Option}
    
    G -->|Full Data| H[Upload All Location Data + Surveys]
    G -->|Partial Data| I[Show Location Cluster Selection]
    G -->|Survey Only| J[Upload Only Survey Responses]
    
    I --> K[User Selects/Deselects Location Areas]
    K --> L[Upload Filtered Location Data + Surveys]
    
    H --> M[Save Consent Decision]
    L --> M
    J --> M
    M --> N[Encrypt & Upload to Research Server]
    N --> O[Mark Data as Synced]

Background Processing Flow

graph TD
    A[App in Background/Terminated] --> B[Background Geolocation Plugin]
    B --> C[HeadlessTask Triggered]
    C --> D[Location Event Processing]
    D --> E[Store Location Data]
    E --> F[Sync with Server APIs]
    
    G[Background Fetch Service] --> H[Periodic Tasks]
    H --> I[Check for Pending Data]
    I --> J[Retry Failed Uploads]

File Structure

gauteng-wellbeing-mapper-app/lib/
├── main.dart                    # App entry point
├── main_debug.dart              # Debug configuration
├── main_original.dart           # Original entry point
├── main_simple.dart             # Simplified entry point  
├── shared_events.dart           # Event management system
├── styles.dart                  # Global styling
├── components/                  # Reusable UI components
├── db/                         # Database management
│   ├── database_unpushed_locations.dart # Failed sync storage
│   └── survey_database.dart       # Survey response storage
├── debug/                      # Debug utilities
├── external_projects/          # External project integrations
├── models/                     # Data models
│   ├── app_localizations.dart  # Internationalization
│   ├── app_mode.dart           # App mode definitions
│   ├── consent_models.dart     # Consent management models
│   ├── custom_locations.dart   # Location data models
│   ├── data_sharing_consent.dart # Data sharing models
│   ├── locations_to_push.dart # Sync queue model
│   ├── route_generator.dart    # Navigation routing
│   ├── survey_models.dart      # Survey data models
│   ├── wellbeing_survey_models.dart # Wellbeing survey models
│   └── statistics/             # Statistics models
├── mocks/                      # Test data and mocking
├── services/                   # Business logic services
│   └── notification_service.dart # Survey reminder system
├── theme/                      # App theming
├── ui/                        # User interface screens
│   ├── change_mode_screen.dart # App mode selection
│   ├── change_mode_screen_new.dart # New mode selection UI
│   ├── consent_form_screen.dart # Consent management
│   ├── data_sharing_consent_dialog.dart # Data sharing dialogs
│   ├── data_sharing_preferences_screen.dart # Privacy settings
│   ├── data_upload_screen.dart # Data upload interface
│   ├── help_screen.dart        # In-app help system
│   ├── home_view.dart         # Main screen
│   ├── initial_survey_screen.dart # Initial user survey
│   ├── list_view.dart         # Location history list
│   ├── map_view.dart          # Interactive map
│   ├── notification_settings_view.dart # Notification preferences
│   ├── participation_selection_screen.dart # Research participation
│   ├── recurring_survey_screen.dart # Recurring surveys
│   ├── report_issues.dart     # Bug reporting
│   ├── side_drawer.dart       # Navigation menu
│   ├── survey_list_screen.dart # Survey management
│   ├── web_view.dart          # Survey web view
│   ├── wellbeing_map_view.dart # Wellbeing mapping interface
│   ├── wellbeing_survey_screen.dart # Wellbeing surveys
│   ├── wellbeing_timeline_view.dart # Timeline visualization
│   ├── consent/               # Consent management UI
│   └── statistics/            # Statistics and analytics UI
├── ui_style/                  # UI styling components
└── util/                      # Utility functions
    ├── env.dart              # Environment configuration
    ├── dialog.dart           # Dialog utilities
    └── spacemapper_auth.dart # Authentication services

Key Features

1. Location Tracking

2. Research Participation

3. Data Management

4. User Interface

5. Notification System

Getting Started

Prerequisites

Quick Start

# Install FVM for Flutter version management
dart pub global activate fvm

# Clone the repository
git clone [repository-url]
cd gauteng-wellbeing-mapper-app/gauteng-wellbeing-mapper-app

# Use correct Flutter version
fvm use 3.27.1

# Install dependencies
fvm flutter pub get

# Run the app
fvm flutter run

Configuration

1. Environment Setup

Configure util/env.dart with appropriate server URLs and settings.

2. Permissions

Ensure location permissions are properly configured in platform files:

3. Research Server Setup (For Research Teams)

Generate RSA Key Pairs:

# For Gauteng research site  
openssl genrsa -out gauteng_private_key.pem 4096
openssl rsa -in gauteng_private_key.pem -pubout -out gauteng_public_key.pem

Update App Configuration: Edit lib/services/data_upload_service.dart and replace the placeholder public keys:

static const Map<String, ServerConfig> _serverConfigs = {
  'gauteng': ServerConfig(
    baseUrl: 'https://your-gauteng-server.com',
    uploadEndpoint: '/api/v1/participant-data', 
    publicKey: '''-----BEGIN PUBLIC KEY-----
[PASTE YOUR GAUTENG PUBLIC KEY HERE]
-----END PUBLIC KEY-----''',
  ),
};

Rebuild App:

fvm flutter clean
fvm flutter pub get
fvm flutter build apk --release

4. Server Setup

For detailed server setup instructions, see:

Server Setup

Research Data Collection Server

Each research site requires a secure HTTPS server with:

  1. REST API endpoint for encrypted data uploads
  2. Private key storage for data decryption
  3. Database for storing encrypted participant data
  4. Processing pipeline for decrypting and analyzing data

Minimum Server Requirements

API Endpoint Implementation

Node.js Example:

app.post('/api/v1/participant-data', async (req, res) => {
  const { uploadId, participantUuid, researchSite, encryptedData, encryptionMetadata } = req.body;
  
  try {
    // Store encrypted data (cannot be read without private key)
    await database.storeEncryptedUpload({
      uploadId,
      participantUuid,
      researchSite,
      encryptedPayload: encryptedData,
      metadata: encryptionMetadata,
      receivedAt: new Date()
    });
    
    res.json({ 
      success: true, 
      uploadId: uploadId,
      message: 'Data received and stored securely' 
    });
  } catch (error) {
    res.status(500).json({ success: false, error: 'Upload failed' });
  }
});

For complete server implementation, see Server Setup Guide.

Development Workflow

Adding New Features

  1. Model First: Define data models in models/ directory
  2. Database Layer: Add database operations in db/ directory
  3. Business Logic: Implement logic in appropriate service classes
  4. UI Implementation: Create UI components in ui/ directory
  5. Integration: Wire everything together through state management

Location Feature Development

// Example: Adding a new location-based feature
class CustomLocationService {
  // 1. Define the data model
  static Future<CustomLocation> processLocation(bg.Location rawLocation) async {
    // Process raw location data
    CustomLocation location = await CustomLocation.createCustomLocation(rawLocation);
    
    // 2. Store in database
    await CustomLocationsManager.storeLocation(location);
    
    // 3. Update UI
    NotificationCenter.broadcast('location_updated', location);
    
    return location;
  }
}

Project Integration Development

// Example: Adding a new research project
class NewProjectIntegration extends Project {
  @override
  void participate(BuildContext context, String locationData) {
    // 1. Configure project-specific parameters
    Map<String, String> projectConfig = {
      'projectId': 'new_project_001',
      'dataFormat': 'custom',
      'requiredFields': 'lat,lng,timestamp'
    };
    
    // 2. Process location data according to project requirements
    String processedData = processLocationData(locationData, projectConfig);
    
    // 3. Launch project interface
    Navigator.pushNamed(context, '/new_project_interface', 
        arguments: {'data': processedData, 'config': projectConfig});
  }
}

Testing

Unit Tests (test/unit/)

Widget Tests (test/widget/)

Integration Tests (integration_test/)

Running Tests

# Unit tests
flutter test test/unit/

# Widget tests
flutter test test/widget/

# Integration tests
flutter test integration_test/

Screenshots & Visual Documentation

Automated Screenshot System

The app includes a comprehensive screenshot system for documenting the user interface and generating visual documentation. This system is particularly useful for:

Quick Start

Generate screenshots using the automated system:

# Show instructions and setup environment
./generate_screenshots.sh

# Manual screenshot capture workflow
./generate_screenshots.sh --manual

# Automated capture (experimental)
./generate_screenshots.sh --automated

# Show detailed capture instructions
./generate_screenshots.sh --instructions

Key Features Captured

The screenshot system documents all major app workflows:

  1. 🔘 Participation Selection: Private and Gauteng research modes
  2. 📝 Survey Interface: Research surveys and form interactions
  3. 🗺️ Map Views: Location tracking and visualization
  4. ⚙️ Settings: Data upload, privacy controls, and configuration
  5. 📊 Data Management: Upload status and encryption indicators
  6. ℹ️ Information Screens: Consent forms, privacy policy, help

Screenshot Organization

Screenshots are automatically organized into:

Integration with Development

The screenshot system integrates with the development workflow:

# Capture screenshots after feature implementation
./generate_screenshots.sh --automated

# Update visual documentation
git add screenshots/
git commit -m "Update app screenshots for new feature"

For detailed instructions and advanced usage, see screenshots/README.md.

Deployment

Android Deployment

  1. Configure android/key.properties with signing keys
  2. Build release APK: flutter build apk --release
  3. Build App Bundle: flutter build appbundle --release

iOS Deployment

  1. Configure signing in Xcode
  2. Build for release: flutter build ios --release
  3. Archive and upload through Xcode

CI/CD Pipeline

The project includes GitHub Actions workflows:

Privacy Considerations

Wellbeing Mapper is designed with privacy as a core principle:

  1. Local Storage: All location data is stored locally by default
  2. Explicit Consent: Users must explicitly agree to share data
  3. Anonymization: Shared data uses random UUIDs, not personal identifiers
  4. User Control: Users can delete their data at any time
  5. Transparency: Clear information about what data is collected and how it’s used

Troubleshooting

Common Issues

Location Not Updating:

Data Not Syncing:

Debug Mode

Enable debug mode by setting debug: true in background geolocation configuration to see detailed logging.

Enhanced Notification System

Overview

The notification system provides recurring survey reminders to encourage user participation in research studies. The system has been significantly enhanced to use a dual-notification approach combining device-level notifications with in-app dialogs for maximum research reliability. It uses the existing background_fetch infrastructure for scheduling and now supports cross-platform device notifications.

Architecture

┌─────────────────────────────────────────┐
│      Enhanced NotificationService       │
│  - Dual notification strategy           │
│  - Device-level notifications           │
│  - Schedule management                  │
│  - Permission handling                  │
│  - Testing & diagnostics               │
├─────────────────────────────────────────┤
│     flutter_local_notifications        │
│  - Cross-platform device notifications │
│  - Android/iOS platform integration    │
│  - Permission management               │
├─────────────────────────────────────────┤
│         BackgroundFetch                 │
│  - Periodic execution                   │
│  - Headless operation                   │
│  - Cross-platform scheduling            │
├─────────────────────────────────────────┤
│         SharedPreferences               │
│  - Notification timestamps              │
│  - User preferences                     │
│  - Statistics storage                   │
└─────────────────────────────────────────┘

Key Components

1. Enhanced NotificationService

2. Background Integration

3. User Interface

Implementation Details

Scheduling Logic

// Check every hour for notification timing
await BackgroundFetch.scheduleTask(TaskConfig(
  taskId: 'com.wellbeingmapper.survey_notification',
  delay: 3600000, // 1 hour in milliseconds
  periodic: true,
  // ... other configuration
));

Enhanced Notification Flow

  1. Background task executes hourly
  2. Checks time since last notification
  3. If ≥14 days, triggers dual notification strategy:
    • Device notification: Immediate system notification sent
    • Pending flag: Sets in-app dialog flag as backup
  4. User sees notification even if app is closed
  5. When app opens, checks for pending prompts (fallback)
  6. Shows survey dialog if prompt is pending

New Testing & Diagnostics Methods

// Test device notifications
await NotificationService.testDeviceNotification();

// Test in-app notifications  
await NotificationService.testInAppNotification(context);

// Check notification permissions
bool hasPermissions = await NotificationService.checkNotificationPermissions();

// Get comprehensive diagnostics
Map<String, dynamic> diagnostics = await NotificationService.getDiagnostics();

Data Storage

Enhanced Usage Examples

Initialize Enhanced Service

// In main.dart
await NotificationService.initialize();

Check Device Notification Permissions

// Check permissions before relying on device notifications
bool hasPermissions = await NotificationService.checkNotificationPermissions();
if (!hasPermissions) {
  // Handle permission request or inform user
}

Check for Pending Prompts

// In home_view.dart initState
bool hasPendingPrompt = await NotificationService.hasPendingSurveyPrompt();
if (hasPendingPrompt) {
  await NotificationService.showSurveyPromptDialog(context);
}

Research Team Testing & Diagnostics

// Test device notifications
await NotificationService.testDeviceNotification();

// Test in-app notifications  
await NotificationService.testInAppNotification(context);

// Get comprehensive diagnostics
Map<String, dynamic> diagnostics = await NotificationService.getDiagnostics();
print('Platform: ${diagnostics['systemInfo']['platform']}');
print('Device notifications enabled: ${diagnostics['deviceNotificationsEnabled']}');
print('System initialized: ${diagnostics['notificationSystemInitialized']}');

Notification System Features

Beta Testing Capabilities

The notification system includes comprehensive testing features designed for beta testing and development:

Testing Intervals

Testing Tools Available in Beta

Device Notification Testing:

In-App Dialog Testing:

Permission Diagnostics:

Beta Testing Workflow

  1. Set Testing Interval: Use notification settings to set 1-5 minute intervals
  2. Test Notifications: Verify device notifications appear correctly
  3. Test Navigation: Tap notifications to ensure proper survey navigation
  4. Test Dialogs: Verify in-app prompts show when expected
  5. Reset to Production: Clear testing intervals when done

Notification Settings Screen

File: lib/ui/notification_settings_view.dart

Provides comprehensive notification management:

Research Release Considerations

For the full research release:

Recent Updates and Changes

App Mode System (Latest)

Notification System Enhancement

Welcome Screen Updates

Documentation Improvements

User Preference Management

// Enable notifications
await NotificationService.enableNotifications();

// Disable notifications
await NotificationService.disableNotifications();

// Reset schedule
await NotificationService.resetNotificationSchedule();

Dependencies

Google Play Store Compliance

Inexact Alarm Implementation

Important: As of July 2025, the notification system has been updated to comply with Google Play Store policies regarding exact alarm permissions.

Changes Made:

Technical Implementation:

// Before (restricted by Google Play)
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,

// After (Google Play compliant)
androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle,

Impact on Functionality:

Why This Works for Surveys:

Testing

Trigger Notification Manually

Use the notification settings screen to trigger a survey prompt immediately for testing purposes.

Reset Schedule

Reset the notification schedule through settings to test first-time user experience.

Background Testing

Test background functionality by:

  1. Triggering background fetch manually
  2. Checking SharedPreferences for updated timestamps
  3. Verifying prompt appears on next app launch

Configuration

Notification Interval

static const int _notificationIntervalDays = 14; // 2 weeks

Background Task Frequency

delay: 3600000, // Check every hour

Future Enhancements

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run tests: flutter analyze && flutter test
  5. Submit a pull request

For more information, see the main README.md file.