Android: how to make multi-module dagger component injection? - android

I have android gradle multi-module dagger application.
Inside each module I'm doing inject via it's own component like this:
MainApp.appComponent.inject(this) for module :app and
BaseApp.appComponent.inject(this) for module :app:base.
Now trying to make SharedPreferences wrapper available within app:base module. Until this point everything worked fine, but after this I faced this error
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.alazar.tracker/com.alazar.tracker.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property appComponent has not been initialized
Application.name property specified at Manifest.xml of the :app:base module.
What am I doing wrong and how to fix that? Thanks for any suggestion.
MainActivity:
class MainActivity : BaseActivity(), View.OnClickListener {
#Inject
lateinit var userManager: UserManagerInterface
#Inject
lateinit var preferences: PreferenceProvider
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MainApp.appComponent.inject(this)
binding = ActivityMainBinding.inflate(layoutInflater)
binding.btnSignOut.setOnClickListener(this)
binding.btnStart.setOnClickListener(this)
binding.btnStop.setOnClickListener(this)
if (!userManager.isAuthenticated()) {
openPostActivity.launch(Intent(this, AuthActivity::class.java))
} else {
setContentView(binding.root)
changeStatus(preferences.getServiceStatus())
}
}
}
MainApp:
#MainScope
#Component(
dependencies = [
],
modules = [
MainModule::class,
]
)
interface MainAppComponent {
fun inject(activity: MainActivity)
fun inject(activity: MapActivity)
}
#Module(
includes = [
AuthUserModule::class,
BaseModule::class,
]
)
class MainModule
class MainApp : Application() {
override fun onCreate() {
super.onCreate()
appComponent = DaggerMainAppComponent
.builder()
.build()
}
companion object {
lateinit var appComponent: MainAppComponent
}
}
BaseApp:
#Singleton
#Component(
modules = [
BaseModule::class,
]
)
interface BaseComponent {
fun inject(baseApp: BaseApp)
fun inject(wrapper: SharedPrefWrapper)
}
#Module
class BaseModule constructor(private val application: Application) {
#Provides
fun provideContext() : Context = application.applicationContext
#Provides
fun provideSharedPreferences() : PreferenceProvider = SharedPrefWrapper(application.applicationContext)
}
class BaseApp : Application() {
override fun onCreate() {
super.onCreate()
appComponent = DaggerBaseComponent
.builder()
.baseModule(BaseModule(this))
.build()
}
companion object {
lateinit var appComponent: BaseComponent
}
}
SharedPreferences wrapper
class SharedPrefWrapper #Inject constructor(private val context: Context) : PreferenceProvider {
init {
BaseApp.appComponent.inject(this)
Log.d("************ CONTEXT", context.toString())
}
private var preferences: SharedPreferences = context.getSharedPreferences(
context.getString(R.string.shared_preference_name),
AppCompatActivity.MODE_PRIVATE
)
override fun saveServiceStatus(status: Boolean) {
val editor = preferences.edit()
editor.putBoolean(context.getString(R.string.preference_service_param), status)
editor.apply()
}
override fun getServiceStatus(): Boolean {
return preferences.getBoolean(context.getString(R.string.preference_service_param), false)
}
}

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

#Inject lateinit property has not been initialized Dagger2

I try to use Dagger2 to my project. I have a Firebase service and a class called SyncFactory that makes a specific request. When i get a call from Firebase i make my request.
I have created a Mangers Module
#Module(includes = [RepositoryModule::class, NetworkModule::class, AppModule::class])
class ManagersModule {
...
#Singleton
#Provides
fun provideSyncFactory(context: Context, accountsRepository: AccountsRepository, messagesRepository: MessagesRepository) : SyncFactory {
return SyncFactory(context, accountsRepository, messagesRepository)
}
...
}
The SyncFactory class is like below
class SyncFactory #Inject constructor(
private val context: Context,
private val accountsRepository: AccountsRepository,
private val messagesRepository: MessagesRepository
) {
fun getAccounts(){....}
and i also have an interface
#Singleton
#Component(modules = [ViewModelsModule::class, DatabaseModule::class, RepositoryModule::class, AppModule::class, NetworkModule::class, ManagersModule::class])
interface ViewModelComponent {
fun inject(viewModels: ViewModels)
fun inject(firebaseService: AppFirebase)
}
And finally inside my firebase service i Inject the SyncFactory
class AppFirebase : FirebaseMessagingService(), SyncFactoryCallback {
#Inject
lateinit var syncFactory: SyncFactory
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
// lateinit property syncFactory has not been initialized
syncFactory.getAccounts()
}
And when my service gets called i get a lateinit property syncFactory has not been initialized exception.
What do i do wrong..?
The solution is to implement a HasServiceInjector in your Application class
class MyApplication : Application(), HasServiceInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Service>
companion object {
private lateinit var instance: MyApplication
}
override fun serviceInjector(): AndroidInjector<Service> {
return dispatchingAndroidInjector
}
}

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()
}
}

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 {

Inject property into ViewModel using Dagger 2

I try to learn how to use Dagger 2. Please help with follow exception:
Exception:
UninitializedPropertyAccessException: lateinit property trips has not
been initialized
MainActivityViewModel:
class MainActivityViewModel : ViewModel() {
private lateinit var tripsLiveData: MutableLiveData<List<Trip>>
#Inject
lateinit var trips : List<Trip>
fun getTrips() : LiveData<List<Trip>> {
if (!::tripsLiveData.isInitialized){
tripsLiveData = MutableLiveData()
tripsLiveData.value = trips
}
return tripsLiveData
}
}
TripModule:
#Module
class TripModule{
#Provides
fun provideTrips(): List<Trip> {
var list = ArrayList<Trip>()
list.add(Trip(100,10))
list.add(Trip(200,20))
return list
}
}
AppComponent:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBuilder::class,
TripModule::class])
interface AppComponent{
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MyApplication)
}
MainActivity:
class MainActivity : AppCompatActivity() {
#Inject
lateinit var tripsAdapter: TripsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
// Inject external dependencies
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupRecyclerView();
setUpViewModel();
}
private fun setupRecyclerView() {
recycler_view.apply {
layoutManager = LinearLayoutManager(context)
adapter = tripsAdapter
}
}
private fun setUpViewModel(){
val model = ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
model.getTrips().observe(this, Observer { tripsAdapter.trips = it!! })
}
}
If you want your viewmodel's to be part of the dagger graph, you need to do several things - using dagger's multibindings (just once, for newer viewmodels it will be easier). You'd create new viewmodel factory which will take care of instantiating viewmodels. This factory will be part of dagger graph and therefore will have references to anything provided via dagger. You can then have either constructor injection via #Inject constructor(anyParameterFromDagger: Param) or #Inject lateinit var someParam: Param inside the body of viewmodel.
1) Create qualifier for view model classes
#MustBeDocumented
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
2) create viewmodel factory which takes values from dagger's multibindings
#Singleton
class DaggerViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("unknown model class $modelClass")
}
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
3) have dagger module which will provide the factory (from point 2) and then your viewmodels
abstract class YourDaggerModuleWhichThenNeedToBePartOfYourGraphAsIncluded {
#Binds
abstract fun bindViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory // this needs to be only one for whole app (therefore marked as `#Singleton`)
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
abstract fun bindMainActivityViewModel(vm: MainActivityViewModel): ViewModel // for every viewmodel you have in your app, you need to bind them to dagger
}
4) in your activity, when you get your viewmodel, you need to use the factory from dagger: (places changed marked as // TODO in the code below)
class MainActivity : AppCompatActivity() {
#Inject
lateinit var tripsAdapter: TripsAdapter
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory // TODO this was added to the activity
override fun onCreate(savedInstanceState: Bundle?) {
// Inject external dependencies
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupRecyclerView();
setUpViewModel();
}
private fun setupRecyclerView() {
recycler_view.apply {
layoutManager = LinearLayoutManager(context)
adapter = tripsAdapter
}
}
private fun setUpViewModel(){
val model = ViewModelProviders.of(this, viewModelFactory)[MainActivityViewModel::class.java] // TODO this was changed
model.getTrips().observe(this, Observer { tripsAdapter.trips = it!! })
}
}
I didn't provide the code for including module to dagger component as I hope this is something you already did.
You can read more about this e.g. in this medium article (I'm not author of the article):
#Provides
fun provideTrips(): List<Trip> {
I'm a bit wary of this in the sense that I doubt that it's really Dagger's job to provide this for you directly, but I'll just take that for granted and ignore that for now.
Your code should be:
class MainActivityViewModel #Inject constructor(
trips: List<Trip>
): ViewModel() {
private val tripsLiveData: MutableLiveData<List<Trip>> = MutableLiveData()
init {
tripsLiveData.setValue(trips)
}
fun getTrips() : LiveData<List<Trip>> = tripsLiveData
}
#Module
class TripModule{
#Provides
// #Singleton // <-- possibly should be here?
fun provideTrips(): List<Trip> = listOf(
Trip(100,10),
Trip(200,20)
)
}
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBuilder::class,
TripModule::class
])
interface AppComponent{
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MyApplication)
}
class MainActivity : AppCompatActivity() {
#Inject
lateinit var viewModelProvider: Provider<MainActivityViewModel>
// this is specifically not marked with `#Inject`
lateinit var viewModel: MainActivityViewModel
private val tripsAdapter = TripsAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
// Inject external dependencies
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupRecyclerView();
setUpViewModel();
}
private fun setupRecyclerView() {
recycler_view.apply {
layoutManager = LinearLayoutManager(context)
adapter = tripsAdapter
}
}
private fun setUpViewModel(){
viewModel = ViewModelProviders.of(this, object: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if(modelClass == MainActivityViewModel::class.java) {
#Suppress("UNCHECKED_CAST")
return viewModelProvider.get() as T
}
throw IllegalArgumentException("Unexpected argument: $modelClass")
}
}).get(MainActivityViewModel::class.java)
viewModel.getTrips().observe(this, Observer {
val trips = it ?: return#observe
tripsAdapter.trips = trips
})
}
}
Aka you should use constructor injection, use the Provider<T> to only get the ViewModel from Dagger when you actually need it, and otherwise initialize it via a ViewModelProviders.Factory so that you actually get the ViewModel from Dagger.

Categories

Resources