retrieve fragment with koin - android

My project has multi modules, and i am using koin. I have HomeActivity which is in home module and AskQuestionFragment which is in feature module. I need to show AskQuestionFragment in HomeActivity, so i try to inject fragment with koin.
factory (named("askFragment")) { AskQuestionFragment() }
then in HomeActivity i write this
private val fragmentAsk by inject<Fragment>(named("askFragment"))
override fun onCreate(...) {
val pagerAdapter = PagerAdapter(fragmentAsk, fragmentOther, fragmentOther2, fragmentBlablabla)
viewPager.adapter = pagerAdapter
}
It gives me error Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for class:'androidx.fragment.app.Fragment' & qualifier:'askFragment'. Check your definitions!.
For testing purpose, i tried to change
factory (named("askFragment")) { AskQuestionFragment() } to be factory (named("askFragment")) { 74521647256142765412 }
And it run well, so i think koin is not support fragment injection
How to solve it?

Your issue is that you're trying to inject the subtype "Fragment" but your bean definition is for your class "AskQuestionFragment".
Change your declaration like so:
private val fragmentAsk by inject<AskQuestionFragment>(named("askFragment"))
Alternatively you can declare your type like so:
private val fragmentAsk: AskQuestionFragment by inject(named("askFragment"))

Related

Inject String parameter into ViewModel from Compose using Koin

As the title says I want to inject two string params into my ViewModel from my Compose Activity using Koin. And I don't want to create a Factory ViewModel.
I saw how to inject Objects but I'm confused when it comes to parameters. This was so simple using Dagger Hilt I feel stupid for asking this..Any tips please?
I call this from the compose activity
val someViewModel: SnapshotViewModel by viewModel {
parametersOf(displayName, securityName)
}
and in my Koin Module I do this but I get an error
Too many arguments for public constructor
val module = module {
single { params -> SnapshotViewModel(params.get<String>(), params.get<String>())}}
And here I try to inject them in my ViewModel
private val displayName: String by inject()
private val securityName: String by inject()
No need to call by inject() in your property definitions. Either you pass the values via constructor or via injection
Option 1:
Create a constructor for your view model
class SnapshotViewModel(displayName: String, securityName: String)
Then define property instance in the module
val module = module {
single { params ->
SnapshotViewModel(params.get<String>(), params.get<String>())
}
}
or
val module = module {
single { (displayName: String, securityName: String) ->
SnapshotViewModel(displayName, securityName)
}
}
Option 2:
Add the property definitions as follow, assuming your view model is tagged with KoinComponent
private val displayName: String
get() = getKoin().getProperty("displayName", "defaulValue")
private val securityName: String
get() = getKoin().getProperty("securityName", "defaulValue")
Then set the property values before initializing the view model. Depending where you set the value, you may need to tag the class with KoinComponent in order to have access to getKoin()
getKoin().setProperty("displayName", "Value")
getKoin().setProperty("securityName", "newValue")

Android pass object from App Fragment to module fragment

I have an android app (main) that creates an object -- in a singleton manner and is used throughout the application. The class is called NetworkFrame which is a common module in the project.
The main app has an activity that contains a fragment which exists in another module in the project (viewer). The viewer module is imported into the main app using Gradle. I need to pass the NetworkFrame object to the viewer fragment.
Both the module and the app both have NetworkFrame as a dependency.
The object is too detailed to use serializable or Parcel. I have looked into dependency injection, but I'm not sure if that's the correct use of it.
class MainApplication : Application() {
companion object {
var networkFrame: NetworkFrame? = null
}
}
class MainFragment : Fragment() {
...
if (MainApplication.networkFrame == null) {
MainApplication.networkFrame = networkFrame
}
viewModel.networkFrame = MainApplication.networkFrame
...
}
Above shows the creation of the object in the main application. I need to pass this object to the viewer fragment's networkFrame object.
class ViewerModel : ViewModel() {
var networkFrame: NetworkFrame? = null
}
What's the best way to get this object to the viewer fragment?
I think that you require an instance of NetworkFrame then I recommend to you create an interface that provides to you the instance
interface HasNetworkFrame {
fun getNetworkFrame(): NetworkFrame
}
This interface should be implemented on the application, something like this
class OneApplication : Application(), HasNetworkFrame{
fun getNetworkFrame(): NetworkFrame {
// return your object
}
}
And in your fragment can access via something like this
val frame = (requireContext().applicationContext as? HasNetworkFrame)?.getNetworkFrame()
However i think that this is not a good approach, take a look into solid principles and put focus on
Dependency Inversion Principle

Koin: Passing Properties between different module definitions

