Skip to main content
Version: 4.2 - LTS

Scopes

Scopes control the lifecycle of your dependencies. This guide covers how to define, create, and manage scopes.

Understanding Scopes​

Scope TypeLifecycleExample
Single (Singleton)App lifetimeDatabase, ApiClient
FactoryPer requestPresenters, Use Cases
ScopedPer scopeActivity-bound, Session-bound

When to Use Scopes​

Use scopes when you need:

  • Instances that live longer than a factory but shorter than a singleton
  • Shared state within a specific context (Activity, Fragment, Session)
  • Automatic cleanup when a context ends

Defining Scoped Definitions​

DSL​

val appModule = module {
// Scope for MyActivity
scope<MyActivity> {
scoped<Presenter>()
scoped<Navigator>()
}

// Named scope
scope(named("session")) {
scoped<SessionData>()
scoped<UserPreferences>()
}
}

Annotations​

AnnotationDSL EquivalentPurpose
@Scopescope<T> { }Specify which scope a class belongs to
@Scopedscoped<T>()Define a scoped binding

A scoped class needs both @Scoped and @Scope:

@Scope(MyActivityScope::class)
@Scoped
class Presenter(private val repository: UserRepository)

@Scope(MyActivityScope::class)
@Scoped
class Navigator

Or use scope archetype annotations for common Android scopes (no @Scoped needed):

// ViewModel scope
@ViewModelScope
class UserCache

// Activity scope
@ActivityScope
class ActivityPresenter

@ActivityRetainedScope
class RetainedPresenter

// Fragment scope
@FragmentScope
class FragmentPresenter

Creating and Using Scopes​

Manual Scope Management​

// Create a scope
val myScope = getKoin().createScope("my_scope_id", named("session"))

// Get instances from scope
val sessionData: SessionData = myScope.get()
val prefs: UserPreferences = myScope.get()

// Close when done
myScope.close()

Android Activity Scope​

class MyActivity : AppCompatActivity(), AndroidScopeComponent {
// Automatically create and destroy Scope based on Activity Lifecycle
override val scope: Scope by activityScope()

// Scoped instances - created per Activity instance
private val presenter: Presenter by inject()

override fun onDestroy() {
super.onDestroy()
// Scope automatically closed
}
}

Android Fragment Scope​

class MyFragment : Fragment(), AndroidScopeComponent {
// Automatically create and destroy Scope based on Fragment Lifecycle
override val scope: Scope by fragmentScope()

private val presenter: Presenter by inject()
}

Scope Types​

Type-Based Scope​

scope<MyActivity> {
scoped<ActivityPresenter>()
}

The scope is identified by the type MyActivity. This scope is only triggered by MyActivity, whereas activityScope is a generic one.

Named Scope​

scope(named("user_session")) {
scoped<SessionManager>()
}

Use when the scope isn't tied to a specific type.

Qualifier-Based Scope​

scope(named<MyQualifier>()) {
scoped<ScopedService>()
}

Scope Archetypes​

Koin provides dedicated DSL for common Android scope patterns. These archetypes simplify scope definition for ViewModel, Activity, and Fragment.

ViewModel Scope​

Define dependencies scoped to a ViewModel's lifecycle:

val appModule = module {
viewModelScope {
scoped<UserCache>()
scoped<UserRepository>()
viewModel<UserViewModel>()
}
}

The ViewModel automatically gets access to its scoped dependencies:

class UserViewModel(
private val cache: UserCache, // Scoped to this ViewModel
private val repository: UserRepository
) : ViewModel()

Activity Scope​

Define dependencies scoped to an Activity's lifecycle:

val appModule = module {
activityScope {
scoped<ActivityPresenter>()
scoped<ActivityNavigator>()
}
}

Fragment Scope​

Define dependencies scoped to a Fragment's lifecycle:

val appModule = module {
fragmentScope {
scoped<FragmentPresenter>()
}
}

Comparison​

ArchetypeDSLAnnotationLifecycle
ViewModelviewModelScope { }@ViewModelScopeViewModel cleared
ActivityactivityScope { }@ActivityScopeActivity destroyed
Activity RetainedactivityRetainedScope { }@ActivityRetainedScopeActivity finished
FragmentfragmentScope { }@FragmentScopeFragment destroyed
info

Scope archetypes are available in Koin 4.0+. They provide a cleaner syntax than manually defining scope<T> { } for common Android components.

Scope Linking​

Link scopes to access parent scope definitions:

val appModule = module {
// Activity scope
scope<MainActivity> {
scoped<ActivityData>()
}

// Fragment scope linked to Activity
scope<UserFragment> {
scoped<FragmentPresenter>()
}
}
class UserFragment : Fragment(), AndroidScopeComponent {
override val scope: Scope by fragmentScope()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// Link to parent Activity scope
scope.linkTo((requireActivity() as AndroidScopeComponent).scope)

// Now can access both Fragment and Activity scoped instances
val fragmentPresenter: FragmentPresenter by inject()
val activityData: ActivityData by inject() // From linked scope
}
}

Scope Source​

Inject dependencies that are aware of their scope:

class Presenter(
val scope: Scope // Injected by Koin
) {
fun clearScope() {
scope.close()
}
}

scope<MyActivity> {
scoped { Presenter(get()) } // Scope injected
}

Scope Instance ID​

Each scope instance has a unique ID:

// Create with explicit ID
val scope1 = getKoin().createScope("scope_1", named("session"))
val scope2 = getKoin().createScope("scope_2", named("session"))

// Different instances, same scope type
scope1.get<SessionData>() !== scope2.get<SessionData>()

Accessing Scoped Instances​

From Within Scope​

class MyActivity : AppCompatActivity(), AndroidScopeComponent {
override val scope: Scope by activityScope()

// Directly inject scoped instances
private val presenter: Presenter by inject()
}

From Outside Scope​

// Get or create scope
val myScope = getKoin().getOrCreateScope("my_id", named("session"))

// Get instance
val session: SessionData = myScope.get()

In Compose​

@Composable
fun MyScreen() {
// Create scope tied to Composable lifecycle
val scope = rememberKoinScope(named("screen_scope"))

// Get scoped instance
val presenter: ScreenPresenter = scope.get()
}

Scope Lifecycle​

Closing Scopes​

When a scope closes:

  1. All scoped instances are released
  2. onClose callbacks are invoked
  3. Scope becomes unusable
val scope = getKoin().createScope("my_scope", named("session"))

// Use the scope
val data: SessionData = scope.get()

// Close when done
scope.close() // SessionData instance released

// This throws an exception
// scope.get<SessionData>() // Error: Scope is closed

onClose Callback​

scope(named("session")) {
scoped {
SessionData()
} onClose {
it?.cleanup() // Called when scope closes
}
}

Common Patterns​

Session Scope​

val appModule = module {
scope(named("user_session")) {
scoped { SessionManager() }
scoped { UserPreferences(get()) }
scoped { CartRepository(get()) }
}
}

// Login
fun onLogin(userId: String) {
val sessionScope = getKoin().createScope(userId, named("user_session"))
// Session instances now available
}

// Logout
fun onLogout(userId: String) {
getKoin().getScopeOrNull(userId)?.close()
// Session instances released
}

Feature Scope​

val appModule = module {
scope(named("checkout")) {
scoped { CheckoutNavigator() }
scoped { CheckoutPresenter(get()) }
}
}

class CheckoutActivity : AppCompatActivity(), AndroidScopeComponent {
override val scope: Scope by lazy {
getKoin().createScope("checkout_${hashCode()}", named("checkout"))
}

override fun onDestroy() {
super.onDestroy()
scope.close()
}
}

Best Practices​

  1. Use singletons sparingly - Only for truly app-wide dependencies
  2. Scope shared state - When multiple components need the same instance
  3. Close scopes explicitly - Don't rely on garbage collection
  4. Keep scopes focused - Don't put everything in one scope
  5. Use Android scope components - For automatic lifecycle management

Next Steps​