Skip to content

fix(nocodes): prevent crash when ScreenActivity is restored before NoCodes.initialize#807

Merged
NickSxti merged 1 commit intomainfrom
fix/nocodes-450-process-death-restore
May 7, 2026
Merged

fix(nocodes): prevent crash when ScreenActivity is restored before NoCodes.initialize#807
NickSxti merged 1 commit intomainfrom
fix/nocodes-450-process-death-restore

Conversation

@NickSxti
Copy link
Copy Markdown
Contributor

@NickSxti NickSxti commented May 6, 2026

Summary

Fixes qonversion/flutter-sdk#450.

ScreenActivity is restored by Android after process death (e.g., user kills the app from devtools while a no-codes screen is on the back stack and later relaunches). On restoration, super.onCreate(savedInstanceState) triggers AndroidX FragmentManager.restoreSaveState, which recreates ScreenFragment via reflection on the no-arg constructor. ScreenFragment's six property initializers read DependenciesAssembly.instance, which is set only by NoCodes.initialize - before the host app re-initializes (Flutter SDK can't realistically initialize before MainActivity.onCreate), the lateinit is unset and the read throws kotlin.UninitializedPropertyAccessException.

Three layers of defense

  1. DependenciesAssembly.isInstanceInitialized() - new helper on the companion that reports whether the lateinit instance has been assigned.
  2. ScreenActivity.onCreate guard - if savedInstanceState != null and DI is not ready, strip both legacy (android:fragments) and modern (android:support:fragments) fragment-state keys, call super.onCreate(null), and finish(). The host can re-show the screen after re-initialization.
  3. ScreenFragment lazy DI lookups - all six DI-reading initializers (presenter, logger, delegateProvider, purchaseDelegateProvider, screenCustomizationDelegate, delegate) converted to by lazy. themeProvider was already a deferred lambda. This keeps the no-arg constructor side-effect-free as defense-in-depth even if a future change forgets the activity guard.

@NickSxti NickSxti requested a review from SpertsyanKM May 6, 2026 11:04
When Android kills the host process while a ScreenActivity is on the
back stack and the user later returns, the OS recreates the activity
before the host app has had a chance to call NoCodes.initialize again.
AndroidX FragmentManager then reflectively reconstructs the saved
ScreenFragment via its no-arg constructor, whose property initializers
read DependenciesAssembly.instance - an uninitialized lateinit static -
and the lateinit throws.

Three layers of defense:
- DependenciesAssembly exposes an isInstanceInitialized() helper so
  callers can check whether NoCodes.initialize has run in this process.
- ScreenActivity installs a custom FragmentFactory before super.onCreate
  that returns a FinishingStubFragment in place of ScreenFragment when
  the assembly is uninitialized; the stub closes the activity and the
  host can re-show the screen after its own re-initialization. A
  defensive finish() handles the rare case where the saved state has
  no fragments, so the factory is never consulted.
- ScreenFragment defers all DependenciesAssembly lookups via `by lazy`
  so its no-arg constructor is side-effect-free as defense in depth.

Fixes qonversion/flutter-sdk#450

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@NickSxti NickSxti force-pushed the fix/nocodes-450-process-death-restore branch from 9edb46b to 0e74ef3 Compare May 7, 2026 11:19
@NickSxti NickSxti merged commit 9edee22 into main May 7, 2026
1 check passed
@NickSxti NickSxti deleted the fix/nocodes-450-process-death-restore branch May 7, 2026 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants