I'm new at Koin. I have set all the stuff and is working. But I'm getting some problems when I'm trying to inject interactor and presenter at the same time. That not sure it is possible.
This is my Module
val applicationModule = module(override = true) {
factory{VoucherImpl(get())}
factory<VoucherContract.Presenter> { (view: VoucherContract.View) -> VoucherPresenter(view, get()) }
}
This is my Activity where inject the presenter
private val presenter: VoucherContract.Presenter by inject { parametersOf(this)}
This is my Presenter
class VoucherPresenter (private var view: VoucherContract.View?, private var mCodeRechargeInteract : VoucherImpl) : VoucherContract.Presenter, VoucherContract.Callback, KoinComponent {
override fun create() {
view?.initView()
view?.showProgress()
mCodeRechargeInteract.run()
}
.
.
.
Interactor class
class VoucherImpl(private var mCallback: VoucherContract.Callback?) : AbstractInteractor() {
.
.
.
contract
interface VoucherContract {
interface Presenter {
fun create()
fun destroy()
fun checkIfShoppingCartHaveItems()
fun addVoucherToShoppingCart(voucherProduct: Product)
fun onItemClick(product: Product)
}
interface Callback {
fun onResponseVouchers(vouchers: List<Product>?)
fun onError()
}
}
With this code I get
No definition found for 'xxx.voucher.VoucherContract$Callback' has been found. Check your module definitions.
Then, I try to put it in the module and I can't do it because I get: a Type mismatch. Required VoucherContract.Callback Found VoucherImpl
factory<VoucherContract.Callback> { (callBack: VoucherContract.Callback) -> VoucherImpl(callBack) }
You have a circular dependency that's why this doesn't work.
VoucherImpl(VoucherContract.Callback) and VoucherPresenter(View, VoucherImpl):VoucherContract.Callback
There are multiple ways out of this predicament.
I would recommend the following changes:
The VoucherImpl should not have the constructor parameter VoucherContract.Callback. This callback should be the parameter of a method something like this:
class VoucherImpl : AbstractInteractor(){
fun listen(VoucherContract.Callback){...}
}
This way the dependency becomes one way and you can inject them.
Related
Is there a way to inject an object inside an extension function or a global function using DI framework in Android Kotlin?
I use this function in many places. So I do not want to pass a parameter every time.
DI framework can be any of Koin, Hilt, Dagger2, or others.
Something like that:
fun Context.showSomething() {
val myObject = inject()
showToast(myObject.text)
}
Instead of thinking about using Inject you could pass it as a parameter:
fun Context.showSomething(myObject: String) {
showToast(myObject.text)
}
With Koin you can do like this,
fun Context.showSomething() {
val myObject = GlobalContext.get().get()
showToast(myObject.text)
}
but it's totally not recommended way of using it.
I use my app component to inject into extension methods. For example, to use MyInjectableClass in an extension method:
// Your app component
#Component
interface ApplicationComponent {
fun myInjectable(): MyInjectableClass
}
// Your app class
class MyApplication: Application() {
companion object {
var appComponent: ApplicationComponent? = null
}
override fun onCreate() {
appComponent = DaggerAppComponent.create()
}
}
// Ext.kt file
private val injectable: MyInjectableClass?
get() = MyApplication.appComponent?.myInjectable()
fun Foo.extension() {
injectable?.bar()
// huzzah!
}
Of course you still need to provide the #Provides or #Binds method for MyInjectableClass.
Is there any alternative to javax.inject.Provider in koin?
To react to actions, I am injecting Commands to my activity.
Command is a single-run object, for example WriteToFile.
In dagger I could make it like this:
class MainPresenter : Presenter() {
#Inject
lateinit var writeFile: Provider<WriteFileCommand>
fun onSaveClicked() {
writeFile.get().run()
}
}
in koin, when I try to use:
class MainPresenter : Presenter() {
lateinit var writeFile: Provider<WriteFileCommand> by inject()
fun onSaveClicked() {
writeFile.get().run()
}
}
My koin module:
val appModule = module {
factory { WriteFileCommand(get(), get()) }
factory { FileProvider() }
single { DataStore() }
}
Than I got error saying:
Can't create definition for 'Factory [name='WriteFileCommand',class='com.test.WriteFileCommand']' due to error :
No compatible definition found. Check your module definition
I understand that I can call:
var command: WriteFileCommand = StandAloneContext.getKoin().koinContext.get()
command.run()
But It looks so cumbersome
There's nothing like a provider directly. If you use inject, you'll use a lazy delegate. If you use get, you'll create a new instance you declared the dependency with a factory. So get is what you need in your case. Just let your MainPresenter implement KoinComponent and you'll be able to use get directly:
class MainPresenter : Presenter(), KoinCompontent {
fun onSaveClicked() = get<WriteFileCommand>().run()
}
I make project with mvvm pattern with koin for DI, but i always have No definition found repository
I alredy define repository in module app before viewmodel, but i get some error
Gradle app
// Koin for Android
implementation "org.koin:koin-android:$rootProject.koin_version"
// Koin Android Scope features
implementation "org.koin:koin-androidx-scope:$rootProject.koin_version"
// Koin Android ViewModel features
implementation "org.koin:koin-androidx-viewmodel:$rootProject.koin_version"
module
val dataModule = module {
//remoteData
single { AppRemoteData() }
//repository
single{ AppRepository(get()) as AppDataSource}
// viewmodel
viewModel{ ListHomeViewModel(get()) }
viewModel { LoginViewModel(get()) }
define module
val myAppModule = listOf(appModule, dataModule)
in app
startKoin {
androidLogger(Level.DEBUG)
androidContext(this#MainApp)
modules(myAppModule)
}
Repository class
class AppRepository(val appRemoteDataSource: AppRemoteData) : AppDataSource {
override fun loginAccount(email: String, password: String) : LiveData<String> {
val data = MutableLiveData<String>()
appRemoteDataSource.loginAccount(email,password,object : AppDataSource.LoginCallback{
override fun onSucces(id: String?) {
//berhasil
data.postValue(id)
}
override fun onFailed(message: String?) {
data.postValue(message)
d(message)
}
override fun onFailure(message: String?) {
data.postValue(message)
d(message)
}
})
return data
}
AppRemoteData
class AppRemoteData {
val ref = FirebaseDatabase.getInstance().getReference("user")
var auth = FirebaseAuth.getInstance()
fun loginAccount(email: String, password: String, callback: AppDataSource.LoginCallback) {
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener {
task ->
if(task.isComplete){
callback.onSucces(task.result?.user?.providerId)
}else{
callback.onFailed(task.exception?.localizedMessage)
}
}
}}
here error message
if in your ViewModel has an interface as parameter.
class MyViewModel(val interfaceName: InterfaceName) : ViewModel()
In your module definition use singleBy<> Instead of Single().
singleBy<InterfaceName,InterfaceNameImplementation>()
Finally in for your ViewModel
viewModel { MyViewModel(get()) }
This worked for me in Koin 2.0
Hope it helps :)
The error message is telling you that Koin couldn't create a LoginViewModel instance for you, because it would've had to provide an instance of AppRepository during its creation, but you didn't tell it how to do that.
My guess is that you've accidentally used the AppRepository type in the LoginViewModel constructor directly, instead of using your AppDataSource that you've bound the repository instance to in your module.
So if you have something like this, that would require an AppRepository specifically:
class LoginViewModel(val dataSource: AppRepository) : ViewModel()
You should replace it with this, where you're only asking Koin for an AppDataSource, which you did configure it to be able to provide:
class LoginViewModel(val dataSource: AppDataSource) : ViewModel()
I heve base class and I would like to use Koin injection on this base class like:
abstract class BasePresenterFragment<T : BasePresenter> : BaseFragment() {
lateinit var presenter: T by inject<T>() // here is problem
override fun onStart() {
super.onStart()
presenter.subscribe()
}
override fun onStop() {
super.onStop()
presenter.unSubscribe()
}
}
I know there are solutions for inject viewModel but not for simple injection. So is there any way to use Koin injection with generic type?
Well, I've found only partly solution for this question. It's use presenter like abstract val in base class. This will make it possible to use the methods of presenter in the base class but I still should use inject() in every subclasses for initialization. Example:
abstract class BasePresenterFragment<P : BasePresenter> : BaseFragment() {
abstract val presenter: P
override fun onStart() {
super.onStart()
presenter.subscribe()
}
override fun onStop() {
super.onStop()
presenter.unSubscribe()
}
}
And subclass:
class HomeFragment : BasePresenterFragment<HomeContract.Presenter>(), HomeContract.View {
...
override val presenter: HomeContract.Presenter by inject()
...
}
Koin does not support generics by default.
"Koin definitions doesn't take in accounts generics type argument."
however you are supposed to you the named argument to workaround this:
The latest Version even supports directly passing the type insted of a custom string:
module {
single(named<Int>) { ArrayList<Int>() }
single(named<String>) { ArrayList<String>() }
}
and when injecting, simply use get(named<Int>) or get(named<String>) depending on your need. For more information cf.: https://insert-koin.io/docs/reference/koin-core/definitions/
I'm building the architecture of a new Android application using Kotlin and Android Architecture Components (ViewModel, LiveData) and I'm also using Koin as my dependency injection provider.
The problem is that I'm not been able to initialize the ViewModel in a generic way inside my BaseActivity via koin injection. The current code looks like this:
abstract class BaseActivity<ViewModelType : ViewModel> : AppCompatActivity() {
// This does not compile because of the generic type
private val viewModel by lazy {
// Koin implementation to inject ViewModel
getViewModel<ViewModelType>()
}
#CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Fabric.with(this, Crashlytics())
}
/**
* Method needed for Calligraphy library configuration
*/
#CallSuper
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase))
}
}
I'd like to know if is there a way to do this in Kotlin because I'm pretty sure I would be able to do in Java easily.
Thanks.
The solution was provided by the koin team in version 0.9.0-alpha-11 and the final code looks like this:
open class BaseActivity<out ViewModelType : BaseViewModel>(clazz: KClass<ViewModelType>) :
AppCompatActivity() {
val viewModel: ViewModelType by viewModel(clazz)
fun snackbar(message: String?) {
message?.let { longSnackbar(find(android.R.id.content), it) }
}
fun toast(message: String?) {
message?.let { longToast(message) }
}
}
Here is example of not passing Class and Generic to base implementation
In your base fragment/activity:
abstract class BaseFragment<T : BaseViewModel> : Fragment() {
...
#Suppress("UNCHECKED_CAST")
private val clazz: KClass<T> = ((this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<T>).kotlin
protected val viewModel: T by viewModel(clazz = clazz)
...
}
It looks ugly, but it works.
you can use a delegate version declaration for your ViewModel and avoid using directly a lazy expression. Try with this:
abstract class BaseActivity<T : ViewModel> : AppCompatActivity() {
val model by viewModel<T>()
}
This will give you a lazy of
getViewModel<T>()
Throw an eye on the quick ref: https://insert-koin.io/docs/1.0/getting-started/android-viewmodel/
Hope it will help.