(Kotlin)NullPointerException in textview using callback with Dagger 2 object instance - android

NullPointerException in textview when I try to change text by dagger 2 instance object. Note: Using a common instance (new Myobjcet(this)) works.
Activity
class MainActivity : AppCompatActivity(), MyCallBack{
#Inject
lateinit var myObject: MyObject
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mytextview.text = "first change"
val myComponent = (application as App).myComponent
myComponent.inject(this)
}
override fun callBack(string: String) {
try {
mytextview.text = string
} catch (e: Exception) {
Log.d("xxx", e.toString())
}
}
}
Object
class MyObject(var res: MyCallBack) {
init {
res.callBack("second change")
}
}
Component
#Component(modules = MyModule.class)
public interface MyComponent {
void inject(MainActivity mainActivity);
}
Module
#Module
class MyModule {
#Provides
fun proviesMyObject(): MyObject = MyObject(MainActivity())
}

the problem stems from the fact that you're manually instantiating MainActivity when constructing MyObject in your module. because it is manually constructed, it isn't managed by the framework and is therefore not run through it's expected lifecycle (e.g. onCreate(), onStart(), onResume(), etc).
(side note - never do this in production code).
since onCreate() never runs for that manually constructed instance, the layout for that instance isn't inflated, so MyObject is referring to an Activity (as an implementation of MyCallBack) that has no awareness of any Views.
if you want to involve a valid, framework-managed instance of MyActivity in your object graph, one solution is to add it as a required constructor parameter to your module, like so:
#Module
class MyModule(private val myCallBack: MyCallBack) {
#Provides
fun providesMyObject(): MyObject = MyObject(myCallBack)
}
...then, in MainActivity construct the component and perform self-injection, like so:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
mytextview.text = "first change"
DaggerMyComponent.builder()
.myModule(MyModule(this))
.build()
.inject(this)
}
hope that helps clear things up!

The problem is that in your Module, you are instantiating a new MainActivity that is different from the one instantiated by the framework. You need to realize that if you manually instantiated an activity, its views will not be inflated, hence the NullPointerException. I suggest you pass the instance of your activity in your module instead.
#Module
class MyModule(val res: MyCallBack) {
#Provides
fun providesMyObject(): MyObject = MyObject(res)
}
Instantiate your component in MainActivity like
val myComponent = DaggerMyComponent.builder()
.myModule(MyModule(this))
.build()
myComponent.inject(this)

Related

Can the lifecycleScope for the current Activity be injected into a non-Activity class using Dagger?

Here's an extremely simplified example:
class MyActivity : DaggerAppCompatActivity() {
}
#ActivityScope // Dagger scope
class Foo #Inject constructor(
private val bar: Bar,
#Named("MAIN_SCOPE") private val scope: CoroutineScope,
) {
private val myJob = scope.launch {
bar.myFlow?.collect {
// process result
}
}
fun cleanUp() {
myJob.cancel()
}
}
I'm wondering if there's a better way to approach this. Again, this is a simplified example of what I have. I realize an obvious alternative would be to pass the scope from MyActivity to Foo - let's just say that isn't practical in this case.
Is there a way to use Dagger to inject the lifecycleScope from the current Activity - MyActivity in this case, but it could be a different one at runtime - into Foo? Then I couldn't need to explicitly cancel the Job; I could just make use of the injected scope, assured that coroutine Jobs would shut down when the Activity shut down.
Here is how dagger component can be created:
#Component // or can be #Subcomponent
#ActivityScope
internal interface MyActivityComponent {
fun inject(activity: MyActivity)
#Component.Factory // or #Subcomponent.Factory respectively
interface Factory {
fun create(#BindsInstance activity: MyActivity): MyActivityComponent
}
}
And here is activity and Foo class:
class MyActivity : AppCompatActivity() {
#Inject
internal lateinit var foo: Foo
override fun onCreate(savedInstanceState: Bundle?) {
DaggerMyActivityComponent.factory() // or take factory from parent component
.create(this)
.inject(this)
foo.launchFoo()
}
}
#ActivityScope
internal class Foo #Inject constructor(
private val activity: MyActivity,
private val bar: Bar,
) {
private val activityScope: CoroutineScope
get() = activity.lifecycleScope
fun launchFoo() {
activityScope.launch {
bar.myFlow?.collect {
// process result
}
}
}
}

