Scopes
Scopes control the lifecycle of your dependencies. This guide covers how to define, create, and manage scopes.
Understanding Scopes
| Scope Type | Lifecycle | Example |
|---|---|---|
| Single (Singleton) | App lifetime | Database, ApiClient |
| Factory | Per request | Presenters, Use Cases |
| Scoped | Per scope | Activity-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
@Scoped(MyActivityScope::class)
class Presenter(private val repository: UserRepository)
@Scoped(MyActivityScope::class)
class Navigator
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 {
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 {
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.
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 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:
- All scoped instances are released
onClosecallbacks are invoked- 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
}
}
Android Scope Components
Activity Scope
class MainActivity : AppCompatActivity(), AndroidScopeComponent {
override val scope: Scope by activityScope()
// Scope closed automatically in onDestroy()
}
Fragment Scope
class MyFragment : Fragment(), AndroidScopeComponent {
override val scope: Scope by fragmentScope()
// Scope closed automatically in onDestroy()
}
ViewModel Scope (4.1+)
startKoin {
options {
viewModelScopeFactory() // Enable ViewModel scopes
}
}
val appModule = module {
viewModel<UserViewModel>()
scope<UserViewModel> {
scoped<UserCache>()
}
}
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
- Use singletons sparingly - Only for truly app-wide dependencies
- Scope shared state - When multiple components need the same instance
- Close scopes explicitly - Don't rely on garbage collection
- Keep scopes focused - Don't put everything in one scope
- Use Android scope components - For automatic lifecycle management
Next Steps
- Koin for Android - Android-specific scopes
- Koin for Compose - Scopes in Compose
- Best Practices - Scope patterns