Implementing a simple Dagger2 sample - android

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

Related

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 {

Why I implement a Factory in this code with Dagger2 not working?

I excuse a simple code about dagger in kotlin but it not working in runtime. What is wrong and how to update this code?
I have a Notification class and I want to execute 'runNotification'.
interface Notifications {
fun runNotification()
}
class NotificationsImpl #Inject constructor(
private val context: Application
) : Notifications {
override fun runNotification() {
Log.e("Notifications", "runNotification")
}
}
NotificationModule.kt
#Module
class NotificationModule {
#Provides
#Singleton
fun providerNotifications(notificationsImpl: NotificationsImpl): Notifications = notificationsImpl
}
ApplicationComponent.kt
#Singleton
#Component(modules = [NotificationModule::class])
interface ApplicationComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
#BindsInstance
fun notificationModule(notificationModule: NotificationModule): Builder
fun build(): ApplicationComponent
}
fun inject(application: Application)
}
Code in Application
DaggerApplicationComponent.builder()
.application(this)
.notificationModule(NotificationModule())
.build()
.inject(this)
Code Test(this code 'notifications' return is NULL):
class MainActivity : AppCompatActivity() {
#Inject
lateinit var notifications: Notifications
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
notifications.updateNotification()
}
}
Gradle:
implementation "com.google.dagger:dagger:2.17"
kapt "com.google.dagger:dagger-compiler:2.17"
implementation "com.google.dagger:dagger-android:2.17"
kapt "com.google.dagger:dagger-android-processor:2.17"
kapt "com.google.dagger:dagger-android-support:2.17"

Android UI testing for MVVM architecture with Dagger2, Espresso and Mockito

I'm trying to add UI tests for the first time to an app, and I was looking at using espresso.
The app uses Dagger2 for DI with #Inject annotations for classes that should be injectable, and AndroidInjection/AndroidSupportInjection in screens (Activity / Fragment).
class Application : android.app.Application(), HasActivityInjector, HasServiceInjector {
...
override fun onCreate() {
super.onCreate()
initDagger()
Timber.d("Application initialized successfully!")
}
protected open fun initDagger() {
Components.initialize(this)
}
}
object Components : ComponentFactory {
private lateinit var sComponent: AppComponent
fun initialize(app: Application) {
sAppComponent = DaggerAppComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
}
// overrides
}
interface ComponentFactory {
fun app(): AppComponent
fun authenticated(): AuthenticatedComponent
}
Next the Components and Modules. The AppActivitiesModule and AuthenticatedActivitiesModule are classes with #ContributesAndroidInjector for screens.
#Singleton
#Component(modules = [AppModule::class, AppActivitiesModule::class, AndroidInjectionModule::class, AndroidSupportInjectionModule::class])
interface AppComponent {
fun authenticatedComponentBuilder(): AuthenticatedComponent.Builder
fun inject(app: Application)
#Component.Builder
interface Builder {
fun build(): AppComponent
fun applicationModule(applicationModule: ApplicationModule): Builder
}
}
#Module
open class AppModule(private val application: Application) {
// some #Provides
}
#AuthenticatedScope
#Subcomponent(modules = [AuthenticatedModule::class, AuthenticatedActivitiesModule::class])
interface AuthenticatedComponent {
fun inject(application: Application)
#Subcomponent.Builder
interface Builder {
fun userModule(module: UserModule): Builder
fun build(): AuthenticatedComponent
}
}
#Module
class AuthenticatedModule(private val userId: Long,
private val userRole: User.Role) {
// Some #Provides #AuthenticatedScope
}
And a typical use case would be:
#Singleton
class AppLevelService
#Inject constructor(...) { ... }
#AuthenticatedScope
class AuthenticatedLevelServices
#Inject constructor(...) { ... }
class ViewModel
#Inject constructor(private val appService: AppLevelService,
private val authService: AuthenticatedLevelServices) { ... }
class MyActivity : BaseActivity {
#Inject
lateinit var vmProvider: Provide<ViewModel>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
}
}
How can I make the test setup for this "type" of dagger usage?
I find a lot of examples of testing with dagger2 but for #Provides annotations, and I'm thinking there should be a way to mock injectable classes with #Inject.
I've tried DaggerMock but I get:
You must define overridden objects using a #Provides annotated method instead of using #Inject annotation
Not that it should affect anything, but I'm also using a custom Runner with DexOpener.
Any ideas or good documentation / examples for testing this setup?

Android Kotlin with Dagger2, lateinit var has not been initialized

I'm a Dagger newb and have a trouble with using it.
What I want to develop is that using RxAndroidBle and to initialize it by Dagger for providing Context.
So I researched how it can be implemented, and I wrote some codes and It seems to be working for me but not working at all.
The followings are my codes.
AppComponent.kt
#Singleton
#Component(modules = [
AppModule::class,
BluetoothModule::class,
AndroidInjectionModule::class])
interface AppComponent : AndroidInjector<BluetoothController> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
AppModule.kt
#Module
class AppModule {
#Provides
#Named("appContext")
#Singleton
fun provideContext(application: Application): Context =
application.applicationContext
}
BluetoothModule.kt
#Module
class BluetoothModule {
#Provides
#Named("rxBleClient")
#Singleton
fun provideRxBleClient(#Named("appContext") context: Context):RxBleClient =
RxBleClient.create(context)
}
BluetoothController.kt for injecting by DaggerApplication.
class BluetoothController : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this).build()
}
}
I've inserted
android:name".BluetoothController"
to AndroidManifest.xml
And this is how I would use it.
#field:[Inject Named("rxBleClient")]
lateinit var rxBleClient: RxBleClient
But it always occurs an error says: lateinit property context has not been initialized
What things I've missed? Can anyone help me?
Thanks in advance.
Add the below code to make this happen.
Create ActivityBuilderModule for injecting within the activity. Consider our activity as MainActivity
#Module
abstract class ActivityBuilderModule {
#ContributesAndroidInjector(modules=[MainActivityModule::class])
abstract fun contributeSplashActivity(): MainActivity
}
Create your MainActivityModule
#Module
class MainActivityModule{
#Provides()
fun contributeSplashActivity(mainActivity: MainActivity):
MainActivity=mainActivity
}
Modify your component.
#Singleton
#Component(modules = [
AppModule::class,
BluetoothModule::class,
ActivityBuilderModule::class,
AndroidInjectionModule::class])
interface AppComponent : AndroidInjector<BluetoothController> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
Within MainActivity just inject.
class MainActivity{
...
#Inject
lateinit var rxBleClient: RxBleClient
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
}
Let us know in case of any issue.
the context object is not initialized before its called
Though i dont know, how chained your initialization are.
Use #Inject to add deppendencies, do something like this
#Module
class BluetoothModule(val context : Context) {
//#Inject private lateinit var context : Context
#Provides
#Named("rxBleClient")
#Singleton
fun provideRxBleClient():RxBleClient =
RxBleClient.create(context)
}
let your call be like this
val component = AppComponent()
component.bluetoothModule(appContext)
.//other calls here
.build()

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