Lessons Learnt Migrating to Koin 3


Wow, I haven’t written an article in 4 years! It was a combination of procrastination and just feeling like I didn’t have anything worth writing an article about (likely not true). But here we are, it’s good to be back!
Ok now, for what the article is about, Koin! I have a project where I use Koin for dependency injection (or service location for the DI snobs), and I’ve been on version 2 for a long time, just because I didn’t feel like migrating. But this week I finally had the time, and decided lets go to Koin 3, fix any problems that pop up, and then after that we’ll go to Koin 4.
So I updated my grade dependencies to v3, synced, and built the app. A lot of problems appeared as expected. But overall they could be categorized into a few main problem groups.
koin.createRootScope() no longer exists
Usually in your application subclass, you have a bunch of setup code in your startKoin
block, something like this:
startKoin {
androidLogger()
androidContext(this)
val modules = // Some list of modules
koin.loadModules(modules)
koin.createRootScope()
}
In Koin 3, this method was removed, and we no longer need to create a root scope in our Application
class. So you can simply delete that line.
get() is no longer available as a top-level import
In a lot of classes that extend KoinComponent. I was using the top-level get() function to satisfy dependencies.
class SomeClass: KoinComponent {
fun saveSomeState() {
get<LocalRepository>().saveSomeState(SomeState())
}
}
The recommended way to fix this is to replace such usages by using inject()
to provide the dependencies, as you always have in other situations.
SomeClass: KoinComponent {
private val localRepository: LocalRepository by inject()
fun saveSomeState() {
localRepository.saveSomeState(SomeState())
}
}
If that is not possible though, as it wasn’t for me in some situations, you can still get access to get()
by using getKoin().get() in any subclass of KoinComponent. So now you have something likegetKoin().get().saveOnboardingState(OnboardingState())
Changes to how to provide a ViewModel
Honestly, this was the biggest one based on how many places in the codebase needed to be refactored. The app uses the MVVM architecture with one ViewModel per activity. It is sort of enforced with a BaseActivity like this:
abstract class BaseActivity<VM : BaseViewModel> : AppCompatActivity() {
protected lateinit var viewModel: VM
protected abstract val viewModelClass: KClass<VM>
open fun provideViewModel(): VM = getViewModel(viewModelClass)
}
And I can hear a few of you booing from behind. Pardon me, this is from back when inheritance in Android development was cool. The point is though, you need to provide a viewModel class and we use getViewModel()
to provide it for you if it’s a regular viewModel with no runtime or assisted parameters. If you have parameters however you can just override provideViewModel()
.
getViewModel() is deprecated.
Ok, this brings us to the first change getViewModel()
is deprecated. Instead prefer using Koin’s viewModel()
delegate. Something like:private val viewModel: AnnouncementViewModel by viewModel()
or if you have parametersprivate val viewModel: AnnouncementViewModel by viewModel { parametersOf(param1, param2) }
Pretty easy, which is the reason why people love Koin in the first place!
Ok, but there’s a problem here, this doesn’t really fit in with the existing structure and architecture here, with a BaseActivity where I don’t know the exact class yet. So in order to fix this, we rely on our good friend getKoin()
. And basically all usages of getViewModel(viewModelClass)
become
getKoin().get<viewModelClass>() // If you have no parameters or
// If you have some parameters to inject
getKoin().get<viewModelClass>(
parameters = {
parametersOf(
intent.getStringExtra(COMMUNITY_ID_EXTRA)
)
}
}
The parameter version of the above example was super important because that was a common pattern in the app. If there was a runtime parameter to be injected in an Activity or Fragment. Usually, I override provideViewModel()
and use getViewModel(viewModelClass)
to provide the param in the actual Activity since it should be available at that point.
getSharedViewModel is deprecated.
I have scenarios in the app where multiple fragments in the same activity are using the activity’s viewModel. In those cases, the viewModel was usually provided usinggetSharedViewModel(viewModelClass)
. That is now deprecated, and I have replaced such usages with getActivityViewModel<viewModelClass>()
instead.
Yeah, that was pretty much it! At least for my use case. Overall the migration wasn’t much work, the biggest challenge was finding the information for what is the proper replacement for certain things. Once those were figured out, everything was straightforward! I’ll probably make another post when I migrate to Koin 4, whenever I’m able to finally get to it! 😁
Subscribe to my newsletter
Read articles from Isaac directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
