3

I am working on project with Dagger2 for DI. I am injecting presenter in MVP architecture. For some reason when I build app it crashes with error: "lateinit property presenter has not been initialized". I know it means that injection is not made but I don't understand why. Here is my code:

APPLICATION CLASS

class FlowerApp : Application() {

override fun onCreate() {
    super.onCreate()
    initAppComponent()
}

private fun initAppComponent() {
    appComponent = DaggerAppComponent
        .builder()
        .appModule(AppModule(this))
        .build()
}

companion object {
    lateinit var appComponent: AppComponent
}
 }

HOMEMODULE

@Module
class HomeModule(var homeFragment: HomeContract.View) {
    @Provides
    fun providePresenter(homeInteractor: HomeInteractor): HomePresenter {
        return HomePresenter(homeFragment, homeInteractor)
    }

    @Provides
    fun provideInteractor(): HomeInteractor {
        return HomeInteractor()
    }
}

APPCOMPONENT

 @Component(
    modules = [
        (AppModule::class),
        (NetworkModule::class),
        (HomeModule::class)
    ]
)

interface AppComponent {
    fun inject(application: FlowerApp)
    fun inject(homeFragment: HomeContract.View)
}

HOMEFRAGMENT

class HomeFragment : Fragment(), HomeContract.View {
    @Inject
    lateinit var presenter: HomePresenter
    private lateinit var flowerAdapter: FlowerAdapter
    private var startingPage = 1
    private var recyclerStartPos = 0

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_home, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setAdapter()
        presenter.getFlowers(startingPage)
        setListeners()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        FlowerApp.appComponent.inject(this)
    }

If any other info about code needed just ask...

Edited: logcat error:

2020-06-06 22:12:37.513 13401-13401/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: element.list.flowersmvp, PID: 13401
    kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized
        at element.list.flowersmvp.home.HomeFragment.onViewCreated(HomeFragment.kt:37)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:892)
        at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
        at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
        at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
        at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
        at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManagerImpl.java:2663)
        at androidx.fragment.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManagerImpl.java:2613)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:246)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:542)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:201)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1392)
        at android.app.Activity.performStart(Activity.java:7252)
        at android.app.ActivityThread.handleStartActivity(ActivityThread.java:2970)
        at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
        at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:201)
        at android.app.ActivityThread.main(ActivityThread.java:6806)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
Boken
  • 4,825
  • 10
  • 32
  • 42
Kratos
  • 681
  • 1
  • 13
  • 30

4 Answers4

2

You're trying to inject the presenter provided in the HomeModule via the application's main component. This won't work because you've never provided the HomeModule to your AppComponent. HomeModule shouldn't even be a part of the AppComponent since the things it provides (presenter and interactor) only exist when the fragment exists, meaning they exist in the fragment scope, not the application scope.

What you need to do is create another component that will inject into your fragment, e.g.

@Component(
    modules = [
        (HomeModule::class)
    ]
)
interface HomeComponent {
    fun inject(homeFragment: HomeFragment)
}

And then in your HomeFragment:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    DaggerHomeComponent
        .builder()
        .homeModule(HomeModule(this))
        .build()
        .inject(this)
}

If you need things from application scope (from network and app modules), then you inject them through the AppComponent as you've previously did.

chef417
  • 494
  • 2
  • 7
0

I think problem is in var homeFragment: HomeContract.View in your HomeModule. I suggest you rewrite HomePresenter and replace passing HomeFragment via constructor to methods bindView/unbindView.

class HomePresenter(private val interactor: HomeInteractor) {
    var view: HomeView? = null

    fun bindView(view: HomeView) {
        this.view = view
    }

    fun unbindView() {
        view = null
    }
}

Storing fragment reference in presenter can cause memory leaks and null-pointer exceptions in situations when Fragment destroyed and presenter is not.

ZSergei
  • 807
  • 10
  • 18
  • var homeFragment: HomeContract.View is not acctually a fragment refference. it is interface (part of MVP) from class HomeContract. My real fragment HomeFragment impements it only to know which methods it needs to override. So this suggestion doesn't make sense. But thanks anyways... – Kratos Jun 04 '20 at 21:48
  • That "interface" ***is*** actually a fragment reference. And you will most likely get either memory leaks, or crashes, by doing that. – EpicPandaForce Jun 04 '20 at 21:56
  • hmm thats weird. I get this way of doing it from my superior teamLead's project which I thought is written in very highly quality. Ok I will changge it. So @EpicPandaForce you think this could really be reason for dagger2 not woking? – Kratos Jun 04 '20 at 22:01
  • You should always (well, try to always :p) test if your app works on ANY screen after low memory condition. You can see how to test for that here: https://stackoverflow.com/questions/49046773/singleton-object-becomes-null-after-app-is-resumed/49107399#49107399 In your case, if you pass Fragment references around without using `findFragmentByTag`, you'll either get NPEs, or you'll get overlapping fragments. – EpicPandaForce Jun 04 '20 at 22:39
  • OK thanks. but this doesn't help me with Dagger2 error and this question. Do you maybe know how to solve it? – Kratos Jun 05 '20 at 08:42
  • Honestly I'm kind of surprised it works at all. `HomeModule` has a mutable variable that is never initialized. But in your case, it's null because you specified `fun inject(HomeContract.View)` instead of `fun inject(HomeFragment)`. – EpicPandaForce Jun 05 '20 at 17:41
0

In your component you are not injecting HomeFragment, but the base class (HomeContract.View). Meaning Dagger wont inject the properties of HomeFragment that are not in the base class.

Mister Smith
  • 27,417
  • 21
  • 110
  • 193
  • Yes but if i change Appcompnent to take HomeFragment instead of HomeContract.View it still crashes – Kratos Jun 08 '20 at 10:45
0

I think the problem, you didn't inject the HomeFragment

Add an extension method in AppComponent

@Component(
    modules = [
        (AppModule::class),
        (NetworkModule::class),
        (HomeModule::class)
    ]
)

interface AppComponent {
    fun inject(application: FlowerApp)
    fun inject(homeFragment: HomeContract.View)
}

fun HomeFragment.inject(){
    FlowApp.appComponent.inject(this)
}

Inject HomeFragment

override fun onAttach(context: Context) {
        super.onAttach(context)
        inject()
}