Reference activity in koin Module

I have a single activity application.
My MainActivity is referenced in a number of dependency injection modules, as the implementer of these interfaces. I currently have a work around, which is less than ideal.
class MainActivity : TransaktActivity(), RegistrationNavigator, IAuthPresenter,
IAuthButtonNavigator {
override fun navigateAwayFromAuth() {
navController.navigate(R.id.homeFragment)
}
override fun navigateToAuthPin(buttonId: Int) {
//todo navigate to auth with pin fragment
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_mainActivity = this
setContentView(R.layout.activity_main)
}
companion object {
private var _mainActivity: MainActivity? = null
fun getInstance() = _mainActivity
}
}
interface RegistrationNavigator {
fun navigateToCardDetails()
fun navigateToOtpCapture()
fun navigateToLoading()
fun navigateOutOfCardRegistration()
}
The appModule is a Koin Module
val appModule = module {
viewModel { SharedViewModel() }
single { MainActivity.getInstance() as RegistrationNavigator }
}
What is the preferred way of achieving this?
Android-lifecycled components such as activities should not be in koin modules.
For example you will have issues with e.g. configuration changes since the koin module would be serving references to stale activity after the activity is recreated.
I haven't really worked with NavController but rather rolled up my own navigation solution. As a generic approach I would refactor the RegistrationNavigator implementation to a separate class the instance of which you can provide from your koin module. If lifecycle-dependent params such as Context (or NavController) are needed, supply them as function args.

Problem with injecting of dagger subcomponent into activity

I have a hard time figuring out how to set up Dagger component hierarchy to match my vision. Any help would be greatly appreciated and please feel free to criticise the vision if you find any holes in it.
Let's get to the point.
I'm trying to redesing part of my application related to user registration. Since registration is a self contained domain I assume that it's a good candidate for a separate dagger component(RegistrationSubcomponent). Within that registration domain I will show an activity(RegistrationActivity) and on top of it few fragments(RegistrationFragment1, RegistrationFragment2, ...). I already have an application component(AppComponent) with the life scope of an application which should be a parent for RegistrationSubcomponent. AppComponent is used to inject dependecies into other activites excluding RegistrationActivity but also to a common activity(CommonActivity) which is a parent for every activity in my app including RegistrationActivity. To inject dependencies into RegistrationActivity i want to use RegistrationSubcomponent.
The RegistrationSubcomponent should be instantiated shortly before displaying RegistrationActivity and should live until after the RegistrationActivity returns. I dont't want to bind RegistrationSubcomponent lifecycle with RegistrationActivity lifecycle. Mayby in this case it may have sense but in future I will probably would like to create components for other app domains which may not neceserralily match the lifecycle of activities they encompass.
In general the problem is injection of dependecies specfied in RegistrationComponent into RegistrationActivity as well as to RegistrationFragments.
The problem is that the app crashes when navigating to RegistrationActivity with following error:
Unable to start activity ComponentInfo{pl.kermit.diproblem/pl.kermit.diproblem.activities.RegistrationActivity}: java.lang.IllegalArgumentException: No injector factory bound for Class
Minimal project presenting the problem can be found at https://github.com/mariola3000/DIProblemStack01
Below I put the most important parts of code
// APP COMPONENT
#Singleton
#Component(modules = [AndroidInjectionModule::class, AppModule::class, AppAndroidBindingModule::class,
AndroidSupportInjectionModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(application: Application)
}
#Module(subcomponents = [RegistrationComponent::class])
class AppModule {
#Provides
fun provideGlobalDependency(): GlobalDependency {
return GlobalDependency("GlobalDependency")
}
#Provides
fun provideGlobal2Dependency(): Global2Dependency {
return Global2Dependency("Global2Dependency")
}
}
#Module
abstract class AppAndroidBindingModule {
#PerActivity
#ContributesAndroidInjector()
abstract fun bindMainActivity(): MainActivity
}
// REGISTRATION COMPONENT
#PerRegistration
#Subcomponent(modules = [RegistrationModule::class, RegistrationAndroidBindingModule::class])
interface RegistrationComponent {
#Subcomponent.Builder
interface Builder {
fun requestModule(module: RegistrationModule): Builder
fun build(): RegistrationComponent
}
}
#Module
class RegistrationModule {
#PerRegistration
#Provides
fun provideRegistrationDependency(): RegistrationDependency {
return RegistrationDependency("RegistrationDependency")
}
#PerRegistration
#Provides
fun provideRegistrationFragmentDependency(): RegistrationFragmentDependency {
return RegistrationFragmentDependency("RegistrationFragmentDependency")
}
}
#Module
abstract class RegistrationAndroidBindingModule {
#PerActivity
#ContributesAndroidInjector
abstract fun bindRegistrationActivity(): RegistrationActivity
#PerFragment
#ContributesAndroidInjector
abstract fun bindRegistrationFragment(): RegistrationFragment
}
// ACTIVITIES
abstract class CommonActivity : DaggerAppCompatActivity(){
#Inject
lateinit var globalDependency: GlobalDependency
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
Toast.makeText(this, "global message: " + globalDependency.message, Toast.LENGTH_LONG).show()
}
}
class MainActivity : CommonActivity() {
#set:Inject
lateinit var global2Dependency: Global2Dependency
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val text: TextView = findViewById(R.id.main_text)
text.text = global2Dependency.message
val button: Button = findViewById(R.id.main_button)
button.setOnClickListener {
startActivity(Intent(this, RegistrationActivity::class.java))
}
}
}
class RegistrationActivity : CommonActivity() {
#Inject
lateinit var registrationDependency: RegistrationDependency
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_registration)
val registrationMessage = findViewById<TextView>(R.id.registration_text)
registrationMessage.text = registrationDependency.message
val transaction = this.supportFragmentManager.beginTransaction()
val fragment = RegistrationFragment()
transaction.replace(R.id.registration_fragment_container, fragment)
transaction.addToBackStack(fragment.javaClass.simpleName)
transaction.commit()
}
}

