How to inject WeakReference Fragment using Koin - Android - android

I have a class that relies on WeakReference<Fragment>.
class ExampleManager(reference: WeakReference<Fragment>)
How would I inject ExampleManager constructor?
val exampleModule = module {
factory { ExampleManager(get()) }
}
private val exmpManager: ExampleManager by inject()
At the end I receive error:
No definition found for class:'java.lang.ref.WeakReference'. Check your definitions!
How can I implement definition for WeakReference<Fragment> in my case?

In order to inject a WeakReference using Koin, you can create a factory function that creates the WeakReference and use it in your Koin module definition.
Here's an example:
// Factory function to create WeakReference<Fragment>
fun createWeakRef(fragment: Fragment) = WeakReference(fragment)
// Koin module definition
val exampleModule = module {
factory { (fragment: Fragment) -> ExampleManager(createWeakRef(fragment)) }
}
Then, you can use the by inject() delegate to inject the ExampleManager class in your activity or fragment:
class ExampleFragment : Fragment() {
private val exmpManager: ExampleManager by inject { parametersOf(this) }
// ...
}
Here, parametersOf(this) is used to pass the current fragment instance to the factory function.
It is important to mention that, WeakReference is a java class and you should import it using import java.lang.ref.WeakReference
Make sure you have included the created module in your Koin's startKoin function
i recommend to use'lazy' when you are injecting instances that are only used in certain conditions, this can help to avoid unnecessary instantiation and memory leaks.
private val exmpManager: ExampleManager by inject { parametersOf(this) }.lazy
This way, exmpManager will be instantiated only when it is accessed for the first time.

Related

Koin. Cannot inject to fragment with ScopeActivity

I'm trying to inject some dependency to both activity and fragment using Koin and I expect it to live as long as activity lives, but it turned out a headache for me.
I managed to create a module that resolves MainRouter, inject it into an activity, but it doesn't work for a fragment.
val appModule = module {
scope<MainActivity> {
scoped { MainRouter() }
}
}
MainActivity extends ScopeActivity, MyFragment extends ScopeFragment.
in MainActivity private val router : MainRouter by inject() works fine, but in MyFragment it throws org.koin.core.error.NoBeanDefFoundException: No definition found for class:'com.example.app.MainRouter'. Check your definitions!
Finally I managed to inject, but it doesn't look pretty
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val scopeId = scopeActivity!!.getScopeId()
scope.linkTo(getKoin().getScope(scopeId))
mainRouter = get()
...
I also don't like that scopeActivity can't be accessed in the init method. Does this mean that activity scoped dependencies cannot be resolved in fragment using by inject()?
As I can see in your code, you have to declare a Fragment instance, just declare it as a fragment in your Koin module and use constructor injection. Like below:
val appModule = module {
single { MyService() }
fragment { MyFragment(get()) }
}
Please refer link for more details.

Is it possible to inject a conditional class( based on a parameter from the previous fragment) into a view model?

I'm using Hilt. I want to inject a subclass of Foo into my hilt view model.
All subclasses of Foo depend on different class that is already using an #Inject constructor and can be injected into view models, activities, etc. But not into my subclass, so I'm using EntryPoints to inject them.
Also, which subclass gets injected depends upon a property I'm getting from the previous fragment via the SavedStateHandle Hilt provides the view model.
Is it possible to create a Factory (or another solution) that somehow gets the param from the previous fragment and injects the correct Foo object?
I have a solution that doesn't use Hilt to get the Foo object, it just instantiates the right object using a conditional on the param. This solution is not testable and I don't like it.
// in the view model I would like to do this
//
// #Inject
// lateinit var thatFooINeed: Foo
//
// But thatFooINeed could be the Foo with Dependency1 or Dependency2
// It depends on the param sent from the previous fragment
interface Foo {
fun doThis()
fun doThat()
}
class Bar1(context: Context): Foo {
private val dependencyInterface =
EntryPoints.get(context, DependencyInterface::class.java)
val dependency1: Dependency1 = dependencyInterface.getDependency1()
// override doThis() and doThat() and use ^ dependency
...
}
class Bar2(context: Context): Foo {
private val dependencyInterface =
EntryPoints.get(context, DependencyInterface::class.java)
val dependency2: Dependency2 = dependencyInterface.getDependency2()
// override doThis() and doThat() and use ^ dependency
...
}
#EntryPoint
#InstallIn(SingletonComponent::class)
interface DependenciesInterface {
fun getDependency1(): Dependency1
fun getDependency2(): Dependency2
}
class Dependency1 #Inject constructor(val yetAnotherDep: ButWhosCounting)
class Dependency2 #Inject constructor(val yetAnotherDep: ButWhosCounting)```
You have to adopt #AssistedInject, assisted injection will work as factory for your case. Inject the factory and then use the factory to lazily create the instance of the interface.

Cannot create an instance of class com.example.event.ui.main.EventsViewModel

