MyFriends - Emergency SOS & Contact Management App
Production-ready emergency SOS app with multi-layered persistent notification system (foreground + background + 60 scheduled alarms), real-time location sharing, and comprehensive contact management using Flutter and Firebase.
Role
Full Stack Mobile Developer
Client
Academic Project - Mobile Programming Course
Team
3-person Team
Timeline
2 months • 2025

Skills & Tools
Skills Applied
Tools & Software
Final Results








Project Overview
MyFriends is an emergency SOS mobile application that enables users to instantly notify trusted contacts with real-time location during emergencies. The app features a production-grade multi-layered notification system that guarantees alert delivery even when the device is on Do Not Disturb mode or the app is terminated.
Key Challenge: Ensure emergency contacts receive loud, persistent alerts across all phone states (foreground, background, terminated, DND mode, battery saver).
Solution: Three independent notification mechanisms:
- Foreground Timer:
Timer.periodicfor active app alerts - Background Isolate Handler: Top-level function for terminated app
- 60 Scheduled Exact-Time Alarms:
AndroidScheduleMode.exactAllowWhileIdlebypasses Doze mode
Technical Architecture
Core Technology Stack
Framework: Flutter 3.27.2 (Dart 3.6.1)
Firebase Services:
firebase_auth: ^6.1.1 # Email/password authentication
cloud_firestore: ^6.0.3 # Real-time NoSQL database
firebase_storage: ^13.0.3 # Photo uploads
firebase_messaging: ^16.0.3 # Push notifications (FCM)
Key Packages:
provider: ^6.1.2 # State management
geolocator: ^13.0.2 # GPS location
flutter_local_notifications: ^18.0.1 # Alarm-based notifications
timezone: ^0.9.4 # Scheduled notifications
image_picker: ^1.1.2 # Camera/gallery
csv: ^6.0.0 # Data export
State Management Pattern
Provider with ProxyProvider Architecture
The app uses a sophisticated cascading dependency injection pattern where multiple providers depend on the authentication state. The architecture consists of four main providers:
- AuthProvider (Independent): Manages user authentication state and Firebase Auth operations
- ContactProvider (Depends on Auth): Handles contact CRUD operations with user-scoped queries
- GroupProvider (Depends on Auth): Manages group creation and membership
- SOSProvider (Depends on Auth): Controls emergency alert lifecycle
ProxyProvider Implementation: Each dependent provider automatically receives the current user ID when authentication state changes. This prevents null reference errors and ensures all Firestore queries are properly scoped to the logged-in user. When a user logs in, the auth provider notifies all dependent providers to update their user context, triggering fresh data fetches.
Benefits:
- Automatic user ID propagation across all providers
- Clean separation of concerns
- Prevents data leakage between users
- Simplifies logout flow (user ID becomes null, all providers clear data)
Multi-Layered Notification System
The Three-Layer Architecture
Layer 1: Foreground Timer (App Active)
Purpose: Provides immediate, aggressive alerts when the app is actively running in the foreground.
Implementation: Uses Dart's Timer.periodic to trigger notifications every 3 seconds. The notification is configured with maximum importance and priority to ensure visibility. Key features include:
- Alarm Audio Stream: Bypasses Do Not Disturb mode by using
AudioAttributesUsage.alarm, treating the notification as a critical system alarm - Ongoing Notification: Set as non-dismissible, preventing users from accidentally swiping away emergency alerts
- Custom Vibration Pattern:
[0, 1000, 500, 1000]creates attention-grabbing haptic feedback (1 second vibrate, 0.5s pause, 1s vibrate) - Counter System: Tracks alert count up to 60 alerts, providing visual feedback on alert duration
- Notification ID 99999: Uses a high, unique ID to avoid conflicts with other app notifications
Layer 2: Background Isolate Handler (App Terminated)
Purpose: Ensures alert delivery even when the app is completely closed or terminated by the system.
Technical Architecture:
Firebase Cloud Messaging can wake up a terminated app to execute a background isolate (separate execution context). This handler must be a top-level function (not inside a class) with the @pragma('vm:entry-point') annotation to prevent Dart's ahead-of-time compiler from tree-shaking it out of the compiled binary.
Execution Flow:
- FCM receives push notification while app is terminated
- Android OS creates a new Dart isolate and calls the background handler
- Handler initializes Firebase services independently
- Checks notification type from FCM data payload
- For active SOS: starts a
Timer.periodicsimilar to Layer 1 - For cancelled/resolved SOS: calls
stopSOSAlert()to prevent spam - Isolate remains alive as long as the timer is running
Smart Cancellation Logic: The handler inspects the notification type before processing. If receiving a sos_cancelled or sos_resolved message, it immediately stops all alert mechanisms instead of starting new ones, preventing the common bug of "resolved notifications triggering spam."
Layer 3: Scheduled Exact-Time Alarms (Guaranteed Persistence)
Purpose: The ultimate fallback layer that guarantees alert delivery regardless of app state or battery optimization.
Android Battery Optimization Challenge: Modern Android aggressively kills background processes and enters "Doze mode" to preserve battery. Standard notifications and timers are delayed or dropped entirely. This makes Layers 1 and 2 unreliable in extreme cases (e.g., phone in deep sleep for hours).
Solution - Exact Alarms:
Android's AlarmManager API with exactAllowWhileIdle mode is specifically designed for critical time-sensitive operations (like medication reminders or emergency alerts). It wakes the device from Doze mode at the exact scheduled time, bypassing all battery optimizations.
Implementation Details:
- 60 Pre-scheduled Notifications: At the moment SOS is triggered, the app schedules 60 individual alarm-based notifications, each with a unique ID (100000-100060)
- 3-Second Intervals: Each alarm is scheduled for exactly 3, 6, 9, 12... up to 180 seconds from the trigger time
- Total Duration: 60 notifications × 3 seconds = 180 seconds (3 minutes) of guaranteed alerts
- Full-Screen Intent: Each notification can wake the lock screen and display prominently
- LED Alerts: Red LED flashing pattern for visual notification when phone is face-down
- Unique IDs: Each scheduled notification has a distinct ID, enabling individual cancellation without affecting others
Why This Works: Even if the app process is killed, Layers 1 and 2 fail, and the phone is in airplane mode, these 60 pre-scheduled alarms will still fire because they're registered with the Android OS itself, not dependent on the app process or network connectivity.
SOS Cancellation Logic
Challenge: When an SOS is cancelled or resolved, all three independent notification layers must be stopped to prevent continued alerts.
Cancellation Flow:
- Update Firestore Status: Change SOS document status to 'cancelled' or 'resolved' with timestamp
- Send FCM Cancellation Notice: Firebase Cloud Messaging sends push notification with
type: 'sos_cancelled'to all recipients - Stop Layer 1 (Foreground Timer): Cancel the
Timer.periodicinstance if it exists - Stop Layer 2 (Background Timer): Cancel the background isolate's timer
- Stop Layer 3 (Scheduled Alarms): Iterate through all 60 scheduled notification IDs (99999 + 1 through 99999 + 60) and cancel each individually
- Clear Main Notification: Remove the persistent notification from the notification tray (ID 99999)
- Set Stop Flag: Update internal state to prevent new alerts from starting
Complexity: The cancellation must work even if only one layer is active. For example, if the recipient's app is terminated, only the background handler will be running. The handler must detect the sos_cancelled message and call stopSOSAlert() to cancel the scheduled alarms.
Notification ID Strategy
| ID Range | Purpose | Layer |
|---|---|---|
| 99999 | Main SOS notification | 1 & 2 |
| 100000-100060 | 60 scheduled alarms | 3 |
Real-time Location Services
High-Accuracy GPS Implementation
Location Acquisition Process:
The app uses a multi-step process to obtain the most accurate possible location before sending an SOS:
Step 1: Service Availability Check Verifies that the device's GPS/location services are enabled. If disabled, throws a user-friendly error prompting them to enable GPS in system settings.
Step 2: Permission Management Checks current location permission status. If permission is denied, requests it from the user with a runtime permission dialog. Handles three states:
- Denied: Shows permission request dialog
- Denied Forever: Directs user to app settings since runtime request is no longer possible
- Granted: Proceeds to location acquisition
Step 3: High-Accuracy Position Request
Requests location with LocationAccuracy.high setting, which uses GPS satellites instead of just network triangulation. Sets a 10-second timeout to prevent indefinite waiting in poor GPS conditions.
Step 4: Google Maps URL Generation
Constructs a universal Google Maps URL using the maps/search API with latitude/longitude query parameters. This URL opens directly in the Google Maps app (if installed) or web browser, showing the sender's location with a pin.
Real-World Accuracy Results:
- Outdoor (clear sky): 8-12 meters accuracy, acquired in 2-4 seconds - optimal for emergency scenarios
- Indoor near window: 15-25 meters accuracy, 4-7 seconds - acceptable for building-level precision
- Indoor deep building: 30-50 meters accuracy, 6-10 seconds - may identify wrong building floor but correct general area
Timeout Strategy: The 10-second limit balances accuracy with urgency. In true emergencies, a less accurate location (from network triangulation) is better than no location due to waiting for perfect GPS lock.
Emergency Contact Matching
Challenge: Users add contacts by phone number, but Firebase Cloud Messaging requires Firebase user IDs to send push notifications. How do we match local contacts with registered app users?
Solution: Email-Based Cross-Reference System
Architecture:
- Contact Storage: Users save emergency contacts with name, phone, and email fields
- User Registration: When users register via Firebase Auth, their email is stored in the
userscollection along with their FCM token - SOS Trigger Matching: When SOS is triggered, query Firestore
userscollection for documents whereemailfield matches each contact's email - User ID Extraction: For each match found, extract the Firebase user ID (document ID) and FCM token
- Self-Exclusion: Filter out the sender's own user ID to prevent self-notification
Why Email Instead of Phone Number?
Firebase Auth Integration: Firebase Authentication uses email as the primary unique identifier. Every user account has an email, but phone number auth is optional.
Phone Number Challenges:
- Format Inconsistency: Indonesian numbers can be formatted as
08123456789,+6281234567, or62812345678- hard to match reliably - Number Portability: Users can change phone numbers but keep the same email
- International Format:
+symbols and country codes create matching complexity - Database Indexing: Email queries are simpler and faster in Firestore
Email Benefits:
- Universally Unique: No formatting issues, one canonical form
- Stable Identity: Users rarely change emails compared to phone numbers
- Firebase Native: Already indexed in Firestore for fast lookups
- Cross-Device: Email identifies the user across multiple devices with same account
Limitation Acknowledgment: This system only works if emergency contacts are also registered app users. Contacts not registered in the app won't receive SOS notifications. Future enhancement could add SMS fallback for non-app-users.
SOS Flow Diagram
1. User slides SOS button (80% threshold)
↓
2. Check emergency contacts exist
↓
3. Request location permission
↓
4. Get GPS coordinates (high accuracy, 10s timeout)
↓
5. Match emergency contacts by email → Get user IDs
↓
6. Create SOS document in Firestore with location
↓
7. FCM sends push to all emergency contact tokens
↓
8. Recipients receive FCM message
↓
9. Start 3 notification layers:
- Foreground: Timer.periodic every 3s
- Background: Background handler
- Scheduled: 60 exact-time alarms
↓
10. Notifications show with:
- Alarm audio (bypasses DND)
- Vibration pattern
- Full-screen intent (lock screen)
- Action buttons (View Location, Mute)
↓
11. Cancel SOS → Update Firestore status
↓
12. Send FCM with type='sos_cancelled'
↓
13. Recipients call stopSOSAlert() → Cancel all 3 layers
Custom Slide-to-Send Widget
Design Philosophy: Prevent accidental SOS triggers while maintaining emergency accessibility.
UX Challenge: Emergency buttons face a paradox - they must be easily accessible in crisis situations but difficult to trigger accidentally (pocket touches, children playing with phone, etc.).
Solution - Gesture-Based Activation:
The app uses a custom slide-to-send interaction pattern instead of a simple tap button. This requires intentional, sustained interaction that's unlikely to happen by accident.
Interaction Design:
Visual States:
- Idle State: Pulsing animation (scale 1.0 → 1.1) draws attention without being distracting
- Sliding State: Background gradient intensifies from red to lighter red as user drags
- Text Feedback: Changes from "Slide to Send SOS" to "Keep Sliding..." during interaction
Gesture Recognition:
- Horizontal Drag Detection: Tracks finger movement across the screen
- 80% Threshold Rule: User must drag at least 80% of the track width to trigger SOS
- Snap-Back Animation: If released before 80%, button smoothly returns to start position
- Haptic Feedback: Vibration confirmation when SOS is successfully triggered
Technical Implementation:
- Uses Flutter's
GestureDetectorfor drag events AnimationControllerdrives the pulse effect with sine wave curveTransform.translatemoves the draggable button based on drag delta- Stack layout allows button to slide over background track
Accessibility Considerations:
- Large 70-pixel circular button for easy grabbing
- High contrast warning icon (amber on white)
- Clear text instructions
- Visual progress indicator (track fill percentage)
Loading State: When SOS send is in progress, the button shows a circular progress indicator, preventing duplicate submissions and providing feedback that the action is processing.
Data Models
Contact Model Architecture
Purpose: Represents a user's contact with comprehensive metadata for emergency scenarios.
Key Fields:
id: Firestore document ID (auto-generated)nama&nomor: Basic contact information (Indonesian field names for local context)email: Critical field used for emergency contact matching across Firebase usersphotoUrl: Firebase Storage download URL for profile pictureisEmergency: Boolean flag marking this contact as emergency responderuserId: Owner's Firebase Auth UID for data scoping (ensures users only see their contacts)groupIds: Array of group IDs implementing many-to-many relationshipnote: Private notes field for additional context (max 500 characters)createdAt&updatedAt: Timestamps for audit trail
Firestore Serialization:
The model includes toMap() for writing to Firestore and fromFirestore() factory constructor for reading. DateTime objects are converted to Firestore Timestamp format for proper timezone handling and querying capabilities.
Immutability Pattern:
Includes copyWith() method for creating modified copies without mutating the original instance, following Flutter best practices for state management.
SOS Message Model
Purpose: Represents an active emergency alert with location data and recipient tracking.
Core Structure:
- Sender Information:
senderId,senderName,senderPhonefor identifying who triggered the SOS - Location Data: Nested
SOSLocationobject containing latitude, longitude, and GPS accuracy radius in meters - Google Maps URL: Pre-constructed deep link for instant navigation
- Status Tracking:
'active','cancelled', or'resolved'with corresponding timestamps - Recipients:
emergencyContactIdsarray of Firebase user IDs who should receive alerts - Audit Fields:
createdAt,cancelledAt,resolvedAt,resolvedByfor complete emergency timeline
SOSLocation Submodel: Stores GPS coordinates with accuracy metadata. Accuracy field is crucial - recipients can assess reliability (8m accuracy vs 50m accuracy indicates outdoor vs indoor location).
Firestore Schema
firestore/
├── users/{userId}
│ ├── email, displayName, phoneNumber
│ ├── fcmToken, photoURL
│ └── timestamps
├── contacts/{contactId}
│ ├── userId (indexed)
│ ├── nama, nomor, email, photoUrl
│ ├── isEmergency (indexed)
│ ├── groupIds (indexed, array)
│ └── timestamps
├── groups/{groupId}
│ ├── userId (indexed)
│ ├── nama, colorHex
│ └── timestamps
└── sos_messages/{sosId}
├── senderId (indexed)
├── senderName, senderPhone
├── location {lat, long, accuracy}
├── googleMapsUrl
├── status (indexed)
├── emergencyContactIds (indexed, array)
└── timestamps
Key Features
1. Contact Management
- Full CRUD with real-time Firestore streams
- Photo upload (compressed to 512x512, 75% JPEG)
- Mark as emergency contact
- Assign to multiple groups (many-to-many)
- Real-time search by name/phone
- CSV export with system share
2. Group Organization
- Custom groups with color picker
- Real-time group membership
- Array-based contact association
- Group detail view with member list
3. Emergency SOS System
- Slide-to-send with 80% threshold
- Real-time GPS location (high accuracy)
- Email-based contact matching
- Multi-layered persistent notifications
- FCM push to all emergency contacts
- Google Maps deep linking
- SOS status tracking (active/cancelled/resolved)
4. Authentication
- Email/password via Firebase Auth
- Profile photo upload to Storage
- FCM token management (save on login, delete on logout)
- Password reset via email
5. UI/UX
- 5-slide onboarding flow
- Bottom navigation (Home, Contacts, Groups, Profile)
- Empty states and loading indicators
- Material 3 design with Poppins font
- Real-time search functionality
Photo Upload & Optimization System
Challenge: Users may select large high-resolution photos from modern smartphone cameras (5-20MB), causing slow uploads and excessive Firebase Storage costs.
Solution: Client-side image compression before upload.
Upload Pipeline:
Step 1: File Selection
User picks image from gallery or camera using image_picker package. Returns local file path.
Step 2: Size Validation Check file size before processing. Reject images larger than 5MB with user-friendly error message, preventing excessive compression artifacts.
Step 3: Image Compression
- Decoding: Read raw image bytes and decode to pixel matrix using
imagepackage - Resizing: Resize to maximum 512x512 pixels while maintaining aspect ratio
- Portrait images: height = 512, width scales proportionally
- Landscape images: width = 512, height scales proportionally
- Square images: both dimensions = 512
- JPEG Encoding: Encode to JPEG format with 75% quality setting
- Balances file size (typically 50-150KB) with acceptable visual quality
- Original 8MB photo → compressed ~100KB photo (98% size reduction)
- Temporary Storage: Write compressed bytes to app's temp directory
Step 4: Firebase Storage Upload
- Path Organization:
contact_photos/{userId}/{timestamp}_{contactName}.jpg- User-scoped paths prevent unauthorized access
- Timestamp prefix ensures unique filenames
- Contact name suffix aids debugging and manual file management
- Upload Task: Use Firebase Storage SDK's
putFile()with progress tracking - Download URL: Extract permanent HTTPS URL after upload completes
Step 5: Firestore Reference
Store the download URL in contact's photoUrl field. This decouples image storage from database, allowing image changes without document updates.
Performance Benefits:
- 98% reduction in upload data (8MB → 100KB)
- Faster load times in contact list (uses
cached_network_imagewith memory cache) - Minimal Firebase Storage costs (FREE tier supports 5GB total)
- Reduced mobile data usage for users
Testing Results
Notification Delivery
| Scenario | Result | Notes |
|---|---|---|
| App foreground | Pass | Layer 1 + 3 |
| App background | Pass | Layer 3 guaranteed |
| App terminated | Pass | Layer 2 + 3 |
| Do Not Disturb | Pass | Alarm stream bypasses |
| Phone locked | Pass | Full-screen intent |
| Battery saver | Pass | exactAllowWhileIdle |
| Network offline | Partial | Scheduled alarms continue |
Challenges & Solutions
Challenge 1: Background Notification Persistence
Problem: Android kills background processes in Doze mode.
Solution: Three-layer approach with exact-time alarms using exactAllowWhileIdle flag.
Challenge 2: Do Not Disturb Bypass
Problem: Standard notifications silenced on DND.
Solution: Use AudioAttributesUsage.alarm audio stream, which bypasses DND for emergency scenarios.
Challenge 3: Cascading Provider Dependencies
Problem: Contact, Group, SOS providers all need user ID from Auth provider.
Solution: ProxyProvider pattern automatically injects auth state changes to dependent providers.
Challenge 4: Emergency Contact User Matching
Problem: Contacts have phone numbers, but Firebase Auth uses email.
Solution: Email-based matching by querying Firestore users collection for registered emails.
Key Insights
-
Background Processing is Hard: Android's battery optimization requires exact alarms to guarantee delivery.
-
Notification Lifecycle Complexity: Three separate mechanisms needed to cover all app states.
-
Permission Hell: Must gracefully handle denied permissions for location, notifications, exact alarms.
-
State Management Matters: ProxyProvider cascade prevents null errors when auth state changes.
-
Image Optimization Essential: Always compress before upload (5MB → 100KB typical).
-
Email Better Than Phone: More stable identifier for cross-device matching.
Conclusion
MyFriends demonstrates production-grade emergency notification delivery through a three-layer architecture that guarantees alerts reach recipients even under extreme conditions (DND mode, battery saver, app terminated). The app combines advanced Flutter techniques (background processing, state management, location services) with comprehensive Firebase integration (Auth, Firestore, Storage, FCM) to create a safety-critical application.
Key metrics:
- 100% notification delivery success (with network)
- 3-layer redundancy (foreground + background + scheduled)
- 8-50m location accuracy
- 5.8s average SOS send time
- 127 contacts managed across test users
Technical highlights:
AndroidScheduleMode.exactAllowWhileIdlebypasses DozeAudioAttributesUsage.alarmbypasses DND- Email-based user matching for cross-device SOS
- Provider with ProxyProvider for clean architecture
This project serves as a reference for building safety-critical Flutter applications with guaranteed background task execution.
Repository: GitHub - MyFriends App
Technologies: Flutter 3.27, Firebase (Auth, Firestore, Storage, FCM), Provider, Geolocator
Duration: 3 months
Course: Mobile Programming, Hasanuddin University, 2025
Project Metrics
3-layer notification system: foreground timer + background isolate + 60 scheduled exact-time alarms
100% notification delivery success rate (bypasses Do Not Disturb mode)
Real-time GPS location sharing with Google Maps integration
Email-based emergency contact matching across Firebase users
Real-time Firestore streams for contacts, groups, and SOS messages
Custom slide-to-send widget with 80% threshold gesture detection
Image compression (512x512, 75% quality) before Firebase Storage upload
CSV export with system share dialog integration
Project Tags
Related Projects
View all projects →
Medical Anamnesis Chatbot with NLP (Chatbot PUSTU)
Production-ready medical chatbot achieving 92.61% intent classification accuracy using Multinomial Naive Bayes for Indonesian Puskesmas healthcare anamnesis workflow. Automated training data generation via Gemini Flash 2.0 API with custom NLP preprocessing pipeline built from scratch.

Restaurant Management System - Backend API
Production-ready RESTful API for restaurant management with comprehensive RBAC, JWT authentication (access + refresh tokens), advanced filtering & pagination, order workflow state machine, and real-time table status management. Deployed on AWS EC2 with Elastic IP for stable endpoint.

E-Commerce Trust Simulation with LLM-Powered Agents
Agent-based simulation using MESA framework with 7,580 LLM-powered autonomous agents to quantify fake review manipulation impact on e-commerce conversion rates, demonstrating +54-72pp increase in targeted low-quality products through rigorous statistical validation (Chi-Square = 121-177, p less than 0.0001).
