Building Offline-First Mobile Apps for Low-Connectivity Areas in Nigeria
## The Connectivity Reality in Rural Nigeria Nigeria's urban areas have reasonable 4G LTE coverage. Step outside Lagos, Ibadan, and Abuja, however...
The Connectivity Reality in Rural Nigeria
Nigeria's urban areas have reasonable 4G LTE coverage. Step outside Lagos, Ibadan, and Abuja, however, and the picture changes dramatically. Many parts of Ogun, Oyo (outside Ibadan), Kogi, Benue, and northern states have spotty 2G/3G coverage — or none at all.
If your mobile app serves farmers, rural healthcare workers, teachers, or field agents, "requires internet" is not an acceptable constraint. You must build offline-first.
Core Architecture Principle: Local-First Data
The key mindset shift for offline-first apps:
- The local database is the truth; the server is a sync target
- Users interact with local data 100% of the time
- Sync happens in the background when connectivity is available
- Conflict resolution is handled explicitly, not ignored
Flutter Implementation with Drift (SQLite)
Drift is our recommended local database for Flutter apps. It's type-safe, supports complex queries, and handles migrations gracefully.
// lib/database/app_database.dart
import 'package:drift/drift.dart';
class FarmerRecords extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get farmerId => text()();
TextColumn get cropType => text()();
RealColumn get areaHectares => real()();
TextColumn get notes => text().nullable()();
DateTimeColumn get recordedAt => dateTime()();
BoolColumn get isSynced => boolean().withDefault(const Constant(false))();
}
@DriftDatabase(tables: [FarmerRecords])
class AppDatabase extends _$AppDatabase {
AppDatabase(super.e);
@override
int get schemaVersion => 1;
// Get all unsynced records
Future<List<FarmerRecord>> getUnsyncedRecords() =>
(select(farmerRecords)..where((r) => r.isSynced.equals(false))).get();
// Mark records as synced after successful server sync
Future<void> markAsSynced(List<int> ids) =>
(update(farmerRecords)..where((r) => r.id.isIn(ids)))
.write(const FarmerRecordsCompanion(isSynced: Value(true)));
}
Background Sync Manager
// lib/services/sync_service.dart
import 'package:connectivity_plus/connectivity_plus.dart';
class SyncService {
final AppDatabase db;
final ApiClient api;
bool _isSyncing = false;
SyncService({required this.db, required this.api});
void startListening() {
Connectivity().onConnectivityChanged.listen((result) {
if (result != ConnectivityResult.none && !_isSyncing) {
_syncPendingRecords();
}
});
}
Future<void> _syncPendingRecords() async {
_isSyncing = true;
try {
final pending = await db.getUnsyncedRecords();
if (pending.isEmpty) return;
final syncedIds = await api.batchUpload(pending);
await db.markAsSynced(syncedIds);
debugPrint('Synced ${syncedIds.length} records');
} catch (e) {
debugPrint('Sync failed: $e — will retry on next connection');
} finally {
_isSyncing = false;
}
}
}
Handling Conflicts
When the same record is modified both offline and on the server, you need a conflict resolution strategy:
- Last-Write-Wins: Simple, but can lose data. Acceptable for non-critical records.
- Server-Wins: Server data always overrides local. Good for reference data (product catalog, prices).
- Client-Wins: Local data always wins. Good for user-generated content (notes, photos).
- Manual Resolution: Show the user both versions and let them choose. Best for high-value data (financial records, medical data).
For our agricultural app, we use "Server-Wins for reference data, Client-Wins for field records" with a simple timestamp comparison.
Optimizing for Low Bandwidth
When connectivity is available, it may still be slow (2G, ~50kbps). Optimize your sync payloads:
- Delta sync: Only send records changed since last sync, not the full dataset
- Compress payloads: Use gzip compression on API responses
- Prioritize critical data: Sync financial transactions before photos
- Defer media: Photos and documents sync last, or only on WiFi
Real-World Performance (AgriConnect App)
After deploying our agricultural marketplace app (connecting farmers to buyers) in Oyo, Osun, and Kogi states:
- Offline functionality: 100% of core features work without internet
- Sync success rate: 99.2% of offline records sync within 48 hours of connectivity
- Data loss rate: 0.001% (in 18 months of production)
- User satisfaction in rural areas: 4.7/5 (same as urban users)
If you're building for rural Nigeria or other low-connectivity markets in Africa, invest heavily in offline-first architecture from day one — it's much harder to retrofit later. Our mobile development services include offline-first Flutter apps with Drift + background sync. Use the project estimator to scope your build, or reach out directly.
Contact us: hello@neobottech.org
Neobot Engineering Standard
Every system deployed by Neobot Tech incorporates enterprise baseline practices. We continuously audit our database topologies, REST API query paths, and frontend modular bundles to prevent latency spikes and ensure top-tier security posture.
Discussion
Comments Coming Soon
We are currently migrating our discussion engine to a new real-time database schema. Check back shortly to join the conversation.