Was testing the following code. For brevity:
Class ActivityA {
Val aViewModel: AViewModel by viewModel()
Fun onCreate(){
val id = ….
getKoin().setProperty(“id’”, id)
loadKoinModules(aModule)
}
}
And in my modules.kt definitions:
Val aModule = modules {
viewModel { AViewModel(getProperty(“id”))} //works
}
//For ActivityB
Val bModule = modules {
viewModel { BViewModel(getProperty(“id”)} // Caused by: org.koin.core.error.MissingPropertyException: Property 'Id' not found
}
Why do I get this error when trying to create the BViewModel instance. I would have thought that getKoin() would be the same Koin Instance retrieved. But it seems to work only within the same Module definition loaded. I can’t get the property ‘id’ for module B. I have to do the same in ActivityA’s onCreate() for ActivityB.
Any explanation or links regarding this would be appreciated or do I have to use something like Koins' Scope feature for this?
Many Thanks

Dependency Injection on Arrow KT

In Arrow Kt Documentation on Dependency Injection, the dependency is defined at the "Edge of the World" or in Android could be an Activity or a Fragment. So the given example is as follow:
import Api.*
class SettingsActivity: Activity {
val deps = FetcherDependencies(Either.monadError(), ActivityApiService(this))
override fun onResume() {
val id = deps.createId("1234")
user.text =
id.fix().map { it.toString() }.getOrElse { "" }
friends.text =
deps.getUserFriends(id).fix().getOrElse { emptyList() }.joinToString()
}
}
But now I'm thinking how could the SettingsActivity in the example could be unit tested? Since the dependency is created within the activity, it could no longer be changed for testing?
When using some other Dependency Injection library, this dependency definition is create outside of the class it will be used on. For example in Dagger, a Module class is created to define how the objects (dependencies) are created and an #Inject is used to "inject" the dependency defined inside the module. So now when unit testing the Activity, I just have to define a different module or manually set the value of the dependency to a mock object.
In Dagger you would create a Mock or Test class that you would #Inject instead of ActivityApiService. It is the same here.
Instead of:
class ActivityApiService(val ctx: Context) {
fun createId(): String = doOtherThing(ctx)
}
You do
interface ActivityApiService {
fun createId(): String
}
and now you have 2 implementations, one for prod
class ActivityApiServiceImpl(val ctx: Context): ActivityApiService {
override fun createId(): Unit = doOtherThing(ctx)
}
and another for testing
fun testBla() {
val api = object: ActivityApiService {
override fun createId(): String = "4321"
}
val deps = FetcherDependencies(Either.monadError(), api)
deps.createId("1234") shouldBe "4321"
}
or even use Mockito or a similar tool to create an ActivityApiService.
I have a couple of articles on how to decouple and unitest outside the Android framework that aren't Arrow-related. Check 'Headless development in Fully Reactive Apps' and the related project https://github.com/pakoito/FunctionalAndroidReference.
If your dependency graph becomes too entangled and you'd like some compile-time magic to create those dependencies, you can always create a local class in tests and #Inject the constructor there. The point is to decouple from things that aren't unitestable, like the whole Android framework :D

org.kodein.di.Kodein$NotFoundException: 2 bindings found that match bind()

I have an interface WordsDataSource using which I have implemented two concrete classes namely WordsLocalDataSource that deals with local database and another WordsRemoteDataSource that deals with manipulating data online on the server. The problem is when I try to inject the two classes in repository class using abstract class name WordsDataSource like
DefaultWordsRepository(
private val wordsRemoteDataSource: WordsDataSource,
private val wordsLocalDataSource: WordsDataSource) {
And adding dependencies in Application class like
class WordsApplication : Application(), KodeinAware {
override val kodein = Kodein.lazy {
import(androidXModule(this#WordsApplication))
bind() from singleton { WordsDatabase.getInstance(instance()) }
bind<WordsDao>() with singleton { instance<WordsDatabase>().wordsDao() }
bind() from singleton { WordsLocalDataSource(instance()) }
bind() from singleton { WordsRemoteDataSource() }
bind<WordsRepository>() with singleton { DefaultWordsRepository(instance(), instance()) }
bind() from provider { ViewModelFactory(instance()) }
}
Then upon running the app I encounter the following issue in the logcat
org.kodein.di.Kodein$NotFoundException: 2 bindings found that match bind<WordsDataSource>() with ?<WordsFragment>().? { ? }:
bind<WordsLocalDataSource>() with singleton { WordsLocalDataSource }
bind<WordsRemoteDataSource>() with singleton { WordsRemoteDataSource }
I have tried the workaround for this by simply declaring the variables by their respective concrete class names like
DefaultWordsRepository(
private val wordsRemoteDataSource: WordsRemoteDataSource,
private val wordsLocalDataSource: WordsLocalDataSource) {
But still want to know whether or not is there any way to resolve the issue.
I am using the following dependencies for kodein
implementation "org.kodein.di:kodein-di-generic-jvm:6.3.3"
implementation "org.kodein.di:kodein-di-framework-android-x:6.3.3"
You have done it the right way by writing the explicit types:
DefaultWordsRepository(
private val wordsRemoteDataSource: WordsRemoteDataSource,
private val wordsLocalDataSource: WordsLocalDataSource)
When working with sub-types we cannot know what kind of implementation to choose. Writing
DefaultWordsRepository(
private val wordsRemoteDataSource: WordsDataSource,
private val wordsLocalDataSource: WordsDataSource)
Doesn't cannot find if you want both sub-types or twice the WordsRemoteDataSource or WordsLocalDataSource. Thus, you need to explicit define your types. Even, we could put WordsRemoteDataSource in the property wordsLocalDataSource, as we cannot rely on variable names.

Categories

Resources