Flutter App Integration Guide
Connect your Convergence Protocol Flutter app to the audiobook distribution platform for seamless sync between web and mobile.
๐ฏ What You Get
| Feature | Web | Flutter App | Sync |
|---|---|---|---|
| User Accounts | โ | โ | โ |
| Convergence Progress | โ | โ | โ |
| Session Logging | โ | โ | โ |
| 77Hz Audio | โ | โ | โ |
| Audiobook Streaming | โ | โ | โ |
| Offline Mode | โ | โ | Sync when online |
๐ API Endpoints for Flutter
Base URL
https://audio.kbird.ai
Authentication
Same as web - use JWT tokens:
// Login
POST /auth/login
Body: { "email": "[email protected]", "password": "password" }
Response: { "token": "eyJhbG...", "user": {...} }
// Store token securely (flutter_secure_storage)
// Include in all requests: headers['Authorization'] = 'Bearer $token'Convergence Protocol Endpoints
Get Progress
GET /api/convergence/progress
Headers: { 'Authorization': 'Bearer $token' }
Response: {
"progress": {
"current_node": 5,
"total_sessions": 12,
"streak_days": 3,
"longest_streak": 7,
"last_session_date": "2026-03-16"
},
"completedNodes": [1, 2, 3, 4]
}Update Progress
POST /api/convergence/progress
Headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json' }
Body: {
"currentNode": 5,
"streakDays": 3,
"longestStreak": 7,
"lastSessionDate": "2026-03-16"
}Log Session
POST /api/convergence/sessions
Body: {
"sessionType": "node_practice", // or "77hz", "weekly_convergence", "audio_listening"
"nodeNumber": 5,
"durationMinutes": 15,
"audioFile": "wildflower_ch18.mp3",
"book": "wildflower",
"notes": "Felt the pattern breaking today",
"moodBefore": 6,
"moodAfter": 8,
"syncTimestamp": 1710595200000 // Unix timestamp for sync
}Get Sessions
GET /api/convergence/sessions?limit=50&offset=0
Response: {
"sessions": [
{
"id": 1,
"session_type": "77hz",
"duration_minutes": 20,
"created_at": "2026-03-16T09:30:00Z"
}
]
}Complete a Node
POST /api/convergence/nodes/complete
Body: { "nodeNumber": 5 }Weekly Convergence
// Get current week
GET /api/convergence/weekly
// Save weekly check-in
POST /api/convergence/weekly
Body: {
"weekStartDate": "2026-03-10",
"weekNumber": 3,
"score": 75,
"patternRecognition": "Noticed resistance to silence",
"resistanceEncountered": "Skipped Tuesday practice",
"insightGained": "Silence is where patterns reveal themselves",
"nextWeekFocus": "Daily 10-minute silence",
"completed": true
}77Hz Entrainment
Start Session
POST /api/convergence/entrainment/start
Body: {
"sessionId": "28409296", // or custom
"frequencyHz": 77,
"type": "session_28409296"
}
Response: { "entrainmentId": 123, "message": "77Hz session started" }Stream 77Hz Audio
// Stream for playback
GET /audio/77hz/77Hz_Entrainment_Base.mp3
Headers: { 'Authorization': 'Bearer $token' }
// For Flutter: use just_audio package with headersComplete Session
POST /api/convergence/entrainment/complete
Body: {
"entrainmentId": 123,
"durationMinutes": 20
}Device Sync
POST /api/convergence/sync
Body: {
"deviceId": "unique-device-identifier",
"deviceType": "ios", // or "android"
"lastSyncTimestamp": 1710595200000 // last sync time
}
Response: {
"syncTimestamp": 1710595201000,
"sessions": [...], // sessions since last sync
"progress": {...} // current progress
}๐ฑ Flutter Implementation Example
Dependencies
# pubspec.yaml
dependencies:
http: ^1.2.0
flutter_secure_storage: ^9.0.0
just_audio: ^0.9.36
audio_session: ^0.1.18
shared_preferences: ^2.2.2API Service
class ConvergenceApiService {
static const String baseUrl = 'https://audio.kbird.ai';
final FlutterSecureStorage _storage = const FlutterSecureStorage();
Future<String?> getToken() async {
return await _storage.read(key: 'access_token');
}
Future<Map<String, String>> getHeaders() async {
final token = await getToken();
return {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
}
// Get convergence progress
Future<ConvergenceProgress> getProgress() async {
final response = await http.get(
Uri.parse('$baseUrl/api/convergence/progress'),
headers: await getHeaders(),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return ConvergenceProgress.fromJson(data['progress']);
}
throw Exception('Failed to get progress');
}
// Log a session
Future<void> logSession(Session session) async {
final response = await http.post(
Uri.parse('$baseUrl/api/convergence/sessions'),
headers: await getHeaders(),
body: jsonEncode(session.toJson()),
);
if (response.statusCode != 200) {
// Store locally for later sync
await _storeOffline(session);
}
}
// Sync with server
Future<void> sync() async {
final lastSync = await _getLastSyncTimestamp();
// Get server changes
final response = await http.post(
Uri.parse('$baseUrl/api/convergence/sync'),
headers: await getHeaders(),
body: jsonEncode({
'deviceId': await _getDeviceId(),
'deviceType': Platform.isIOS ? 'ios' : 'android',
'lastSyncTimestamp': lastSync,
}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
// Apply server changes
await _applyServerChanges(data);
// Send local changes
await _pushLocalChanges();
// Update last sync
await _setLastSyncTimestamp(data['syncTimestamp']);
}
}
// Stream 77Hz audio
Future<AudioSource> get77HzAudio(String filename) async {
final token = await getToken();
return LockCachingAudioSource(
Uri.parse('$baseUrl/audio/77hz/$filename'),
headers: {'Authorization': 'Bearer $token'},
);
}
// Stream audiobook (premium content)
Future<AudioSource> getAudiobook(String book, String format, String filename) async {
final token = await getToken();
return ProgressiveAudioSource(
Uri.parse('$baseUrl/audio/$book/$format/$filename'),
headers: {'Authorization': 'Bearer $token'},
);
}
}Data Models
class ConvergenceProgress {
final int currentNode;
final int totalSessions;
final int streakDays;
final int longestStreak;
final DateTime? lastSessionDate;
ConvergenceProgress({
required this.currentNode,
required this.totalSessions,
required this.streakDays,
required this.longestStreak,
this.lastSessionDate,
});
factory ConvergenceProgress.fromJson(Map<String, dynamic> json) {
return ConvergenceProgress(
currentNode: json['current_node'] ?? 1,
totalSessions: json['total_sessions'] ?? 0,
streakDays: json['streak_days'] ?? 0,
longestStreak: json['longest_streak'] ?? 0,
lastSessionDate: json['last_session_date'] != null
? DateTime.parse(json['last_session_date'])
: null,
);
}
Map<String, dynamic> toJson() => {
'currentNode': currentNode,
'streakDays': streakDays,
'longestStreak': longestStreak,
'lastSessionDate': lastSessionDate?.toIso8601String().split('T')[0],
};
}
class Session {
final String sessionType;
final int? nodeNumber;
final int durationMinutes;
final String? audioFile;
final String? book;
final String? notes;
final int? moodBefore;
final int? moodAfter;
final int syncTimestamp;
Session({
required this.sessionType,
this.nodeNumber,
this.durationMinutes = 0,
this.audioFile,
this.book,
this.notes,
this.moodBefore,
this.moodAfter,
}) : syncTimestamp = DateTime.now().millisecondsSinceEpoch;
Map<String, dynamic> toJson() => {
'sessionType': sessionType,
'nodeNumber': nodeNumber,
'durationMinutes': durationMinutes,
'audioFile': audioFile,
'book': book,
'notes': notes,
'moodBefore': moodBefore,
'moodAfter': moodAfter,
'syncTimestamp': syncTimestamp,
};
}Offline Support
class OfflineQueue {
static const String _key = 'offline_sessions';
Future<void> add(Session session) async {
final prefs = await SharedPreferences.getInstance();
final List<String> queue = prefs.getStringList(_key) ?? [];
queue.add(jsonEncode(session.toJson()));
await prefs.setStringList(_key, queue);
}
Future<List<Session>> getAll() async {
final prefs = await SharedPreferences.getInstance();
final List<String> queue = prefs.getStringList(_key) ?? [];
return queue
.map((s) => Session.fromJson(jsonDecode(s)))
.toList();
}
Future<void> clear() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_key);
}
Future<void> sync() async {
final sessions = await getAll();
for (final session in sessions) {
try {
await apiService.logSession(session);
} catch (e) {
// Keep in queue if fails
return;
}
}
await clear();
}
}๐ Sync Strategy
Automatic Sync
- On app launch
- After logging a session
- When returning online
- Every 5 minutes during active use
Conflict Resolution
- Server wins for progress (higher node number)
- Append sessions (both kept)
- Latest weekly convergence wins
Offline Mode
// Check connectivity
if (await Connectivity().checkConnectivity() == ConnectivityResult.none) {
// Store locally
await offlineQueue.add(session);
} else {
// Send to server
await apiService.logSession(session);
}
// When coming back online
Connectivity().onConnectivityChanged.listen((result) {
if (result != ConnectivityResult.none) {
offlineQueue.sync();
}
});๐ต 77Hz Audio Player
class EntrainmentPlayer {
final AudioPlayer _player = AudioPlayer();
final ConvergenceApiService _api = ConvergenceApiService();
int? _currentEntrainmentId;
Future<void> start77HzSession() async {
// Start session on server
final response = await _api.startEntrainment();
_currentEntrainmentId = response['entrainmentId'];
// Load and play audio
final audioSource = await _api.get77HzAudio('77Hz_Entrainment_Base.mp3');
await _player.setAudioSource(audioSource);
await _player.play();
}
Future<void> stopSession() async {
final duration = _player.duration?.inMinutes ?? 0;
await _player.stop();
// Complete on server
if (_currentEntrainmentId != null) {
await _api.completeEntrainment(_currentEntrainmentId!, duration);
}
}
}๐ Tracking Convergence Protocol
// When user completes a node practice
await apiService.logSession(Session(
sessionType: 'node_practice',
nodeNumber: currentNode,
durationMinutes: practiceDuration,
notes: userNotes,
moodBefore: moodBefore,
moodAfter: moodAfter,
));
// Mark node complete
await apiService.completeNode(currentNode);
// Sync progress
await apiService.sync();๐ Security
- Store JWT in
flutter_secure_storage - Refresh token automatically when expired
- Clear tokens on logout
- Use HTTPS only
๐งช Testing
// Test sync
test('Sync merges local and server data', () async {
// Create local session
await offlineQueue.add(Session(sessionType: 'test'));
// Sync
await apiService.sync();
// Verify on server
final sessions = await apiService.getSessions();
expect(sessions.any((s) => s.sessionType == 'test'), true);
});๐ Resources
Questions? The web and Flutter apps share the same API - any changes sync across both platforms automatically!