Hello!
I've tried to use scope feature with viewModel delegate function and I got the following result:
org.koin.core.error.ScopeNotCreatedException: No scope instance when trying to resolve [type:Scope,class:'CoursesUseCase']
I have two global scopes: AppScope and UserSessionScope.
So, when a user is logged in I create the scope with getKoin().createScope(Scopes.USER_SESSION)
And I have two questions:
The declaration of the module:
scope(Scopes.USER_SESSION) {
scoped { CoursesUseCase(get()) }
scoped<CoursesRepository> { CoursesRepositoryImpl(get()) }
scoped { ExpertsUseCase(get()) }
scoped<ExpertsRepository> { ExpertsRepositoryImpl(get()) }
viewModel {
PracticeViewModel(
router = get(),
coursesUseCase = get(),
expertsUseCase = get(),
resourceManager = get(),
schedulersProvider = get()
)
}
}
Inside the fragment I'm trying to obtain the viewModel like this:
private val viewModel: PracticeViewModel by viewModel() but I've got the error described above
It would be awesome if it were possible to combine these features, because that's a common approche to determine scopes and this error get in a way to use Koin.
Koin version:
koin-android version 2.0.0-beta-1
in next beta 馃憤
To me it seems to be still reproduced in the 2.0.0-rc-1.
Unfortunately, scope API doesn't look rather stable on Android.
Also, in the version 1.0.3 there was Scope#addInstance method which is used by inject a new instance to the scoped dependency tree. I can't find the replacement of this method in the 2.0.0.
To me it seems to be still reproduced in the
2.0.0-rc-1.
Unfortunately, scope API doesn't look rather stable on Android.Also, in the version 1.0.3 there was
Scope#addInstancemethod which is used by inject a new instance to the scoped dependency tree. I can't find the replacement of this method in the 2.0.0.
Unfortunately, I'm facing the same issue with the version 2.0.0-RC1. I observed the same exception as the @RcmJava one.
I managed to replicate the problem using @RcmJava's code, however after minor changes I got it working.
For instance:
enum class Scopes {
USER_SESSION
}
val appModule = module {
scope(named(Scopes.USER_SESSION.name)) {
scoped { GetUserDetailsUseCase() }
viewModel { ProtectedActivity.MyViewModel(get()) }
viewModel { LogoutActivity.MyViewModel(get()) }
viewModel { MainActivity.MyViewModel(get()) }
}
}
ProtectedActvity & LogoutActivity
private val viewModel: MyViewModel by viewModel(
scope = getKoin().getScope(Scopes.USER_SESSION.name)
)
Auth scope is created in MainActivity upon a login action using:
getKoin().createScope(Scopes.USER_SESSION.name, named(Scopes.USER_SESSION.name))
.. and same scope is destroyed in LogoutActivity upon a logout action:
getKoin().getScopeOrNull(Scopes.USER_SESSION.name)?.close()
getKoin().deleteScope(Scopes.USER_SESSION.name)
Note that I used Scopes.USER_SESSION.name as a scope id as well. That can be something else.
Take also into account that this code can be cleaned up and written better. :)
I don't see how you can compile the 3 viewModel declarations in a scope() { ... } block. The compiler says that you can't use viewModel within a Scope.
put viewModel keywords outside of scope section 馃憤
from your constructor, pass the scope id or scope to resolve your dependency
I'm not sure what you mean. If you have time, an example would make it clear.
`
Thanks for your comments, i had the same problem.
@MonteCreasor
I think that @arnaudgiuliani means to pass scope as parameter of the factory like the example in the documentation : https://insert-koin.io/docs/2.0/documentation/koin-android/index.html#_scope_features_for_android
```module {
// Shared user session data
scope(named("session")) {
scoped { UserSession() }
}
// Inject UserSession instance from "session" Scope
viewModel { (scopeId : ScopeID) -> MyViewModel(getScope(scopeId).get())}
}
And use it like :
```module {
private val myViewModel: MyViewModel by viewModel{ parametersOf(myScope.id)}
}
It works fine for me 馃憤
Thanks =)
Great explanation! Thanks for taking the time.
this been improved in 2.0.0 stable (out today)
just put your ViewModel definition in the scope, and it will resolve it. No need to pass reference manually
// Shared user session data
scope(named("session")) {
scoped { UserSession() }
viewModel { MyViewModel(get()) }
}
@arnaudgiuliani I am using viewModel in the scope but I kept getting an error if I used by viewModel() for a viewModel with scope. This is what I am doing.
// Shared user session data
scope("session_id",named("session")) {
scoped { UserSession() }
viewModel { MyViewModel(get()) }
}
In the Android activity
override val viewModel: MyViewModel by viewModel()
I kept getting
No definition found for 'MyViewModel' has been found.
However, it works if I do this
override val viewModel: MyViewModel by getKoin().getScope("session_id").viewModel(this)
Why can't viewModel() find the viewModel def from the scope without having to specify it?
You can't use your MyViewModel instance directly, as it is declared in a scope.
Try something like:
scope(named("session")) {
scoped { UserSession() }
viewModel { MyViewModel(get()) }
}
// create scope instance
val myScope = getKoin().getScope("session_id", named("session"))
// retrieve ViewModel instance from scope
override val viewModel: MyViewModel by myScope.viewModel(this)
@arnaudgiuliani how is a viewmodel even tied to a custom scope?..Isn't viewmodel just tied to either in activity or a fragment?.
Would a viewmodel be able to survive even if its holding activity is killed but its scope is alive?
Confused here.
2 mixings here:
To sum up: Scopes in Android are a good way to maintain instances between activities
You can't use your
MyViewModelinstance directly, as it is declared in a scope.
Try something like:scope(named("session")) { scoped { UserSession() } viewModel { MyViewModel(get()) } }// create scope instance val myScope = getKoin().getScope("session_id", named("session")) // retrieve ViewModel instance from scope override val viewModel: MyViewModel by myScope.viewModel(this)
I have a parent Fragment with a viewpager. How do I use the same ViewModel for all the viewpager fragments, while tying the ViewModel to the parent fragment's scope.
So the owner should not be "this" but the parent fragment.. Struggling to get it working as the parent fragment is not attached on initialization yet (so I can't just use parentFragment)
Any ideas?
is this valid code when I want to reduce the boilerplate of fetching scope and injecting dependencies?
factory {
val repositoriesScope = getKoin().getOrCreateScope("repositories", named("repositories"))
repositoriesScope
}
viewModel {
DiscoverViewModel(
androidContext(),
get<Scope>().get(),
get<Scope>().get(),
Dispatchers.IO
)
}
Also, what if a view model survives the scope and has a reference to an object from a previous closed scope? Is there a way to refresh that dependency?
You can't use your
MyViewModelinstance directly, as it is declared in a scope.
Try something like:scope(named("session")) { scoped { UserSession() } viewModel { MyViewModel(get()) } }// create scope instance val myScope = getKoin().getScope("session_id", named("session")) // retrieve ViewModel instance from scope override val viewModel: MyViewModel by myScope.viewModel(this)I have a parent Fragment with a viewpager. How do I use the same ViewModel for all the viewpager fragments, while tying the ViewModel to the parent fragment's scope.
So the owner should not be "this" but the parent fragment.. Struggling to get it working as the parent fragment is not attached on initialization yet (so I can't just use parentFragment)
Any ideas?
Hi, @hendrep
I faced the same issue as you, and found solution: you should replace
val viewModel: MyViewModel by scope.viewModel(requireParentFragment())
to
val viewModel by lazy {
scope.getViewModel<MyViewModel>(requireParentFragment())
}
You probably will ask, what the difference, if both variants are lazy.
The difference is, that requireParentFragment() in the second variant will be called when fragment is already attached and it should work as expected.
Most helpful comment
this been improved in 2.0.0 stable (out today)
just put your ViewModel definition in the scope, and it will resolve it. No need to pass reference manually