Reduce unnecessary API calls, startup work, and battery drain#69
Conversation
- Cache settings in memory to eliminate SELECT roundtrip on every settings update (falls back to DB fetch when cache is unpopulated) - Collapse updateThemeColor from 3 to 1 API call by combining the dedicated column update and settings JSON update in a single request - Replace getCurrentProfile() in updateLocation with a SharedPreferences ghost_mode read (memory-cached) to avoid a network call on each GPS event - Add server-side receiver_id filter to _startMessageListener so only messages addressed to the current user are delivered over the wire - Expose NotificationManager.plugin and reuse it in RoutesTab instead of creating a second FlutterLocalNotificationsPlugin instance and calling initialize() a second time at startup - Cache wake alarm settings (alarm_stops_before, alarm_trigger_threshold) before starting the GPS stream instead of reading SharedPreferences on every GPS position event Co-authored-by: khonager <71403572+khonager@users.noreply.github.com> Agent-Logs-Url: https://github.com/khonager/Trans/sessions/ba1ccabe-3880-414d-ae97-53df8d35cd91
There was a problem hiding this comment.
Pull request overview
This PR reduces redundant network calls and repeated initialization work in hot paths (settings updates, location tracking during the wake alarm, realtime message subscriptions, and local notification plugin lifecycle) to improve startup cost and battery/network usage.
Changes:
- Added an in-memory settings JSON cache to avoid an extra SELECT on each
updateSettingscall, and consolidatedupdateThemeColorinto a single UPDATE request. - Reduced wake-alarm background overhead by avoiding per-position preference reads and by checking ghost mode from
SharedPreferencesinstead of fetching the profile every time. - Narrowed the messages realtime stream to only the current receiver and removed duplicate notification plugin initialization by reusing a shared instance.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| lib/services/supabase_service.dart | Adds settings cache + reduces settings/theme-color roundtrips; reduces GPS-update network/profile fetch work; adds receiver filter to message stream |
| lib/services/notification_manager.dart | Exposes a shared, already-initialized notifications plugin instance |
| lib/screens/tabs/routes_tab.dart | Reuses the shared notifications plugin and moves wake-alarm preference reads outside the GPS loop |
Comments suppressed due to low confidence (1)
lib/services/supabase_service.dart:279
- The new in-memory
_cachedSettingsfast-path meansupdateSettingscan now write based on a potentially stale snapshot (e.g., settings changed from another device/session while the app is running). That can cause lost updates because the wholesettingsJSON is rewritten each time. If multi-device sync matters, consider updating settings server-side with a JSON merge/patch operation (so only the provided keys are overwritten) or periodically reloading/invalidating the cache before writes.
Map<String, dynamic> base;
if (_cachedSettings != null) {
// Fast path: merge into the in-memory cache to avoid a SELECT roundtrip.
base = Map<String, dynamic>.from(_cachedSettings!);
} else {
// Cache not yet populated (e.g. settings changed before loadAndSyncSettings
// completed). Fall back to fetching the current value from the database.
final res = await client
.from('profiles')
.select('settings')
.eq('id', user.id)
.single();
base = Map<String, dynamic>.from(res['settings'] ?? {});
}
final updatedSettings = base..addAll(newSettings);
_cachedSettings = updatedSettings;
await client
.from('profiles')
.update({'settings': updatedSettings}).eq('id', user.id);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try { | ||
| final res = await client | ||
| .from('profiles') | ||
| .select('settings') | ||
| .eq('id', user.id) | ||
| .single(); | ||
| base = Map<String, dynamic>.from(res['settings'] ?? {}); | ||
| } catch (_) { | ||
| base = {}; | ||
| } | ||
| } | ||
| final updatedSettings = base..addAll({'theme_color_value': colorValue}); | ||
| _cachedSettings = updatedSettings; | ||
| await client.from('profiles').update({ | ||
| 'theme_color': colorValue, | ||
| 'settings': updatedSettings, | ||
| }).eq('id', user.id); |
There was a problem hiding this comment.
In updateThemeColor, if the SELECT settings fails you fall back to base = {} but still send an UPDATE that sets the settings column to only {theme_color_value: ...}. This would overwrite/wipe any existing settings JSON on transient network/RLS errors. Consider only updating the theme_color column when the settings cache is cold and the SELECT fails (or bail out), and only include settings in the UPDATE when you have a reliable base (cache hit or successful fetch).
Several hot paths were making redundant network calls or duplicating initialization work on every invocation. Key areas: settings updates, location tracking during wake alarm, realtime message subscriptions, and notification plugin lifecycle.
Changes
Settings cache –
updateSettingspreviously did SELECT → merge → UPDATE on every call (2 round-trips per setting toggle). Now maintains_cachedSettingspopulated byloadAndSyncSettings; subsequent writes merge locally and go straight to UPDATE. Falls back to SELECT if the cache is cold.updateThemeColor3 → 1 API call – Was calling a column-level UPDATE thenupdateSettings(which itself was SELECT + UPDATE). Now mergestheme_color_valueinto the settings cache and issues a single UPDATE covering both fields.updateLocationghost mode check – Was callinggetCurrentProfile()(full network fetch) on every GPS event to readghost_mode. During the wake alarm this fired every ~10 s (6 wasted fetches/min). Now reads fromSharedPreferences, which is memory-cached after first access.Message listener server-side filter – Added
.eq('receiver_id', user.id)to_startMessageListener's Supabase stream. Previously streamed the last 5 messages globally and filtered client-side; now only events for the current user are delivered over the wire.Single
FlutterLocalNotificationsPlugininstance –RoutesTabwas constructing a second plugin instance and calling.initialize()ininitState, duplicating whatNotificationManager.init()already does at startup. AddedNotificationManager.plugingetter;RoutesTabnow reuses it.Wake alarm prefs read outside GPS loop –
alarm_stops_beforeandalarm_trigger_thresholdwere fetched fromSharedPreferenceson every position event inside the stream listener. Both are now read once before the stream starts and captured as closure variables.Original prompt
📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.