Problems with HasSupportFragmentInjector in kotlin - DispatchingAndroidInjector is null - android

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

Related

dagger2 does not provide injection in base activity

I am trying to implement the latest version of Dagger2 in a Single-Activity app, but it is not known why, when initializing my starting activity, Dagger2 does not inject dependencies, I has a
fatal error in my base activity : Unable to resume activity kotlin.UninitializedPropertyAccessException: lateinit property navigatorHolder has not been initialized
here is my code
AppComponent:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityInjectionModule::class,
ActivityProviderModule::class,
AndroidInjectionModule::class,
NetworkModule::class,
RemoteModule::class,
NavigationModule::class,
ParserModule::class,
CacheModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
#BindsInstance
fun context(context: Context) : Builder
fun build(): AppComponent
}
override fun inject(app: App)
}
App:
class App : DaggerApplication(){
private val applicationInjector =
DaggerAppComponent.builder().application(this).context(this).build()
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
applicationInjector
companion object {
lateinit var cicerone: Cicerone<Router>
private set
}
override fun onCreate() {
super.onCreate()
cicerone = Cicerone.create()
initAppComponent()
initStetho()
Timber.plant(Timber.DebugTree())
}
AppActivity:
class AppActivity : MvpAppCompatActivity() , HasAndroidInjector, RouterProvider {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
#Inject
lateinit var mainActivityProvider: ActivityProvider
#Inject
lateinit var navigatorHolder: Lazy<NavigatorHolder>
#Inject
override lateinit var ciceroneRouter: Router
override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector
private val navigator : Navigator by lazy {
CustomSupportAppNavigator(this, supportFragmentManager, R.layout.activity_main)
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
AndroidInjection.inject(this)
mainActivityProvider.acitvity = this
Timber.e("onCreate AppActivity")
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_main)
initBottomBar()
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.get().setNavigator(navigator)
}
ActivityInjectionModule
#Module(includes = [AndroidInjectionModule::class])
interface ActivityInjectionModule {
#ContributesAndroidInjector(
modules = [FragmentInjectionModule::class]
)
fun activityInjector() : AppActivity
}
NavigationModule
#Module
class NavigationModule {
#Provides
#Singleton
fun provideRouter() = App.cicerone.router
#Provides
#Singleton
fun provideNavigatorHolder() : NavigatorHolder {
return App.cicerone.navigatorHolder
}
#Provides
#Singleton
fun provideLocalNavigationHolder(): LocalCiceroneHolder {
return LocalCiceroneHolder()
}
}
Also i post android:name=".App" inandroid manifest.
I have tried many different options already, but I still cannot find the reason
Can you change your App to like this and try again:
class App : DaggerApplication(){
private val appComponent =
DaggerAppComponent.builder().application(this).context(this).build().inject(this)
companion object {
lateinit var cicerone: Cicerone<Router>
private set
}
override fun onCreate() {
super.onCreate()
cicerone = Cicerone.create()
initAppComponent()
initStetho()
Timber.plant(Timber.DebugTree())
}
Also remove AndroidInjector<App> from AppComponent

Dagger 2.26 field injection into application failed

When I try to inject app crashing with lateinit property ... has not been initialized exception. I understand I have to inject my app into graph, but I do not succeed in that. What is the best way to do this?
class App : Application() {
#Inject
lateinit var localeManager: Lazy<LocaleManager>
val appComponent: AppComponent by lazy {
DaggerAppComponent.factory().create(applicationContext)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(localeManager.get().setLocale(base))
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
localeManager.get().setLocale(this)
}
}
#Singleton
#Component(modules = [NetworkModule::class, AppSubcomponents::class])
interface AppComponent {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): AppComponent
}
fun registrationComponent(): RegistrationComponent.Factory
fun lobbyComponent(): PropertyComponent.Factory
fun supplierComponent(): SupplierComponent.Factory
fun notificationComponent(): NotificationComponent.Factory
fun serviceComponent(): ServiceComponent.Factory
fun settingsComponent() : SettingsComponent.Factory
fun cardComponent() : CardComponent.Factory
fun inject(activity: MainActivity)
}
class LocaleManager #Inject constructor(val storage: UserStorage) {
//some code
}

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

Dagger not initizalize var

I have problems when starting Dagger in Android project with Kotlin.
This estructure is the next one
Dagger is included in an Android module that is called by the client application
MagicBox.kt
interface MagicBox {
fun getDate(): Long?
}
MagicBoxImpl.kt
class MagicBoxImpl (): MagicBox{
var date: Long = Date().time
override fun getDate(): Long {
return date
}
}
MainModule.kt
#Module
class MainModule (private val app: Application) {
#Provides
#Singleton
fun provideMagicBox(): MagicBox {
return MagicBoxImpl()
}
}
MainComponent.kt
#Singleton
#Component(modules = [MainModule::class, PresenterModule::class])
interface MainComponent{
fun inject(target: Activity)
}
Application.kt
class Application: Application() {
lateinit var mainComponent: MainComponent
override fun onCreate() {
super.onCreate()
mainComponent = initDagger(this)
}
private fun initDagger(app: Application): MainComponent =
DaggerMainComponent.builder()
.mainModule(MainModule(app))
.build()
}
MainActivity.kt
#Inject
lateinit var magicBox: MagicBox
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
(application as ClientSdk).mainComponent.inject(this)
tvDaggerTest = findViewById(R.id.tvDaggerTest)
tvDaggerTest!!.text = magicBox.getDate().toString()
}
Get the following error
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property magicBox has not been initialized
fun inject(target: Activity) should be fun inject(target: MainActivity)
Also for better Dagger usage, the following should be:
#Module
abstract class MainModule {
#Binds
abstract fun magicBox(impl: MagicBoxImpl): MagicBox
}
and
#Singleton class MagicBoxImpl #Inject constructor(): MagicBox {

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

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.

Categories

Resources