3.1 KiB
Android Bug Hunter — Project Memory
Recurring Patterns Found
NotificationHelper: runBlocking on Service Thread
NotificationHelper.show() calls runBlocking three times (notificationsEnabled, mutedRooms, roomNameCache).
This blocks the PushService callback thread. Fix: pass pre-collected values in or make show() a suspend fun.
See: push/NotificationHelper.kt
FluffytrixPushService: Leaking CoroutineScope
FluffytrixPushService creates its own CoroutineScope(SupervisorJob() + Dispatchers.IO) but never cancels
it when the service is destroyed. Coroutines launched from onNewEndpoint/onUnregistered can outlive the
service. Fix: cancel scope in onDestroy().
PushRegistrationManager: OkHttp response body not closed
registerPusher() stores response.body?.string() (which auto-closes), but the execute() call itself is
never inside a use {} block — if body?.string() throws, the response is not closed.
Fix: wrap in response.use { ... }.
build.gradle.kts: API key in plain BuildConfig
TENOR_API_KEY hardcoded in buildConfigField — visible in plaintext in the APK's BuildConfig class.
MainViewModel: onUpdate listener launches on Dispatchers.Default inside viewModelScope
The timeline listener's onUpdate callback launches a coroutine with viewModelScope.launch(Dispatchers.Default).
If the ViewModel is cleared mid-flight the scope is cancelled but the onUpdate lambda (captured by the
SDK via JNI) can still fire and attempt launch on a cancelled scope — harmless but noisy.
MainViewModel: CoroutineScope inside loadTimeline shared across closures
A Mutex local to loadTimeline is captured by the TimelineListener lambda. When loadTimeline is called
again for a new room, the old listener still holds the old mutex — correct, but produces confusing parallelism.
MainViewModel: sendGif creates a new OkHttpClient per call
A brand-new OkHttpClient() is constructed inside sendGif on every invocation. Should reuse a shared
client (like the one in PushRegistrationManager).
SettingsScreen: new OkHttpClient per API-key test
A new OkHttpClient() is created on every "Save" button press inside SettingsScreen. Leaks the connection
pool after the composable is disposed.
DeepLinkState: global singleton StateFlow — never clears on logout
DeepLinkState is a process-level object. After logout/re-login the stale pendingRoomId can navigate
the new session to the old room.
PreferencesManager: notificationsEnabled default logic is inverted-looking
prefs[KEY_NOTIFICATIONS_ENABLED] != false — treats missing key as true (desired), but any corruption
storing a non-boolean type would also return true. Low risk but worth noting.
Key File Paths
- Push infra:
app/src/main/java/com/example/fluffytrix/push/ - ViewModel:
app/src/main/java/com/example/fluffytrix/ui/screens/main/MainViewModel.kt - Settings UI:
app/src/main/java/com/example/fluffytrix/ui/screens/settings/SettingsScreen.kt - DI:
app/src/main/java/com/example/fluffytrix/di/AppModule.kt - Prefs:
app/src/main/java/com/example/fluffytrix/data/local/PreferencesManager.kt