Let's say I declare a module as follows:
val myModule = module {
scope(named<MyActivity>()) {
scoped { MyPresenter() }
}
}
Then inside MyActivity:
private val presenter: MyPresenter by currentScope.inject()
MyActivity was hosting some fragments that wanted to access the same scoped MyPresenter instance. What would be the recommended way to go about doing that? What I can think of is (inside MyFragment):private val presenter: MyPresenter by requireActivity().currentScope.inject()
MyFragment using FragmentScenario (Robolectric / AndroidJUnit runner) where I cannot control what activity the fragment is launched in (I believe), how do I satisfy the presenter required by MyFragment? Furthermore, how do I declareMock() the presenter inside MyFragment? Or is using requireActivity().currentScope not ideal for tests like this, and I should be creating and managing / binding scopes myself so I can manage them myself during tests?Just got around to testing and
private val presenter: MyPresenter by requireActivity().currentScope.inject()
doesn't work inside the fragment.. and rightfully so because the fragment wouldn't be attached to an activity at the time of the fragment's creation 🤦♂️
So I guess this?
private val presenter by lazy { requireActivity().currentScope.get<MyPresenter>() }
Feels a little hacky though. Not sure if that would be the recommended way..
Still don't have any workarounds in terms of how to write the fragment tests though.
Yes that's how we do it, using a lazy.
On Wed, 3 Apr 2019, 23:47 Shaurya Arora, notifications@github.com wrote:
Just got around to testing and
private val presenter: MyPresenter by requireActivity().currentScope.inject()
doesn't work inside the fragment.. and rightfully so because the fragment
wouldn't be attached to an activity at the time of the fragment's creation
🤦♂️So I guess this?
private val presenter by lazy { requireActivity().currentScope.get
() } Feels a little hacky though. Not sure if that would be the recommended
way..—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/InsertKoinIO/koin/issues/418#issuecomment-479671105,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAL_ZRavvtUBBokXVIEh7LNJtaYpty1aks5vdSF1gaJpZM4cbTLW
.
For anyone coming to this, I decided to do something a little different. We use a VIP (View - Interactor - Presenter) pattern in our app and I like communication only flowing in one direction (View -> Interactor -> Presenter and back around to the View). Hence, I set up a nested scope as follows (Interactor lives inside View scope, Presenter lives inside Interactor scope):
val myModule = module {
scope(named<MyActivity>()) {
scoped<MyContract.Interactor> { MyInteractor() }
}
scope(named<MyInteractor>()) {
scoped<MyContract.Presenter> { MyPresenter() }
}
}
Then, inside MyActivity:
class MyActivity : BaseActivity() {
private val scope = getKoin().getOrCreateScope(SCOPE_ID, named<MyActivity>()).apply {
bindScope(this)
registerCallback(object : ScopeCallback {
override fun onScopeClose() {
interactor.onDestroy()
}
})
}
private val interactor: MyContract.Interactor = scope.get()
...
````
(`SCOPE_ID` is just some constant).
And inside `MyInteractor`:
class MyInteractor : MyContract.Interactor, KoinComponent {
private val scope = getKoin().getOrCreateScope(toString(), named<MyInteractor>()).apply {
registerCallback(object : ScopeCallback {
override fun onScopeClose() {
presenter.onDestroy()
}
})
}
private val presenter: PaymentContract.Presenter = scope.get()
...
Inside `MyPresenter`, the `onDestroy()` basically clears all references to the `View`s.
Now, in case `MyActivity` is hosting multiple fragments that need access to the same Presenter and Interactor instances, we just get `MyActivity`'s scope inside those fragments like so:
class Fragment1 : BaseFragment(), MyContract.View1 {
private val activityScope = getKoin().getScope(MyActivity.SCOPE_ID)
private val interactor: MyContract.Interactor by activityScope.inject()
```
The reason to do all this myself (instead of using currentScope) is to have more control over the scopes in my tests. For instance, with the new FragmentScenario testing library, you cannot really control the Activity that the fragment is launched inside. But using my own scopes, I can just startKoin() with an overriding module definition and create that scope myself before the fragment is launched.
Hope this helps someone!
Testing with scoped something. In this example, the Presenter :)
val appModule = module {
// single instance of HelloRepository
single<HelloRepository> { HelloRepositoryImpl() }
// scoped MainPresenter instance
scope(named<MainActivity>()) {
scoped { MainPresenter(get()) }
}
}
class MainActivity : AppCompatActivity() {
// inject MainPresenter from current scope
private val scopedPresenter: MainPresenter by currentScope.inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView.text = scopedPresenter.sayHello()
}
}
class MainPresenter(val repo: HelloRepository) {
fun sayHello() = repo.giveHello()
}
class MainPresenterTest: KoinTest {
private val scope by lazy {
getKoin().getOrCreateScope(ScopeID(), named<MainActivity>())
}
private val scopedPresenter by lazy {
scope.get<MainPresenter>()
}
@Before
fun before() {
startKoin {
modules(appModule)
}
}
@Test
fun testSayHello() {
assert(scopedPresenter.sayHello() == "Hello Koin")
}
}
I had a requirement for fragment to access Koin Object created with Activity Scope. Using the newer AndroidX dependencyorg.koin:koin-androidx-scope, here is what worked for me
val appModule = module {
// scoped MainPresenter instance
scope(named<MainActivity>()) {
scoped { MainPresenter(get()) }
}
}
Fragment:
import org.koin.androidx.scope.lifecycleScope as koinLifecycleScope
class MyFragment: Fragment() {
private val scopedPresenter by lazy {
requireActivity().koinLifecycleScope.get<MainActivity>()
}
......
}
Most helpful comment
Just got around to testing and
doesn't work inside the fragment.. and rightfully so because the fragment wouldn't be attached to an activity at the time of the fragment's creation 🤦♂️
So I guess this?
Feels a little hacky though. Not sure if that would be the recommended way..
Still don't have any workarounds in terms of how to write the fragment tests though.