Dagger 2.11 - how to make Activity and Fragment share common values? - android

I am currently learning Dagger 2 on Android and I would like to inject the Activity's injection into the Fragment, but I do not know how.
This a working setup, that shows the values being injected into the fragment and the activity.
Sorry for the wall of code, I simplified my code so that it only contains the relevant parts.
#Singleton
#Component(modules = arrayOf(AndroidSupportInjectionModule::class, AppModule::class, BuildersModule::class))
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun setApplication(application: Application): Builder
fun build(): AppComponent
}
fun apiService() : ApiService
fun inject(app: App)
}
#Module(subcomponents = arrayOf(MainActivitySubComponent::class, MainFragmentSubComponent::class))
class AppModule
#Module
abstract class BuildersModule {
#Binds
#IntoMap
#ActivityKey(MainActivity::class)
abstract fun bindMainActivity(builder: MainActivitySubComponent.Builder): AndroidInjector.Factory<out Activity>
#Binds
#IntoMap
#dagger.android.support.FragmentKey(MainFragment::class)
abstract fun bindMainFragment(builder: MainFragmentSubComponent.Builder): AndroidInjector.Factory<out Fragment>
}
The AppComponent is created inside the Application:
class App : Application(), HasActivityInjector {
#Inject
var activityInjector: DispatchingAndroidInjector<Activity>? = null
override fun onCreate() {
super.onCreate()
DaggerAppComponent
.builder()
.setApplication(this)
.build()
.inject(this)
}
override fun activityInjector(): AndroidInjector<Activity>? {
return activityInjector
}
}
MainActivity:
#Subcomponent(modules = arrayOf(MainActivityModule::class))
interface MainActivitySubComponent : AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>()
}
#Module
class MainActivityModule {
#Provides
fun providePresenter(mainView: MainView, apiService: ApiService): MainPresenter
= MainPresenterImpl(mainView, apiService)
}
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
#Inject
lateinit var mainPresenter: MainPresenter
#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.add(R.id.container, MainFragment())
.commitAllowingStateLoss()
}
}
override fun supportFragmentInjector() = fragmentInjector
}
MainFragment:
#Subcomponent(modules = arrayOf(MainFragmentModule::class))
interface MainFragmentSubComponent : AndroidInjector<MainFragment> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainFragment>()
}
#Module
class MainFragmentModule {
#Provides
fun providePresenter(mainView: MainView, apiService: ApiService): MainPresenter
= MainPresenterImpl(mainView, apiService)
}
class MainFragment : Fragment() {
#Inject
lateinit var mainPresenter: MainPresenter
override fun onAttach(context: Context?) {
AndroidSupportInjection.inject(this)
super.onAttach(context)
}
}
So I've found out how activity and fragment can use the same ApiService class.
I'd like to inject the same instance of MainPresenter in both MainActivity and MainFragment.
How could I gain access to the Activity's injected MainPresenter without changing the code in MainFragment? Basically, I don't want the MainFragment to know anything about how it is injected.

Related

ViewModel cannot be provided without an #Inject constructor or an #Provides-annotated

Question EDITED
I am injecting ViewModelProvider.Factory to BaseActivity like below
open class BaseActivity : DaggerAppCompatActivity() {
#Inject
lateinit var factories: ViewModelProvider.Factory
inline fun <reified T : ViewModel> getViewModel(): T {
return ViewModelProvider(this, factories).get(T::class.java)
}
}
viewModel only works when we inject then like below.
class MainViewModel #Inject constructor( private val alertStore: AlertStore)
: BaseViewModel(){
fun showDialog(){
viewModelScope.launch {
delay(4000)
alertStore.showToast("Alert after 4 seconds.")
}
}
}
Why this #Inject constructor is necessary in my current implementation
class MainActivity : BaseActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = getViewModel()
viewModel.showDialog()
}
}
App.kt
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().addContext(this).build()
}
}
AppComponent.kt
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ViewModelInjector::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun build(): AppComponent
}
}
AppModule.kt
#Module
class AppModule {
#Provides
fun provideViewModelFactories(viewModels: Map<Class<out ViewModel>,
#JvmSuppressWildcards Provider<ViewModel>>):
ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val factory = viewModels[modelClass]?.get() ?: error(
"No factory provided against ${modelClass.name}"
)
#Suppress("UNCHECKED_CAST")
return factory as T
}
}
}
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
//#Scope("")
#ContributesAndroidInjector ///(modules = {MainModelFactory.class})
public abstract MainActivity bindMainActivity();
}
ViewModelInjector.kt
#Module
public abstract class ViewModelInjector {
#Binds
#IntoMap
#ViewModelKey(MainViewModel.class)
public abstract ViewModel providesMainViewModel(MainViewModel model);
}
ViewModelKey.kt
#MapKey
#Retention(AnnotationRetention.SOURCE)
annotation class ViewModelKey(
val value: KClass<out ViewModel>
)
Why do I have to append #Inject constructor to each ViewModel and kindly explain a little why we need #Binds #IntoMap and with ViewModel
When you use dagger android you should make your activities and fragments as extensions of DaggerActivity (and DaggerFragment for fragments).
class MainActivity : DaggerActivity() {
#Inject
lateinit var viewModel: MainViewModel
}
Next you should prepare infrastructure for injection:
Create injectors for each your activity:
// All your injectors can be defined in this module
#Module(includes = [AndroidInjectionModule::class])
interface AppInjectorModule {
#ContributesAndroidInjector(modules = [MainActivityVmModule::class, /*other dependecies*/])
fun getMainActivityInjector(): MainActivity
}
Create modules to provide view models (can be multiple for one activity) and factory
#Module(includes = [VmFactoryModule::class])
abstract class MainActivityVmModule {
// bind implementation of ViewModel into map for ViewModelFactory
#Binds
#IntoMap
#ClassKey(MainViewModelImpl::class)
abstract fun bindMainVm(impl: MainViewModelImpl): ViewModel
#Module
companion object {
#Provides
#JvmStatic
fun getMainVm(activity: MainActivity, factory: ViewModelProvider.Factory): MainViewModel {
// create MainViewModelImpl in scope of MainActivity and inject dependecies by ViewModelFactory
return ViewModelProviders.of(activity, factory)[MainViewModelImpl::class.java]
}
}
}
Factory can be provided by different module to avoid duplication
#Module
interface VmFactoryModule {
#Binds
// bind your implementation of factory
fun bindVmFactory(impl: ViewModelFactory): ViewModelProvider.Factory
}
Add activities injectors to AppComponent graph
#Component(
modules = [
AppInjectorModule::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App>
Additional info: Dagger & Android

Implementing a simple Dagger2 sample

I'm new using Dagger2 (I always used Koin) and I'm trying to implement a simple sample but I don't really know what I'm missing. This is what I got so far.
app.gradle:
ext.daggerVersion = '2.23.2'
implementation "com.google.dagger:dagger:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
AppModule.kt:
#Module
class AppModule {
#Provides
#Singleton
fun provideApplication(app: App): Application = app
#Provides
#Singleton
fun provideTestOperator(testOperator: TestOperator) = testOperator
#Provides
#Singleton
fun provideTestClass(testClass: TestClass) = testClass
}
AppComponent.kt:
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: App): Builder
fun build(): AppComponent
}
}
TestClass.kt & TestOperator.kt in the same file:
class TestClass #Inject constructor(private val testOperator: TestOperator) {
fun getRandomValueFromCTest(): Int = testOperator.generateRandomNumber()
}
class TestOperator #Inject constructor() {
fun generateRandomNumber(): Int = Random.nextInt()
}
App.kt:
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this#App).build()
}
}
MainActivity.kt:
class MainActivity : AppCompatActivity() {
#Inject
lateinit var testClass: TestClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
val x = testClass.getRandomValueFromCTest()
}
}
Error: testClass == null
AppModule.kt: Provide the application context. No need to write #singleton #provides for your Test* classes (will see why)
#Module
class AppModule {
#Provides
#Singleton
fun provideApplication(app: App): Context = app.applicationContext
}
AppComponent.kt: #Component.Builder is deprecated IIRC. Use #Component.Factory. And replace AndroidInjectionModule::class with AndroidSupportInjectionModule::class since we are using dagger-android-support and android's *Compat* stuff. Refer a new module here called ActivityModule::class.
#Singleton
#Component(modules = [
ActivityModule::class
AndroidSupportInjectionModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<App> {
#Component.Factory
abstract class Factory : AndroidInjector.Factory<App>
}
TestClass.kt & TestOperator.kt: Since you were providing singletons by writing #singleton and #provides method, I assume you want them to be singletons. Just annotate the class definition with #Singleton and dagger will take care of it. No need to write #Provides methods.
#Singleton
class TestClass #Inject constructor(private val testOperator: TestOperator) {
fun getRandomValueFromCTest(): Int = testOperator.generateRandomNumber()
}
#Singleton
class TestOperator #Inject constructor() {
fun generateRandomNumber(): Int = Random.nextInt()
}
App.kt: Using factory instead of builder since #Component.Builder is deprecated.
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.factory().create(this)
}
}
ActivityModule.kt: Provide a module to dagger to create your activities.
#Module
interface ActivityModule {
#ContributesAndroidInjector
fun provideMainActivity(): MainActivity
}
MainActivity.kt: Finally, extend from DaggerAppCompatActivity.
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var testClass: TestClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
val x = testClass.getRandomValueFromCTest()
}
}
I believe this should run without issues. For more reference you could look into this sample and the new simpler docs at dagger.dev/android
You are missing the actual injection call.
class MainActivity : AppCompatActivity() {
#Inject
lateinit var testClass: TestClass
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
MainActivity should extends DaggerActivity, not AppCompatActivity

Dagger2 + Kotlin: lateinit property appComponent has not been initialized

I'm using Dagger2 to inject class dependent like bellow.
This is a component for Dagger2, AppComponent.kt:
#Component(modules = [ContextModule::class, SuggestModule::class, RetrofitModule::class,
TranslateModule::class, DatabaseModule::class, ViewModelModule::class, FragmentModule::class])
interface AppComponent {
#Singleton
fun inject(fragment: TranslateFragment)
#Singleton
fun inject(fragment: FavouriteFragment)
#Singleton fun inject(fragment: TensesFragment)
#Singleton
fun inject(activity: TensesActivity)
#Singleton
fun inject(activity: MainActivity)
#Singleton
fun inject(translateViewModel: TranslateViewModel)
#Singleton
fun inject(favouriteViewModel: FavouriteViewModel)
#Singleton
fun inject(translateProvider: TranslateProvider)
}
This is App class extended Application class, where i built my component , App.kt
class App : Application() {
companion object{
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.builder()
.contextModule(ContextModule(this))
.suggestModule(SuggestModule(this))
.retrofitModule(RetrofitModule())
.translateModule(TranslateModule(TranslateProvider()))
.databaseModule(DatabaseModule(DatabaseManager(this)))
.viewModelModule(ViewModelModule())
.fragmentModule(FragmentModule())
.build()
}
}
First, i injected TranslateFragment into MainActivity, MainActivity.kt
class MainActivity : AppCompatActivity {
constructor(){
App.appComponent.inject(this)
}
#Inject
lateinit var translateFragment: TranslateFragment
}
Second, i injected TranslateViewModel into TranslateFragment, TranslateFragment.kt
class TranslateFragment : Fragment {
#Inject
constructor() {
App.appComponent.inject(this)
}
#Inject
lateinit var translateViewModel: TranslateViewModel
}
Third, i injected TranslateProvider into TranslateViewModel, TranslateViewModel.kt
class TranslateViewModel : BaseObservable {
#Inject
constructor() {
App.appComponent.inject(this)
}
#Inject
lateinit var translateProvider: TranslateProvider
}
End, i injected RetrofitProvider into TranslateProvider, TranslateProvider.kt
class TranslateProvider {
#Inject
constructor() {
App.appComponent.inject(this)
}
#Inject
lateinit var retrofitProvider: RetrofitProvider
}
But i received a error at TranslateProvider.kt:
kotlin.UninitializedPropertyAccessException: lateinit property
appComponent has not been initialized
I'm not understand, please help me.
Thanks!
I created a instance of TranslateProvider in: .translateModule(TranslateModule(TranslateProvider()))
When constructor of TranslateProvider called appComponent, but appComponent was not initialized that time.
Just move it go to out of TranslateModule constructor look like:
Before:
#Module
class TranslateModule(private val translateProvider: TranslateProvider) {
#Provides
fun getTranslateProvider(): TranslateProvider {
return translateProvider
}
}
After:
#Module
class TranslateModule {
#Provides
fun getTranslateProvider(): TranslateProvider {
return TranslateProvider()
}
}

Wrong graph in Dagger 2 with kotlin

I am trying to use Dagger 2 with Kotlin and I am missing something. The problem comes when I try to inject an MVP presenter into the Fragment.
These are my files:
AppClass
class AppClass : Application(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun activityInjector(): AndroidInjector<Activity> = dispatchingAndroidInjector
companion object {
lateinit var instance: AppClass private set
}
}
AppComponent
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class,
AppModule::class,
ActivityBuilder::class])
interface AppComponent {
fun inject(appClass: AppClass)
#Component.Builder
interface Builder {
#BindsInstance
fun application(appClass: AppClass): Builder
fun build(): AppComponent
}
}
AppModule
#Module
class AppModule {
#Provides
#Singleton
fun provideContext(app : AppClass) = app
#Provides
#Singleton
fun provideDatabaseManager() = DatabaseManager()
}
ActivityBuilder
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [ActivityModule::class, HomeFragmentProvider::class])
internal abstract fun bindHomeActivity(): HomeActivity
}
HomeFragmentProvider
#Module
public abstract class HomeFragmentProvider {
#ContributesAndroidInjector(modules = HomeFragmentModule.class)
abstract HomeFragment provideHomeFragmentFactory();
}
HomeFragmentModule
#Module
class HomeFragmentModule {
#Provides
fun provideHomePresenter(databaseManager: DatabaseManager): HomeContract.Presenter {
return HomePresenter(databaseManager)
}
}
HomeFragment
class HomeFragment : HomeContract.View {
#Inject lateinit var mPresenter: HomeContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
if (arguments != null) {
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false)
return view
}
companion object {
private val STARTING_PAGE_INDEX = 0
fun newInstance(): HomeFragment {
val fragment = HomeFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
}
}
Log
...app/build/tmp/kapt3/stubs/debug/.../di/component/FragmentComponent.java:9: error: [Dagger/MissingBinding] ....HomeContract.Presenter cannot be provided without an #Provides-annotated method.
I try to inject the DatabaseManager in the Activity and works fine so I suppose that my problem is related to the Fragment dependencies.
Any help is appreciated.
UPDATE
HomePresenter
class HomePresenter() :
BasePresenter<HomeContract.View>(), HomeContract.Presenter {
private lateinit var mDatabaseManager : DatabaseManager
#Inject constructor(databaseManager: DatabaseManager) : this() {
this.mDatabaseManager = databaseManager
}
}
Define HomePresenter variable as a function parameter, as below:
#Module
class HomeFragmentModule {
#Provides
fun provideHomePresenter(homePresenter: HomePresenter): HomeContract.Presenter {
return homePresenter
}
}
Modify your HomePresenter class as below:
class HomePresenter #Inject constructor(val databaseManager: DatabaseManager) :
BasePresenter<HomeContract.View>(), HomeContract.Presenter {
....
}
Also, you can use DatabaseManager instance from the constructor directly by marking it as val, there is no need to define it separately.
In AppComponent, try using AndroidInjectionModule::class if you are not using support Fragments.
#Singleton
#Component(modules = [AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class])
interface AppComponent {
....
}
Hope this will resolve your issue!
I came up with the problem. If I change the injection in the HomeFragment from #Inject lateinit var mPresenter: HomeContract.Presenter to #Inject lateinit var mPresenter: HomePresenter everything works fine.

Problems with HasSupportFragmentInjector in kotlin - DispatchingAndroidInjector is null

I'm trying to implement mvp pattern with dagger 2 support in my app
Here's the objects:
class BaseApplication : Application(), HasActivityInjector
{
override fun onCreate()
{
super.onCreate()
initDi()
}
private fun initDi(){
DaggerAppComponent.builder().application(this).build().inject(this)
}
#Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector(): AndroidInjector<Activity>
{
return activityInjector
}
}
#Singleton
#Component(modules = arrayOf(AndroidInjectionModule::class, AppModule::class, ActivityBuilder::class))
interface AppComponent
{
#Component.Builder
interface Builder
{
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: BaseApplication)
}
#Module
class AppModule
{
#Provides
#Singleton
internal fun provideContext(application: Application): Context
{
return application
}
}
#Module
abstract class ActivityBuilder
{
#ContributesAndroidInjector(modules = arrayOf(LoginFragmentProvider::class))
internal abstract fun bindAuthenticationActivity(): AuthenticationActivity
}
#Module
public abstract class LoginFragmentProvider
{
#ContributesAndroidInjector
abstract LoginFragment provideLoginFragmentFactory();
}
class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector
{
#Inject lateinit var androidInjector: DispatchingAndroidInjector<Fragment>
override fun supportFragmentInjector(): AndroidInjector<Fragment>
{
return androidInjector
}
}
class LoginFragment : Fragment() {
override fun onAttach(context: Context?)
{
AndroidSupportInjection.inject(this)
super.onAttach(context)
}
The problem is, when login fragment calls AndroidSupportInjection.inject(this), The AuthenticationActivity supportFragmentInjector get called, but the androidInjector is still null
As a result, I'm getting the exception:
java.lang.RuntimeException: Unable to start activity .....AuthenticationActivity}: kotlin.UninitializedPropertyAccessException: lateinit property androidInjector has not been initialized
I'm not sure how to fix this
Thanks in advance
I think you forget to inject your AuthenticationActivity. You should call AndroidInjection in onCreate.
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
Edit: You can check my example repo for more information. https://github.com/savepopulation/dc-tracker

Categories

Resources