Skip to main content
Version: 4.2

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