Skip to content

Conversation

@arnaudgiuliani
Copy link
Member

@arnaudgiuliani arnaudgiuliani commented Dec 5, 2025

Fix Koin context recreation on theme changes and unify KoinApplication API

Problem

When using KoinMultiplatformApplication() (or KoinApplication()), theme changes on Android were causing Koin to restart, re-registering all modules and recreating dependencies. This was caused by:

  1. Premature cleanup in onForgotten() - The RememberObserver.onForgotten() callback was calling stop(), but this callback fires during temporary composition changes (like theme changes), not just when the composition is truly abandoned
  2. Module re-registration - Even when Koin instance was preserved, modules were being re-registered on recomposition because koinApplication() was being called outside the remember {} block

Related to #2274

Solution

This PR builds on #2326 and adds:

  1. Fix Context Recreation (commits 87811e6, b7c9f3c)
  • CompositionKoinApplicationLoader: Now accepts nullable KoinApplication? and only starts Koin if it doesn't exist yet
  • onForgotten(): Only deallocates reference (koin = null) instead of calling stopKoin() - preventing premature shutdown
  • RememberKoinApplication: Passes null for KoinApplication when Koin already exists, preventing module re-registration
  1. Unify KoinApplication API (commit ec2535b + current changes)
  • New unified entry point: KoinApplication(configuration: KoinConfiguration, logLevel: Level)
    • Works for all platforms (Android, iOS, Desktop, Web)
    • Automatically injects Android context and logger on Android
    • Uses koinConfiguration { } for better clarity and type safety
  • Deprecations:
    • KoinApplication(application: KoinAppDeclaration) → Use new KoinApplication(configuration: ...)
    • KoinMultiplatformApplication(...) → Use new KoinApplication(configuration: ...)

Migration Guide

  // Before:
  KoinMultiplatformApplication(
      config = koinConfiguration {
          modules(appModule)
      }
  ) {
      MyApp()
  }

  // After:
  KoinApplication(
      configuration = koinConfiguration {
          modules(appModule)
      }
  ) {
      MyApp()
  }

Benefits

  • No more Koin restarts on theme changes
  • Single, clear API for all platforms
  • Better parameter naming (configuration vs config)
  • Safer configuration with KoinConfiguration
  • Automatic platform-specific setup (Android context/logger)

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Koin context recreation on Android theme changes and unifies the KoinApplication API. The core issue was that RememberObserver.onForgotten() was being misused for cleanup - this callback fires during temporary composition changes (like theme switches), not true abandonment. The fix moves cleanup logic to onAbandoned() for the application context and prevents module re-registration by conditionally creating KoinApplication based on existing context.

Key changes:

  • Fixed CompositionKoinApplicationLoader to only deallocate references in onForgotten(), not stop Koin entirely
  • Modified application/scope loaders to accept nullable KoinApplication and check for existing Koin context
  • Introduced new unified KoinApplication(configuration: KoinConfiguration, logLevel: Level) API with proper deprecations

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
CompositionKoinScopeLoader.kt CRITICAL BUG: Changed onForgotten() to call close(), contradicting PR intent and fix
RememberKoinApplication.kt Added null-checks to conditionally create KoinApplication only when Koin doesn't exist
CompositionKoinApplicationLoader.kt Made KoinApplication nullable, fixed onForgotten() to only deallocate (not stop), updated start() logic
KoinApplication.kt Added new unified API with deprecations, but has incorrect documentation and ReplaceWith parameter name
build.gradle Changed minSdkVersion from 21 to 23 without explanation in PR description

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +162 to +163
* @throws org.koin.core.error.KoinApplicationAlreadyStartedException if Koin is already started
*
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states that this function throws KoinApplicationAlreadyStartedException if Koin is already started, but this is inaccurate.

The implementation now handles the case where Koin is already started by passing null to CompositionKoinApplicationLoader (via rememberKoinMPApplication), which then re-attaches to the existing Koin instance instead of throwing an exception. This is part of the fix to handle theme changes gracefully.

The @throws documentation should be removed or updated to reflect the actual behavior.

Suggested change
* @throws org.koin.core.error.KoinApplicationAlreadyStartedException if Koin is already started
*

Copilot uses AI. Check for mistakes.
defaultConfig {
applicationId "org.koin.sample.androidx.compose"
minSdkVersion 21
minSdkVersion 23
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The minSdkVersion is changed from 21 to 23, but this change is not explained in the PR description. While this is just a sample app and API 23 aligns with other sample projects in the repository, it would be helpful to clarify whether this change is:

  1. Required for the Koin Compose fixes in this PR
  2. Simply aligning with Compose library requirements
  3. Unintentional and unrelated to this PR

If this change is unintentional or unrelated, consider removing it to keep the PR focused.

Suggested change
minSdkVersion 23
minSdkVersion 21

Copilot uses AI. Check for mistakes.
koin?.logger?.debug("CompositionKoinApplicationLoader - onForgotten")
//don"t stop here, premature
koin?.logger?.debug("CompositionKoinApplicationLoader - onForgotten - $this")
//don"t stop context, premature. Only de-allocate
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect apostrophe character in comment. Use don't instead of don"t.

Suggested change
//don"t stop context, premature. Only de-allocate
//don't stop context, premature. Only de-allocate

Copilot uses AI. Check for mistakes.
koin = KoinPlatform.getKoin()
koin!!.logger.debug("$this -> re-attach Koin instance $koin")
} else {
error("can't start Koin context, no koinApplication argument found nor existing context")
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message should start with a capital letter for consistency with other error messages in the codebase (e.g., line 43: "Can't start Koin from Compose context").

Change to: error("Can't start Koin context, no koinApplication argument found nor existing context")

Suggested change
error("can't start Koin context, no koinApplication argument found nor existing context")
error("Can't start Koin context, no koinApplication argument found nor existing context")

Copilot uses AI. Check for mistakes.
@arnaudgiuliani arnaudgiuliani merged commit 7e773ea into 4.2.0 Dec 5, 2025
@arnaudgiuliani arnaudgiuliani deleted the fix_CompositionKoinApplicationLoader_creation branch December 5, 2025 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants