CRM Mobile Application Development
A CRM app is not a CRUD wrapper over a database. A real customer management system on mobile requires offline synchronization, complex role-based models, push notifications for pipeline events, telephony and email integrations — and all of this must work quickly even on outdated Android devices in zones with unstable 3G connectivity.
Key Technical Challenges
Offline-First Approach and Synchronization
A field sales manager cannot wait for client data to load while searching for Wi-Fi. A CRM application must work offline by default. The standard approach is a local SQLite database with conflict resolution on the server.
On Flutter, we use drift (formerly moor) as a typed ORM over SQLite, and connectivity_plus for network state tracking:
// Save the action locally and queue for synchronization
Future<void> updateDealStage(String dealId, DealStage stage) async {
await localDb.updateDeal(dealId, stage: stage, syncStatus: SyncStatus.pending);
await syncQueue.enqueue(
SyncOperation(
type: OperationType.updateDeal,
payload: {'id': dealId, 'stage': stage.name},
createdAt: DateTime.now(),
),
);
// Trigger synchronization if network is available
if (await connectivity.checkConnectivity() != ConnectivityResult.none) {
syncService.flush();
}
}
Conflicts occur when a single contact is edited from different devices. A "last write wins" strategy at the record level breaks data. The correct approach is Last-Write-Wins at the field level, not the record level — with updated_at on each mutable field, and vector versioning for critical data.
Role-Based Access Control
CRM without roles doesn't exist: a manager sees only their clients, a team lead sees the entire department, an administrator sees everything. The role-based model must be implemented both on the server (Row Level Security in PostgreSQL or middleware) and on the client — not for security but for UX: to hide unavailable actions.
On iOS, we use Keychain to store tokens with binding to kSecAttrAccessibleWhenUnlockedThisDeviceOnly — tokens don't move to another device when restoring from backup, which is critical for corporate CRM.
Telephony Integration
The ability to call directly from a contact card is a standard for mobile CRM. On iOS, we use CallKit for native integration: a VoIP call (through Twilio, Vonage) appears as a regular incoming call with the name from the CRM and is logged in the device's call history.
// iOS CallKit provider
class CRMCallProvider: NSObject, CXProviderDelegate {
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// Connect Twilio Voice SDK
TwilioVoice.connect(with: connectOptions) { [weak self] call, error in
guard let call = call else { return }
self?.activeCall = call
action.fulfill()
// Log call start in CRM
self?.crmService.logCallStarted(contactId: action.callUUID.uuidString)
}
}
}
On Android, the equivalent is ConnectionService via TelecomManager.
Architecture and Stack
For a cross-platform CRM application, Flutter is the optimal choice: a single codebase covers iOS and Android, which matters with constantly changing business requirements. Architecture: BLoC + Clean Architecture, with a repository layer isolating the local database and API.
| Layer | Technologies |
|---|---|
| UI | Flutter + Material 3 / Cupertino adaptations |
| State management | flutter_bloc (BLoC pattern) |
| Local Database | drift (SQLite), Hive for caching |
| Network | Dio + Retrofit generation, Interceptor for token refresh |
| Sync | WorkManager (Android) / BGTaskScheduler (iOS) |
| Push | Firebase Cloud Messaging + background fetch |
| Analytics | Firebase Analytics, Crashlytics |
For native iOS or Android development, use SwiftUI + Combine / Jetpack Compose + ViewModel respectively.
Push Notification Handling
CRM events — scheduled meetings, new leads, overdue tasks — require push delivery in the background. On iOS, UNUserNotificationCenter with content-available: 1 launches the app in the background to update data. Critical point: iOS gives the background process no more than 30 seconds, and too-frequent background fetches trigger system throttling.
Strategy: push only for event notification, heavy data loads lazily when opening the notification.
Work Phases
Requirements audit and data model design → design (if no ready design) → API and mobile client development in parallel → integration testing with synchronization edge cases → testing on real devices with network loss simulation → publication in App Store / Google Play → support.
Mandatory phase: load testing synchronization with a large database (10,000+ contacts). On weak Android devices, SQLite operations on large datasets without proper pagination and indexes cause ANR.
Timeframe
MVP with basic functionality (contacts, deals, tasks, offline): 8–12 weeks. Full-featured application with telephony, integrations (email, calendar), advanced analytics: 4–6 months. Cost calculated individually after requirements analysis.