I try to create instance of ViewModel, but get an error.
java.lang.RuntimeException: Cannot create an instance of class com.example.event.ui.main.EventsViewModel
my code here to get instance:
viewModel = ViewModelProvider(this).get(EventsViewModel::class.java)
view model class
class EventsViewModel #Inject constructor(private val eventApi: EventApi): BaseViewModel() {
val getEventsData = MutableLiveData<Response<List<Event>>>()
fun getEventsData() {
uiScope.launch {
getEventsData.value = eventApi.getEvents()
}
}
}
Here, your ViewModelProvider is not aware of how to create EventsViewModel. If your ViewModel constructor was an empty constructor without any dependencies, it would work. However, if there is dependency or a parameter in your ViewModel constructor you need to provided a custom ViewModelProviderFactory.
If you want to check how/why custom view model factory is created you can check this codelab:
https://developer.android.com/codelabs/kotlin-android-training-view-model#7
If you want to employ dagger for injecting dependencies into your ViewModel, you can find an example in my sample repo:
https://github.com/hakanbagci/truemvvm

Can't use injected value as constructor of another injection

I would like to define 2 injected classes, but one needs to use the second class method for the constructor. I am using Koin framework
class MainActivity : AppCompatActivity() {
private val connectionService : ConnectionService by inject()
private val resourcesHelper : ResourcesHelper by inject()
private val addressPropertyName = "connection.address"
private val portPropertyName = "connection.port"
private val appModule = module {
single { ResourcesHelperImpl(androidContext(), R.raw.config) }
single {
ConnectionServiceTcp(
resourcesHelper.getConfigValueAsString(addressPropertyName),
resourcesHelper.getConfigValueAsInt(portPropertyName)
)
}
}
And then I get an error because I cannot instantiate ConnectionServiceTcp using resourcesHelper. Is there a way to use injected field to inject another field?
Edit
Changing to get() helped, but now I struggle with module configuration.
I moved start koin to MainApplication class:
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this#MainApplication)
androidLogger()
modules(appModule)
}
}
}
And module to AppModule.kt
val appModule = module {
single { ResourcesHelperImpl(androidContext(), R.raw.drone) }
single {
ConnectionServiceTcp(
get<ResourcesHelper>().getConfigValueAsString(ResourcesHelper.droneAddressPropertyName),
get<ResourcesHelper>().getConfigValueAsInt(ResourcesHelper.dronePortPropertyName)
)
}
scope(named<MainActivity>()) {
scoped {
ConnectionServiceTcp(get(), get())
}
}
}
And then I try to inject some object to activities and I am getting
Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for has been found. Check your module definitions.
Okay, I encountered two problems, firstly I could not instantiate bean using other bean in constructor, it was resolved by changing my invoke to
ConnectionServiceTcp(
get<ResourcesHelper>().getConfigValueAsString(ResourcesHelper.droneAddressPropertyName),
get<ResourcesHelper>().getConfigValueAsInt(ResourcesHelper.dronePortPropertyName)
)
Secondly, there was a problem with NoBeanDefFoundException, it was due androidContext() in ResourcesHelperImpl, I needed there Context from the activity, not the koin context.

How to inject a ViewModel with Koin in Kotlin?

How do we inject ViewModel with dependency using Koin?
So For Example I have a ViewModel thats like this:
class SomeViewModel(val someDependency: SomeDependency, val anotherDependency: AnotherDependency): ViewModel()
Now the official docs here, states that to provide a ViewModel we could do something like:
val myModule : Module = applicationContext {
// ViewModel instance of MyViewModel
// get() will resolve Repository instance
viewModel { SomeViewModel(get(), get()) }
// Single instance of SomeDependency
single<SomeDependency> { SomeDependency() }
// Single instance of AnotherDependency
single<AnotherDependency> { AnotherDependency() }
}
Then to inject it, we can do something like:
class MyActivity : AppCompatActivity(){
// Lazy inject SomeViewModel
val model : SomeViewModel by viewModel()
override fun onCreate() {
super.onCreate()
// or also direct retrieve instance
val model : SomeViewModel= getViewModel()
}
}
The confusing part for me is that, normally you will need a ViewModelFactory to provide the ViewModel with Dependencies. Where is the ViewModelFactory here? is it no longer needed?
Hello viewmodel() it's a Domain Specific Language (DSL) keywords that help creating a ViewModel instance.
At this [link][1] of official documentation you can find more info
The viewModel keyword helps declaring a factory instance of ViewModel.
This instance will be handled by internal ViewModelFactory and
reattach ViewModel instance if needed.
this example of koin version 2.0 [1]: https://insert-koin.io/docs/2.0/documentation/koin-android/index.html#_viewmodel_dsl
// Given some classes
class Controller(val service : BusinessService)
class BusinessService()
// just declare it
val myModule = module {
single { Controller(get()) }
single { BusinessService() }
}

Categories

Resources