Adding Multiple Adapter Into Fragment - dagger

Hello all can you advice me how to add multiple different adapter in one fragment using dagger ?
#Module
class HomeFragmentModule {
#Provides
internal fun provideHomeInteractor(interactor: HomeInteractor): HomeMVPInteractor = interactor
#Provides
internal fun provideRegisterPresenter(presenter: HomePresenter<HomeView, HomeMVPInteractor>) : HomeMVPPresenter<HomeView, HomeMVPInteractor> = presenter
#Provides
#Named("ppobAdapter")
fun providePpobAdapter(): PpobAdapter = PpobAdapter(ArrayList())
#Provides
#Named("sliderAdapter")
internal fun provideSliderAdapter(): SliderAdapter = SliderAdapter(ArrayList())
} ```

Try this:
HomeFragment : Fragment() {
#Inject
#Named("ppobAdapter")
lateinit var ppobAdapter: PpobAdapter
#Inject
#Name("sliderAdapter")
lateinit var sliderAdapter: SliderAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
DaggerAppComponent
.builder()
.build()
.inject(this)
}
}
With a component that looks like this:
#Component(modules = [HomeFragmentModule::class])
interface AppComponent {
fun inject(fragment: HomeFragment)
}
For a really good intro to dagger I always advise this tutorial series

Related

Dagger 2 - How to establish sibling dependency via Subcomponent or Component Dependency

Background
As per docs dependency between sibling subcomponents is not possible so, the other way left is Component Dependency. I'm trying to encapsulate "Repository" class so that UI can only access the instance of "Repository", not implementation details of "Repository". I mean nothing from ApiModule should be exposed.
ApiModule
#Module
class ApiModule {
#Provides
fun provideConnection(): Connection = //....
}
RepositoryModule
#Module (
includes = [
ApiModule::class
]
)
abstract class RepositoryModule {
#Provides
fun providesRepository(connection Connection): Repository = //.....
}
AppComponent
#Component (modules = [
AppModule::class,
RepositoryModule::class
])
interface AppComponent{
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
Problem
Now, if I create a subcomponent (let's say ActivitySubComponent) of AppComponent, all binding in RepositoryModule and ApiModule modules will be exposed to that subcomponent. Though, I want only provided "Repository" to be accessible from ActivitySubComponent
Proposed Solution
Create RepositoryComponenet and ActivityComponent components make AppComponenet their dependency:
AppComponent
#Component (modules = [
AppModule::class
])
interface AppComponent{
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
ActivityComponent
#Component(dependencies = [AppComponent::class], modules = [ActivityModule::class])
interface ActivityComponent {
fun inject(mainActivity: MainActivity)
#Component.Builder
interface Builder{
fun appComponent(appComponent: AppComponent): Builder
fun build(): ActivityComponent
}
}
RepositoryComponent
#Component(dependencies = [AppComponent::class], modules = [RepositoryModule::class])
interface RepositoryComponent {
}
The problem is how do I expose "Repository" instance into sibling ActivityComponent's bindings or #Inject into MainActivity? Any explanation, proposal to change my configuration or links to articles that may potentially solve my problem is appreciated.
If I understand your problem correctly, you don't want to expose all dependencies from AppComponent to its child components. As component documentation says (section Component dependencies),
Note that only the bindings exposed as provision methods are available through component dependencies.
you should use Component dependencies for this purpose.
Working example is below
class Connection
interface Repository
#Module
object AppModule {
#Provides
#Singleton
fun provideConnection() = Connection()
}
#Module(includes = [AppModule::class])
object RepositoryModule {
#Provides
#Singleton
fun provideRepository(connection: Connection) = object : Repository {}
}
#Singleton
#Component(modules = [AppModule::class, RepositoryModule::class])
interface AppComponent {
val repository: Repository
}
#Scope
#MustBeDocumented
#kotlin.annotation.Retention
annotation class ActivityScope
#ActivityScope
#Component(dependencies = [AppComponent::class])
interface ActivityComponent {
fun inject(activity: FakeActivity)
}
class FakeActivity {
#Inject
lateinit var repository: Repository
// uncomment this and code won't compile because connection isn't explicitly exposed in AppComponent
// #Inject
// lateinit var connection: Connection
}
fun main() {
val appComponent = DaggerAppComponent.create()
val activityComponent = DaggerActivityComponent.builder().appComponent(appComponent).build()
val fakeActivity = FakeActivity()
activityComponent.inject(fakeActivity)
println("hash: ${fakeActivity.repository}")
println("hash: ${appComponent.repository}")
}
I used scopes because I suppose you want just one instance of Repository per Application. If something is not clear, let me know.

Reactive repository does not save an object

I think I don't understand well how a Reactive repository and handlers using it work. I have written a special test class only to test the simpliest handler using a repository
#SpringBootTest
class TestRepository() {
#Autowired
lateinit var myRepo: myRepo
#Autowired
lateinit var myHandler: MyHandler
#Test
fun `save with a handler`() {
val myObject = MyObject()
myHandler.save(request).subscribe()
StepVerifier.create (myRepository.count() ) <--this does not work
.expectNext (1L )
.expectComplete().verify()
}
#Test
fun `test only database saving`() {
val object = MyObject()
myRepo.save(myRepo).subscribe()
StepVerifier.create (myRepo.count() ) <-- this works
.expectNext (1L )
.expectComplete().verify()
}
}
my handler and repository are defined in the following way:
#Service
class MyHandler(private val myRepository: MyRepository) {
fun save(object: MyObject): Mono<MyObject> {
return myRepository.save(request)
}
}
#Repository
interface MyRepo : ReactiveMongoRepository<MyObject, String> {
fun save(request: MyObject): Mono<MyObject>
}
I also tried to play with subscribe method but it still does not see the results.
What should I correct?
Use Mono.then function to chain save and count functions and get a resulting Mono:
#Test
fun `save with a handler`() {
val countAfterSave = myHandler.save(MyObject()).then(myRepository.count());
StepVerifier.create(countAfterSave)
.expectNext(1L)
.expectComplete()
.verify()
}

Dagger can not inject ViewModel with KClass

I am moving my project java to kotlin, but got some confusion about KClass and Class
Here is my BaseActivity
abstract class BaseActivity<DB : ViewDataBinding, VM : BaseViewModel> : DaggerAppCompatActivity() {
private lateinit var mCustomDialog: CustomDialog
private lateinit var mViewDataBinding: DB
private lateinit var mViewModel : VM
#Inject
lateinit var viewModelFactory: ViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set Custom Dialog
mCustomDialog = CustomDialog(this, R.style.LoadingDialogStyle)
// Set ViewModel
mViewModel = ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass().java)
// Set DataBinding
mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId())
mViewDataBinding.lifecycleOwner = this
mViewDataBinding.setVariable(getBindingVariable(), mViewModel)
mViewDataBinding.executePendingBindings()
// Initialize UI
prepareView(savedInstanceState)
}
#LayoutRes
abstract fun getLayoutId(): Int
protected abstract fun getViewModelClass(): KClass<VM>
abstract fun getBindingVariable(): Int
fun getViewModel(): VM {
return mViewModel
}
fun getViewDataBinding() : DB {
return mViewDataBinding
}
I am using protected abstract fun getViewModelClass(): KClass<VM> function for initializing ViewModel class in the function below
ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass().java)
I use ViewModel in activities by this way
class SplashActivity : BaseActivity<ActivitySplashBinding, SplashViewModel>() {
override fun getViewModelClass(): KClass<SplashViewModel> {
return SplashViewModel::class
}
override fun getLayoutId(): Int {
return R.layout.activity_splash
}
override fun getBindingVariable(): Int {
return BR.vm
}
override fun prepareView(savedInstanceState: Bundle?) {
getViewModel().testLog()
}
}
But when I run the project, I got this error
error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.example.example.MyApp> {
^
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.example.example.utils.ViewModelFactory(viewModels)
com.example.example.utils.ViewModelFactory is injected at
com.example.example.base.BaseActivity.viewModelFactory
com.example.example.ui.splash.SplashActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.example.example.di.AppComponent ? com.example.example.di.ActivityBindingsModule_SplashActivityInjector$app_debug.SplashActivitySubcomponent]
So I made some research and find out it is about KClass in my ViewModelKey
Here is ViewModelKey
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
If I do not change my code to Kotlin and use old Java class like this it works properly
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#MapKey
public #interface ViewModelKey {
Class<? extends ViewModel> value();
}
This is my ViewModelFactory class
#Suppress("UNCHECKED_CAST")
class ViewModelFactory #Inject
constructor(private val viewModels: MutableMap<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = viewModels[modelClass]
?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("unknown model class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
My SplashActivityModule
#Module
abstract class SplashActivityModule {
#Binds
#IntoMap
#ViewModelKey(SplashViewModel::class)
internal abstract fun provideSplashViewModel(splashViewModel: SplashViewModel) : ViewModel
}
So how can I use ViewModelKey properly with Kotlin and what is main cause of this error, any help will be appreciated
Your ViewModelKey be like
#MustBeDocumented
#kotlin.annotation.Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
As mentioned this question problem is related to Kotlin version. Using higher than 1.3.30 version solves the problem.

MVVM without DataBinding

I started learning Kotlin to develop Android applications and for my first project I have to use MVVM as pattern to separate the presentation layer from the logic. All the articles I've found about MVVM use DataBinding library to bind the data of the models directly to the xml views. I worked a little with DataBinding in Java but I doesn't like it because it is very difficult to find errors when binding is wrong. My questions is if there is another way to use MVVM without DataBinding?
Sure it is. Just abstract example: imagine ViewModel:
class SomeViewModel : ViewModel() {
private lateinit var roadmap: RoleScreenRoadmap
private lateinit var uiScope: CoroutineScope
private val _nameRequired = MutableLiveData<Boolean>()
private val _userName = MutableLiveData<String>()
val nameRequired: LiveData<Boolean>
get() = _nameRequired
val userName: LiveData<String>
get() = _userName
...
}
And now Activity:
class AbstractActivity : AppCompatActivity() {
private lateinit var viewModel: RoleScreenViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_some)
initializeDependencies()
performBindings()
}
private fun initializeDependencies() {
viewModel = ViewModelProviders.of(this).get(AbstractViewModel::class.java)
}
private fun performBindings() {
val lifecycle = ::getLifecycle
viewModel.nameRequired.observe(lifecycle) { nameRequired: Boolean? ->
if (nameRequired!!) {
showNameInputWindow()
}
}
viewModel.userName.observe(lifecycle, ::setTitle)
}
...
}
You can just subscribe during android's lifecycle component initialization and no databinding is ever needed.

Dagger2 Circular Dependency Error

I am using dagger2 2.16 version for dependency injection inside mine android project. I examine a lot of examples, and although I do not have a similar approach I get the error of "circular dependency".
Mine source code;
AppComponent.kt
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityBuilderModule::class]
)
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: App)
}
App.kt
class App : Application(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
AppInjector.init(this)
initOneSignal()
}
private fun initOneSignal() = OneSignal.startInit(this).setNotificationOpenedHandler(CustomNotificationOpenedHandler()).inFocusDisplaying(OneSignal.OSInFocusDisplayOption.Notification).init()
override fun activityInjector() = dispatchingAndroidInjector
}
ActivityBuilderModule.kt
#Module
abstract class ActivityBuilderModule {
#ContributesAndroidInjector
abstract fun contributeSplashActivity(): SplashActivity
}
AppModule.kt
#Module(includes = [(ViewModelModule::class)])
class AppModule {
#Singleton
#Provides
fun provideContext(app: Application): Context = app.applicationContext;
#Singleton
#Provides
fun provideApiService(client: OkHttpClient): ApiService {
return Retrofit.Builder()
.baseUrl(Constants.baseUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(ApiService::class.java)
}
#Singleton
#Provides
fun provideOkHttpClient(interceptor: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder().addInterceptor(interceptor).build()
}
#Singleton
#Provides
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
return interceptor
}
}
If I remove the ActivityBuilderModule from the AppComponent, the project is compiled without problems. But if you add to the modules section, the project gives the error below.
error: [ComponentProcessor:MiscError]
dagger.internal.codegen.ComponentProcessor was unable to process this
interface because not all of its dependencies could be resolved. Check
for compilation errors or a circular dependency with generated code.
Please help me.
In App.kt you need to initialize component with the application context. the line
AppInjector.init(this)
should be inside the Activity i.e. splashActivity in which you're going to inject the dependencies.
The above mentioned error message might also appear, because kotlin stdlib is not declared as dependency. So adding e.g. implementation("org.jetbrains.kotlin:kotlin-stdlib:1.3.60") in your build.gradle(.kts) file might also help.