chore(android): enable StrictMode penaltyDeath in the demo app#489
Draft
chore(android): enable StrictMode penaltyDeath in the demo app#489
Conversation
Configure StrictMode in the demo's Application.onCreate() with detectAll() + penaltyLog() on both ThreadPolicy and VmPolicy, gated on BuildConfig.DEBUG. Phase 1 of #488 — log-only so existing library-side violations surface during local dev without crashing manual testing. Phase 2 (penaltyDeath) is a separate PR and depends on the library-side violations catalogued in docs/code/strictmode.md being addressed.
Switch the demo's StrictMode ThreadPolicy from penaltyLog to penaltyDeath, so any new main-thread disk/network I/O introduced in the library crashes the demo immediately rather than logging quietly. VmPolicy stays on penaltyLog — VM violations fire during GC and would crash at unpredictable times unrelated to the offending code path. To make penaltyDeath viable, the demo's startup and editor flows are restructured to keep the main thread off disk: - AccountRepository init moves to Dispatchers.IO behind a Deferred. Callers use a new withAccountRepository helper that awaits the deferred and then hops to IO for the actual repo call. - All AccountRepository reads/writes (.all(), .store(), .remove()) across MainActivity, AuthenticationManager, EditorActivity, PostsListActivity, and SitePreparationViewModel are wrapped via withAccountRepository. - SitePreparationViewModel.createEditorService wraps EditorService.create in withContext(IO). EditorService.purge / fetchAssetBundleCount also. - SitePreparationActivity.launchEditor moves EditorDependenciesSerializer.writeToDisk into a coroutine. - EditorActivity.onCreate reads dependencies into Compose state on IO and defers setContent until the read completes. NetworkAvailabilityProvider stays eager on the main thread (its constructor only stores a SAM lambda). Verified on a Pixel 9 (Android 16) and an emulator: cold launch + Standalone Editor + Prepare Editor + Start + typing produces zero ThreadPolicy violations. Closes #488.
The async deserialize of EditorDependencies left the activity rendering nothing during the read and would have either hung blank or crashed the activity coroutine on a deserialization failure. Replace the boolean ready flag with a sealed Loading/Failed/Ready state. While loading, show a CircularProgressIndicator. On failure, catch the exception, surface its message, and offer a Close button. The Ready path is unchanged. This also makes the activity safe across process-death restore — a stale EXTRA_DEPENDENCIES_PATH whose temp file has since been cleaned up now lands on the error screen instead of crashing.
CI Detekt flagged three issues from the prior commits: - AuthenticationManager.handleOAuthCallback was 61 lines (max 60). Extract a persistOAuthAccount helper that takes the unpacked blogId / accessToken and handles the repo write + OAuth scratch-state cleanup. - EditorActivity.onCreate was 63 lines (max 60). Extract the dependencies loader to loadDependenciesFromIntent, which returns the state holder. - The catch (Exception) around EditorDependenciesSerializer.readFromDisk was redundant — the serializer already swallows exceptions internally and returns null on failure. Drop the try/catch and treat a null return from a non-null path as the Failed signal.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #488. Configures
StrictModein the demo app'sApplication.onCreate()withdetectAll()+penaltyDeath()onThreadPolicyandpenaltyLog()onVmPolicy, gated onBuildConfig.DEBUG. Release builds are unaffected.Diverges from the phased Phase 1 / Phase 2 plan in #488 — the original plan was
penaltyLogonly, withpenaltyDeathdeferred to a follow-up after the catalogued violations were addressed. We've collapsed both phases into one PR: the library-side violations are wrapped at the demo call sites (per the issue's "no library permits" rule), sopenaltyDeathships green on day one and any future regression crashes the demo immediately.Demo-side initialization shape
Because
penaltyDeathcrashes on any violation, the demo had to be restructured so nothing on the main thread touches disk:AccountRepositoryis initialized lazily onDispatchers.IObehind aDeferred. Its constructor readsfilesDirand triggerscom.sun.jna.Native.<clinit>, which runs ~8 disk reads while loading the dispatch library.GutenbergKitApplication.withAccountRepository { ... }helper awaits the deferred and dispatches the call toDispatchers.IO. All.all() / .store() / .remove()callsites acrossMainActivity,AuthenticationManager,EditorActivity,PostsListActivity, andSitePreparationViewModeluse it.SitePreparationViewModel.createEditorServicewrapsEditorService.createinwithContext(IO). Same forEditorService.purgeandEditorService.fetchAssetBundleCount.SitePreparationActivity.launchEditormovesEditorDependenciesSerializer.writeToDiskinto a coroutine beforestartActivity.EditorActivity.onCreatereads dependencies on IO into Compose state and deferssetContentuntil the read completes.NetworkAvailabilityProviderstays initialized eagerly on the main thread — its constructor only stores a SAM lambda.Library-side debt the demo wraps around
docs/code/strictmode.mdlists the library-internal disk I/O the demo wraps inwithContext(IO)rather than fixing upstream:EditorService.create(syncEditorHTTPClientSSL setup +Paths.cacheRoot/storageRoot/defaultTempStorageRoot+EditorURLCache.<init>+EditorAssetsLibrary.<init>).EditorService.fetchAssetBundleCount/EditorAssetsLibrary.readAssetBundles.EditorDependenciesSerializer.writeToDisk/readFromDisk.Each is a candidate for a follow-up that moves the I/O off the caller's thread or exposes a
suspendAPI. Library fixes are out of scope here per #488.Test plan
Verified on a Pixel 9 (Android 16) and an Android 14 emulator. Zero
ThreadPolicyviolations, no crashes:SitePreparationActivityloadsEditorActivityloads, editor renders./gradlew :app:compileDebugKotlin— cleandocs/code/strictmode.mddocuments the convention).Files changed
android/app/src/main/java/com/example/gutenbergkit/GutenbergKitApplication.kt— StrictMode setup,Deferred<AccountRepository>,withAccountRepositoryhelper.android/app/src/main/java/com/example/gutenbergkit/MainActivity.kt— load saved accounts inlifecycleScope, dispatch delete to IO.android/app/src/main/java/com/example/gutenbergkit/AuthenticationManager.kt— takeGutenbergKitApplicationinstead ofAccountRepository; dispatch store/all paths to IO.android/app/src/main/java/com/example/gutenbergkit/SitePreparationViewModel.kt—createEditorService(...)wrapper, IO dispatch onpurge/fetchAssetBundleCount/ account lookup.android/app/src/main/java/com/example/gutenbergkit/SitePreparationActivity.kt— asynclaunchEditor.android/app/src/main/java/com/example/gutenbergkit/EditorActivity.kt— asyncreadFromDiskinto Compose state; deferredsetContent.android/app/src/main/java/com/example/gutenbergkit/EditorActivity.kt,PostsListActivity.kt— IO-dispatched account lookups.docs/code/strictmode.md(new) — configuration, demo-side init shape, library debt, verification, conventions.docs/code/README.md— link to the new doc.