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()
Related
WHY NOT HELP ME ANY PEOPLE???
In my application i used Kotin and Kodein for Dependency injection.
I write below codes, but when run application show me Force close error!
In my application i used Kotin and Kodein for Dependency injection.
I write below codes, but when run application show me Force close error!
Application class codes:
class QuoteDiApp : Application(), KodeinAware {
override val kodein = Kodein.lazy {
bind<Database>() with singleton { DatabaseFakeImpl() }
bind<QuoteDao>() with singleton { instance<Database>().quoteDao }
bind<QuoteRepository>() with singleton { QuoteRepositoryImpl(instance()) }
bind() from provider { QuotesViewModelFactory(instance()) }
}
}
Activity class codes:
class QuoteDiActivity : AppCompatActivity(), KodeinAware {
override val kodein by closestKodein()
private val viewModelFactory: QuoteViewModelFactory by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_quote_di)
initializeUi()
}
private fun initializeUi() {
// Use ViewModelProviders class to create / get already created QuotesViewModel
// for this view (activity)
val viewModel = ViewModelProviders.of(this, viewModelFactory).get(QuoteViewModel::class.java)
// Observing LiveData from the QuotesViewModel which in turn observes
// LiveData from the repository, which observes LiveData from the DAO ☺
viewModel.getQuotes().observe(this, Observer { quotes ->
val stringBuilder = StringBuilder()
quotes.forEach { quote ->
stringBuilder.append("\n$quote")
}
textView_quotes.text = stringBuilder.toString()
})
// When button is clicked, instantiate a Quote and add it to DB through the ViewModel
button_add_quote.setOnClickListener {
val quote = Quote(editText_quote.text.toString(), editText_author.text.toString())
viewModel.addQuote(quote)
editText_quote.setText("")
editText_author.setText("")
}
}
}
I initialize application class into Manifest
Error message :
Caused by: org.kodein.di.Kodein$NotFoundException: No binding found for bind<QuoteViewModelFactory>() with ? { ? }
Registered in this Kodein container:
bind<QuoteDao>() with singleton { QuoteDao }
bind<QuotesViewModelFactory>() with provider { QuotesViewModelFactory }
bind<Database>() with singleton { DatabaseFakeImpl }
bind<QuoteRepository>() with singleton { QuoteRepositoryImpl }
at org.kodein.di.internal.KodeinContainerImpl.factory(KodeinContainerImpl.kt:174)
at org.kodein.di.KodeinContainer$DefaultImpls.factory$default(KodeinContainer.kt:33)
at org.kodein.di.KodeinContainer$DefaultImpls.provider(KodeinContainer.kt:80)
at org.kodein.di.internal.KodeinContainerImpl.provider(KodeinContainerImpl.kt:7)
at org.kodein.di.KodeinContainer$DefaultImpls.provider$default(KodeinContainer.kt:79)
at org.kodein.di.KodeinAwareKt$Instance$1.invoke(KodeinAware.kt:152)
at org.kodein.di.KodeinProperty$provideDelegate$1.invoke(properties.kt:39)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at nouri.mohammad.mvvm_kodeindi.with_di.ui.quotes.QuoteDiActivity.getViewModelFactory(QuoteDiActivity.kt)
at nouri.mohammad.mvvm_kodeindi.with_di.ui.quotes.QuoteDiActivity.initializeUi(QuoteDiActivity.kt:30)
at nouri.mohammad.mvvm_kodeindi.with_di.ui.quotes.QuoteDiActivity.onCreate(QuoteDiActivity.kt:24)
at android.app.Activity.performCreate(Activity.java:5231)
In your application class you use QuotesViewModelFactory class, but into you activity class you use QuoteViewModelFactory class.
This two classes is not match!
Please check your classes and match this two classes !
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.
I'm pretty new in Android development and currently, I'm testing a basic activity with Roboelectric and Koin.
Code:
class SplashActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
Stetho.initializeWithDefaults(this)
val user = viewModel.getPersistedUser()
if (user != null) {
viewModel.setUser(user)
startActivity(HomeActivity.getStartIntent(this))
} else {
startActivity(LoginActivity.getStartIntent(this))
}
}
}
val appModule = module(override = true) {
...
viewModel<LoginViewModel>()
}
Now all I want to do in the test is to inject a mocked version of the viewModel to simulate the response of the method getPersistedUser.
How can I do that with Roboelectric and Koin?
First, if you want to write UI test for SplashActivity. Better to use Expresso testing framework (https://developer.android.com/training/testing/espresso)
Second, if you want to mock your viewmodel with Koin in your test, you can load your Koin modules then declare your viewmodel mock, code will be similar like this
class SplashActivityTest : AutoCloseKoinTest() {
private val viewModel: LoginViewModel by inject()
#Before
fun before() {
koinApplication {
loadModules(appModule)
}
declareMock<LoginViewModel> {
given(getPersistedUser()).willReturn { User(username, password) }
}
}
#Test
fun loadCurrentUserWhenActivityInit() {
// verify your test here
}
}
More details here https://start.insert-koin.io/#/getting-started/testing
I have an app based on MVVM architecture.
I have two modules that repository and datasource.
And using coroutines. I came across some projects on Github they applied different ways.
My implementation like this;
You can think naming as login, profile etc. instead of X letter
datasource interface
interface IXDatasource{
suspend fun fetchData(): LiveData<XResponse>
}
datasource implementation
class XDatasourceImp #Inject constructor(private val apiService: ApiService) : IXDatasource{
suspend fun fetchData(): LiveData<XResponse> {
// getting data
return xResponse
}
}
repository interface
interface XRepository {
suspend fun getXData(): LiveData<XResponse>
}
repository implementation
class XRepositoryImp #Inject constructor(private val iDatasource: IXDatasource): XRepository {
override suspend fun getXData(): LiveData<XResponse> {
return withContext(Dispatchers.IO) {
iDatasource.fetchData()
}
}
}
I called this in my ViewModel
class XViewModel #Inject constructor(xRepository: XRepository) : BaseViewModel() {
val xModel by lazyDeferred {
xRepository.getXData()
}
}
And I use in my activity/fragment
private fun init() = launch {
val xModel = viewModel.xModel.await()
xModel.observe(this#MyActivity, Observer {
if (it == null) {
return#Observer
}
// filling views or doing sth
})
}
It works but I wonder all of them is required or not? I can apply the room database to my datasource. Is there any way better than this? I know it can change by the case of the app. I try to find the best way. I will be happy if you suggest anything or share anything about repository pattern implementation.
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.