New Architecture with Dagger and Kotlin

I have a problem with New Architecture components in Kotlin, when I create ViewModel component in recomended way (in onCreate() method) the result is as suposed:
after activity orientation changes, I got the same instance of ViewModel as before
Here is the way i create this
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
val arrayMap = ArrayMap<Class<out ViewModel>, ViewModel>()
arrayMap.put(ListViewModel::class.java, ListViewModel(webApi, repoDao))
val factory = ViewModelFactory(arrayMap)
listViewModel = ViewModelProviders.of(this, factory).get(ListViewModel::class.java)
listViewModel.items.observe({ this.lifecycle }) {
Toast.makeText(this, it?.joinToString { it + " " } ?: "null", Toast.LENGTH_SHORT).show()
}
But when I have used Dagger for inject ListViewModel I got new instance of ListViewModel every time Activity was recreated. Here is a code of Dagger ListActivityModel.
#Module #ListActivityScopeclass ListActivityModule {
#Provides
#ListActivityScope
fun provideListViewModel(webApi: WebApi, repoDao: RepoDao, listActivity: ListActivity): ListViewModel {
val arrayMap = ArrayMap<Class<out ViewModel>, ViewModel>()
arrayMap.put(ListViewModel::class.java, ListViewModel(webApi, repoDao))
val factory = ViewModelFactory(arrayMap)
val result = ViewModelProviders.of(listActivity, factory).get(ListViewModel::class.java)
return result
}
}
Then ListActivity onCreate() method looks like:
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
listViewModel.items.observe({ this.lifecycle }) {
Toast.makeText(this, it?.joinToString { it + " " } ?: "null", Toast.LENGTH_SHORT).show()
}
}
And there is what I have notice after logging:
D/ListActivity: ---> onCreate() ListActivity: = [com.example.dom.app.new_arch.ListActivity#a0f2778]
D/ListActivity: ---> onCreate() listViewModel: = [com.example.dom.app.new_arch.ListViewModel#54a8e51]
//Activity orientation changes
E/ViewModelStores: Failed to save a ViewModel for com.example.dom.app.new_arch.ListActivity#a0f2778
D/ListActivity: ---> onCreate() ListActivity: = [com.example.dom.app.new_arch.ListActivity#6813433]
D/ListActivity: ---> onCreate() listViewModel: = [com.example.dom.app.new_arch.ListViewModel#55cf3f0]
The error I have received :
ViewModelStores: Failed to save a ViewModel for
comes from Android class HolderFragment with package android.arch.lifecycle.
There is something what I missed working with Dagger and new arch components?
The issue has to do with the order of dagger injection and activity creation. The view model implementation relies on a non-visual fragment for identity. By injecting the viewModelProvider before the activity has completed onCreate it is unable to complete this association.
Since super.onCreate does not likely depend on things you are injecting try injecting after the call to super.onCreate and you should be fine.
I had this exact same issue and solved it by this change in order.
Specifically from your code instead of:
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
go with:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidInjection.inject(this)
setContentView(R.layout.activity_list)
JP
The way I do this is by only providing the ViewModelFactory using Dagger. Then it gets injected in the activity and you call ViewModelProviders.of(listActivity, factory).get(ListViewModel::class.java) from there. The reason your approach doesn't work is that AndroidInjection.inject() will create the ViewModel before onCreate, which leads to undefined behavior.
Also see: https://github.com/googlesamples/android-architecture-components/issues/202
I don't use AndroidInjection.inject() because it creates a new Dagger component. I create an Dagger Component in the Application class and I use that component instance to call inject in all other places of the app. This way your singletons are initialized only one time.

Dagger2 not inject

I am a newbie to android and trying to use Dagger2. I spend whole night and still dont know why my dagger does not provide presenter. Here are my code (I use Kotlin)
AppComponent
#Singleton
#Component(modules = arrayOf(PresenterModule::class))
interface AppComponent {
fun inject(target: SplashActivity)
}
PresenterModule
#Module
class PresenterModule {
#Provides
#Singleton
fun provideSplashPresenter(): SplashPresenter {
return SplashPresenter()
}
}
App
class App: Application() {
companion object {
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
appComponent = initDagger()
}
private fun initDagger(): AppComponent {
return DaggerAppComponent.create()
}
}
This is the presenter
class SplashPresenter: BasePresenterImpl<SplashContract.View>(), SplashContract.Presenter {
override fun performToast(mess: String) {
logi("abc", "performToast")
logi("abc", "mess: " + mess)
mView?.showLoading()
if (mess.isNullOrBlank()) {
mView?.showTosat("this is empty mess") ?: logi("abc", "null")
} else {
mView?.showTosat(mess) ?: logi("abc", "null")
}
mView?.hideLoading()
}
}
And finally, this is my SplashActivity
class SplashActivity : BaseActivity(), SplashContract.View {
#Inject
lateinit var presenter: SplashPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
presenter.attachView(this)
//TODO: check log in
//TODO: If logged in => start main screen
//TODO: If not logged in => load login activity
button.setOnClickListener{
presenter.performToast(editText.text.toString())
logi("abc", "perform clicked")
}
}
}
When I run these code, I got this error
Lateinit property presenter has not been initialized, which means that "Inject" does not work
Since you're not using constructor injection here (which you can't, because you don't 'own' the activity's constructor) Dagger does not 'know' that it has to inject something into your Activity.
You have to manually inject like this:
(applicationContext as App).appComponent.inject(this)
in your SplashActivity's onCreate() method (before using the presenter, of course).
Second, your presenter needs a constructor that tells Dagger how to construct/'build' the presenter, which means a constructor annotated with the #Inject annotation, so:
class SplashPresenter #Inject constructor(): BasePresenterImpl<SplashContract.View>(), SplashContract.Presenter
You forgot to inject the SplashActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
App.appComponent.inject(this)
setContentView(R.layout.activity_splash)
...

Categories

Resources