DEV Community

Arsenii Kharlanow
Arsenii Kharlanow

Posted on

10

Koin - scopes

Koin is a simple-to-use DI library, without code generation.
In this article, I want to show how to use one of the features of Koin - scopes.

I suppose you faced a situation when you need to share an object between multiple components with lifetimes shorter than the app's lifetime. For example, you need to share some in-memory storage between two fragments inside one screen (it is more relevant for single activity app), in this case, you can use singleton but the singleton lifetime is equal to the app lifetime, or use the activity as a holder for this component.

One solution to this problem is to use Koin scopes. Scopes are a powerful tool that can help you manage the lifecycle of your components and ensure that objects are only created and destroyed when they're needed.

However, using scopes correctly isn't always straightforward. In this article, I'll share my experience and offer some tips on how to use Koin scopes effectively. By the end of this article, you'll better understand how to use scopes to manage your components' lifecycles and share objects between them.

According to the Koin documentation, Koin has 3 types of scopes:

  • single - in the case of Android lifetime of the component is equal to the app's lifetime
  • factory - create a new object each time
  • scoped - create an object that is persistently tied to the associated scope lifetime - and this is what we need.

Scoped is a singleton, but we have the ability to clear the object when we need and it will be recreated in the next time call. Inside Koin scopes are stored in the Map, where key is the name of the scope and clear scope means to remove it from this map.

Create a component with our own scope:

module {
    scope(named("myScope")) {
        scoped { Component() }
    }
}
Enter fullscreen mode Exit fullscreen mode

then we can use our component as a dependency in another component:

module {
   factory {
      Component2(
         component1 = getScope("myScope").get(),
      )
   }
}
Enter fullscreen mode Exit fullscreen mode

And we need to create a scope in the Fragment or Activity:

val scope = getKoin().createScope("myScope", named("myScope"))
Enter fullscreen mode Exit fullscreen mode

and when the components in the scope need to be removed, we can close the scope (for example we can call this method inside onDetach in the fragment):

scope.close()
Enter fullscreen mode Exit fullscreen mode

Koin provides a few methods for creating scope which is linked to the lifecycle of the Android components:
createActivityScope()
createActivityRetainedScope()
createFragmentScope()

Based on these methods we can create our own methods that can be used to create custom scopes.

For example, this extension method can be used for creating custom scopes inside Fragments:

fun Fragment.getOrCreateScope(scopeId: String): Scope {
    val scope = getScopeOrNull() ?: createScope()
    val activityScope = activity?.getScopeOrNull()
    activityScope?.let { scope.linkTo(it) }
    val customScope = getKoin().getScopeOrNull(scopeId) ?: getKoin().createScope(scopeId, named(scopeId))
    customScope.linkTo(scope)
    lifecycle.addObserver(object : LifecycleEventObserver {

        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            if (event == Lifecycle.Event.ON_DESTROY && customScope.closed.not()) {
                customScope.close()
            }
        }
    })
    return customScope
}
Enter fullscreen mode Exit fullscreen mode

Here we get or create a parent scope, then try to get an activity scope, and finally, create our own scope.
Method linkTo(...) is used for linking scopes for resolving dependencies.
The most important part here is adding a lifecycle observer in order to destroy our scope when the fragment is destroyed.

The same method we can create for Acivity:

fun AppCompatActivity.getOrCreateScope(scopeId: String): Scope {
    val activityScope = getScopeOrNull() ?: createScope()
    val customScope = getKoin().getScopeOrNull(scopeId) ?: getKoin().createScope(scopeId, named(scopeId))
    customScope.linkTo(activityScope)

    lifecycle.addObserver(object : LifecycleEventObserver {

        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            if (event == Lifecycle.Event.ON_DESTROY && customScope.closed.not()) {
                customScope.close()
            }
        }
    })
    return customScope
}
Enter fullscreen mode Exit fullscreen mode

In conclusion:

  • scope is the same as a singleton with the ability to destroy it when you want. Inside Koin scopes are stored in the map
  • when you define the scope in the DI, you need to create this scope in the Activity/Fragment where we want to use components that are defined with scopes.
  • you need to close the scope when the components became unused in other cases the scope will never be destroyed and it will be the same result as with using a 'singleton'
  • you can use Koin predefined methods for creating lifecycle-depending scopes, or create scopes and manage them by yourself.

Sentry mobile image

Improving mobile performance, from slow screens to app start time

Based on our experience working with thousands of mobile developer teams, we developed a mobile monitoring maturity curve.

Read more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay