android / codelab-android-dagger Goto Github PK
View Code? Open in Web Editor NEWLicense: Apache License 2.0
License: Apache License 2.0
I am new to using Hilt/Dagger,
But I have noticed that when I try to create a module using the step 7 for reference, I get the error..
Module must also be annotated with @Installin.
When annotate with @Installin(value = [ApplicationComponent::class])
All is well.
In this section: https://developer.android.com/codelabs/android-dagger#14
Please explain in detail about:
What does @retention(AnnotationRetention.BINARY) do and what does @retention(AnnotationRetention.BINARY) mean?
"They can be documented" ==> What does it mean?
By almost the end of step 10 we have the text:
"Instead, for the RegistrationActivity to create instances of RegistrationComponent, we need to expose its Factory out in the AppComponent interface."
But I guess instead "RegistrationActivity" you mean "MainActivity".
Nice job btw.
On this step here there is the following note:
Note: If you get an error saying that DaggerAppComponent doesn't exist in the project. It's because you have to build the project so the Dagger annotation processor can generate the code.
It should be:
Note: If you get an error saying that DaggerAppComponent doesn't exist in the project., it's because you have to build the project so the Dagger annotation processor can generate the code.
https://developer.android.com/codelabs/android-dagger#3
First code snapshot on that page still includes id 'kotlin-android-extensions', which give the following mistake:
The 'kotlin-android-extensions' Gradle plugin is no longer supported. Please use this migration guide (https://goo.gle/kotlin-android-extensions-deprecation) to start working with View Binding (https://developer.android.com/topic/libraries/view-binding) and the 'kotlin-parcelize' plugin.
Could you add work manager and the standard way to inject dependencies to Worker?
In the 8. Injecting the graph into an Activity
MainActivity.kt
class MainActivity : AppCompatActivity() {
// @Inject annotated fields will be provided by Dagger
@Inject
private lateinit var userManager: UserManager
@Inject
private lateinit var mainViewModel: MainViewModel
...
}
but Dagger does not support injection into private fields
In MainActivity private fields are annotated @Inject. Dagger does not supported injection private fields
In section #8 we are injecting MainActivity in AppComponent as following
(application as MyApplication).appComponent.inject(this)
which has to be removed in section #13 when updating main activity.
This piece of instruction is missing from the code lab.
Reference : https://developer.android.com/codelabs/android-dagger#12
Hi Manuel, how are you?
On the Android developers documentation it says:
https://developer.android.com/training/dependency-injection/dagger-android#injecting-activities
"When using fragments, inject Dagger in the fragment's onAttach() method. In this case, it can be done before or after calling super.onAttach()."
On the codelab about dagger it says:
https://developer.android.com/codelabs/android-dagger#9
"A Fragment injects Dagger in the onAttach method after calling super."
Which one is correct?
Would you please let me know, and correct where it is wrong, please?
Thanks a lot for the great material that you built up about Dagger.
Best regards,
Rodrigo
After adding @componet, I am getting issues.
Execution failed for task ':app:kaptDebugKotlin'.
A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction
java.lang.reflect.InvocationTargetException (no error message)
Run with --info or --debug option to get more log output.
Run with --scan to get full insights.
Please tell me where am I doing wrong.
In step 10, below the block Important- Best Practices
, the sentence
"RegistrationViewModel it's already annotated with @Inject, RegistrationActivity needed it before."
might need to be reviewed for grammatical errors.
I'm not an expert but the sentence can be:
RegistrationViewModel is already annotated with @Inject, since RegistrationActivity needed it before.
Under Using Dagger in the Main Flow of Using Dagger in your Android app - Kotlin (Part 8)
Point 3 states "Inject MainActivity
in appComponent
to populate the injected fields."
It should state "Inject appComponent
in MainActivity
to populate the injected fields."
The following code from the codelab puzzled me because the private function userJustLoggedIn()
was not called from anywhere.
@Singleton
class UserManager @Inject constructor(...) {
//Remove line
var userDataRepository: UserDataRepository? = null
var userComponent: UserComponent? = null
private set
fun isUserLoggedIn() = userComponent != null
fun logout() {
userComponent = null
}
private fun userJustLoggedIn() {
userComponent = userComponentFactory.create()
}
}
After checking the actual code, I realized that the above code was slightly oversimplified. It might be nicer if simplification was implied in the explanation (e.g. use an ellipsis // …
).
Add separate branch implementing optional step from codelab:
These steps don't contain comments or code, so try it on your own:
Create a SplashActivity with a SplashViewModel that handles the logic of what screen to display.
As we've been doing, use dependency injection in SplashActivity to get fields injected by Dagger.
Remove the logic in the onCreate method of the MainActivity.kt since when the Activity is opened, the user will be logged in.
original:
that'll make that typehave a unique instance in the Component.
should be:
that'll make that type have a unique instance in the Component.
In step 7 it goes into detail about how to provide a context
it tells us...
The way to pass it in is with a Component Factory and using the @BindsInstance annotation.
If I build straight after this i get the error...
android.content.Context cannot be provided without an @Provides-annotated method.
To fix this issue I annoted property in the class that context is used like so..
class SomeClass @Inject constructor( @ApplicationContext private val context: Context )
Near the end of chapter 7 in the Dagger codelab, there is a sentence that reads:
Storage
has a dependency on Context but since we're providing it when we create the graph,Storage
has all its dependencies covered".
Storage
should be replaced with SharedPreferencesStorage
in that sentence
I think this code lab could benefit from showing how to use Dagger with an Androidx ViewModel and ViewModelFactory.
set Content View as R.layout.activity_settings in MainActivity.kt
also userManager initialization should be before super.onCreate(..) method call.
Field doesn't define in the RegistrationActivity class in
https://developer.android.com/codelabs/android-dagger#10
The registrationComponent filed not defined as a class field.
It may be confusable
Could you change from
class RegistrationActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
to
class RegistrationActivity : AppCompatActivity() {
lateinit var registrationComponent: RegistrationComponent
...
override fun onCreate(savedInstanceState: Bundle?) {
in step 7 there is an injection annotation on a private member
`class MainActivity : AppCompatActivity() {
// @Inject annotated fields will be provided by Dagger
@Inject
private lateinit var userManager: UserManager
@Inject
private lateinit var mainViewModel: MainViewModel
...
}
class MainViewModel @Inject constructor(private val userDataRepository: UserDataRepository) { ... }
class UserDataRepository @Inject constructor(private val userManager: UserManager) { ... }`
Isn't it supposed for the annotation not to be on a private member
This paragraph is stated (truthfully): In AppComponent, we have to remove the methods that can inject registration view classes because these won't be used anymore, those classes will use the RegistrationComponent.
But, it'd be helpful to the student to be told explicitly to remove these lines from AppComponent:
fun inject(activity: RegistrationActivity)
fun inject(fragment: EnterDetailsFragment)
fun inject(fragment: TermsAndConditionsFragment)
Furthermore, it'd probably be helpful to add a gotcha warning in the next section of the tutorial ("Scoping Subcomponents"), warning them that if they failed to remove those injector calls from AppComponent they will get this error:
/Users/dg891e/repos/googlecodelabs/android-dagger/app/build/tmp/kapt3/stubs/debug/com/example/android/dagger/di/AppComponent.java:8: error: [Dagger/IncompatiblyScopedBindings] com.example.android.dagger.di.AppComponent scoped with @Singleton may not reference bindings with different scopes:
public abstract interface AppComponent {
^
@com.example.android.dagger.di.ActivityScope class com.example.android.dagger.registration.RegistrationViewModel
First,Thanks for your nice work on this project,I like it
but
What confuse me is that I can hardly see Dagger in many google's android project.
I(maybe many others) want to know why for this,if it is recommend to use Dagger in Android Project
Looking forward to someone to solve my doubts,and thanks for that
Step 8 -> Using Dagger in the Main Flow -> 2.
Error when building:
e: /Users/lazarristic/PersonalWorkspace/Android/Learning/Dagger/android-dagger/app/build/tmp/kapt3/stubs/debug/com/example/android/dagger/di/AppComponent.java:7: error: [Dagger/MissingBinding] android.os.UserManager cannot be provided without an @Inject constructor or an @Provides-annotated method. public abstract interface AppComponent { ^ android.os.UserManager is injected at com.example.android.dagger.main.MainActivity.userManager com.example.android.dagger.main.MainActivity is injected at com.example.android.dagger.di.AppComponent.inject(com.example.android.dagger.main.MainActivity)
Android Studio has imported android.os.UserManager
instead of com.example.android.dagger.user.UserManager
Please add imports in code.
I think the settings button id should be @+id/settings
instead of @+id/logout
😃
Important - Best practices
An Activity injects Dagger in the onCreate method before calling super.
A Fragment injects Dagger in the onAttach method after calling super.
https://codelabs.developers.google.com/codelabs/android-dagger/#9
Constructor injection is preferred whenever possible because javac will ensure that no field is referenced before it has been set, which helps avoid NullPointerExceptions. When members injection is required (as discussed above), prefer to inject as early as possible. For this reason, DaggerActivity calls AndroidInjection.inject() immediately in onCreate(), before calling super.onCreate(), and DaggerFragment does the same in onAttach(), which also prevents inconsistencies if the Fragment is reattached.
https://dagger.dev/dev-guide/android.html
The content of the codelab and the content of the Dagger's document conflict.
There are several related PRs:
https://github.com/google/dagger/pull/603/files
https://github.com/google/dagger/pull/605/files
The order of super.onAttach () calls in the Dagger documentation has changed and I think this is the best practice.
class MainActivity : AppCompatActivity() {
// 1) Remove userManager field
@Inject
lateinit var userManager: UserManager
@Inject
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings) <<<<<<<<<<<<< Not Supposed to be here <<<<<<<<<<
// 2) Grab userManager from appComponent to check if the user is logged in or not
val userManager = (application as MyApplication).appComponent.userManager()
if (!userManager.isUserLoggedIn()) { ... }
else {
setContentView(R.layout.activity_main)
// 3) If the MainActivity needs to be displayed, we get the UserComponent
// from the application graph and gets this Activity injected
userManager.userComponent!!.inject(this)
setupViews()
}
}
...
}
Update buildTools, gradle and other dependencies to the latest versions
At the end of text:
Your project should now build with no errors.. Dagger has generated the application graph successfully and you're ready to use it.
It should be:
Your project should now build with no errors. Dagger has generated the application graph successfully and you're ready to use it.
https://developer.android.com/codelabs/android-dagger#9
Dagger Subcomponents
A RegistrationComponent must be able to access the objects from AppComponent since RegistrationViewModel depends on UserRepository.
But, actually, if we refer to a scheme of application graph from this codelabs, then we figure out that RegistrationViewModel depends on UserManager
First of all, I would like to thank you for the great codelab, beautiful explanation. Even as an experienced user of dagger I learned a lot from it 👏👌
There is this extra line, which I think should not be there at the bottom of the tab Multiple Activities With Same Scope
as we are in the MainActivity
, setContentView()
is setting a view of activity_settings
I am attaching a screenshot for reference.
Thank you!
Both main and registration flow are getting UserManager injected from the application graph even after completing 1_registration_main
Step @component describes the need for an #inject method to be used by the RegistrationActivity, but omits to call that method from the #onCreate method of that activity. The student should be told to add
(application as MyApplication).appComponent.inject(this)
before anything else happens, in #onCreate.
If you get this error:
Caused by: androidx.test.espresso.InjectEventSecurityException: java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
Then,
In the ApplicationTest file:
Replace:
onView(withId(R.id.password)).perform(typeText("password"), closeSoftKeyboard())
with:
onView(withId(R.id.password)).perform(replaceText("password"), closeSoftKeyboard())
(two places in the file)
TestStorageModule
from androidTest
shows an unresolved reference error for FakeStorage
on the solution branch, even though the instrumented tests run successfully.
FakeStorage
is located at the sharedTest
package, and this package is shared with tests through sourceSets
, but it looks like sharing code this way is not supported anymore, according to this comment on Google Issue Tracker.
sourceSets {
String sharedTestDir = 'src/sharedTest/java'
test {
java.srcDir sharedTestDir
}
androidTest {
java.srcDir sharedTestDir
}
}
A suggestion found on the same comment on Google Issue Tracker is to create a new module with the code that needs to be shared and add it as a dependency for testImplementation
and androidTestImplementation
.
testImplementation project(path: ':shared-test')
androidTestImplementation project(path: ':shared-test')
Android Studio version:
Android Studio Giraffe | 2022.3.1 Patch 2
Build #AI-223.8836.35.2231.10811636, built on September 14, 2023
Step 8. Grammar error in line "The name of the functions don't matter".
To fix the below error which occurs when running unit tests that have a dependency with the UserManager
class, we need to update Mockito to a version higher than 5.0.0 because mocking final classes feature is enabled by default. In my case updating Mockito to version 5.3.1 fixed the issue.
Solution:
testImplementation 'org.mockito:mockito-core:5.3.1'
Problem:
Mockito cannot mock this class: class com.example.android.dagger.user.UserManager.
Can not mock final classes with the following settings :
- explicit serialization (e.g. withSettings().serializable())
- extra interfaces (e.g. withSettings().extraInterfaces(...))
You are seeing this disclaimer because Mockito is configured to create inlined mocks.
You can learn about inline mocks and their limitations under item #39 of the Mockito class javadoc.
Underlying exception : org.mockito.exceptions.base.MockitoException: Could not modify all classes [class java.lang.Object, class com.example.android.dagger.user.UserManager]
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class com.example.android.dagger.user.UserManager.
Can not mock final classes with the following settings :
- explicit serialization (e.g. withSettings().serializable())
- extra interfaces (e.g. withSettings().extraInterfaces(...))
You are seeing this disclaimer because Mockito is configured to create inlined mocks.
You can learn about inline mocks and their limitations under item #39 of the Mockito class javadoc.
Underlying exception : org.mockito.exceptions.base.MockitoException: Could not modify all classes [class java.lang.Object, class com.example.android.dagger.user.UserManager]
at app//com.example.android.dagger.registration.RegistrationViewModelTest.setup(RegistrationViewModelTest.kt:32)
at [email protected]/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at [email protected]/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at [email protected]/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at [email protected]/java.lang.reflect.Method.invoke(Unknown Source)
at app//org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at app//org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at app//org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at app//org.junit.internal.runners.statements.RunBefores.invokeMethod(RunBefores.java:33)
at app//org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at app//org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at app//org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at app//org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at app//org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at app//org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at app//org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at app//org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at app//org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
at [email protected]/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at [email protected]/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at [email protected]/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at [email protected]/java.lang.reflect.Method.invoke(Unknown Source)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at jdk.proxy1/jdk.proxy1.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: org.mockito.exceptions.base.MockitoException: Could not modify all classes [class java.lang.Object, class com.example.android.dagger.user.UserManager]
at app//net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:152)
at app//net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:365)
at app//net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:174)
at app//net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:376)
... 45 more
Caused by: java.lang.IllegalStateException:
Byte Buddy could not instrument all classes within the mock's type hierarchy
This problem should never occur for javac-compiled classes. This problem has been observed for classes that are:
- Compiled by older versions of scalac
- Classes that are part of the Android distribution
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:237)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.mockClass(InlineBytecodeGenerator.java:198)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$1.call(TypeCachingBytecodeGenerator.java:46)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$1.call(TypeCachingBytecodeGenerator.java:43)
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:152)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:365)
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:174)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:376)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClass(TypeCachingBytecodeGenerator.java:36)
at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createMockType(InlineByteBuddyMockMaker.java:252)
at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createMock(InlineByteBuddyMockMaker.java:231)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:35)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:69)
at org.mockito.Mockito.mock(Mockito.java:1933)
at org.mockito.Mockito.mock(Mockito.java:1844)
... 45 more
Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 61
at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:196)
at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:177)
at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:163)
at net.bytebuddy.utility.OpenedClassReader.of(OpenedClassReader.java:86)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:3824)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2166)
at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:224)
at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:123)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3595)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.transform(InlineBytecodeGenerator.java:344)
at java.instrument/java.lang.instrument.ClassFileTransformer.transform(Unknown Source)
at java.instrument/sun.instrument.TransformerManager.transform(Unknown Source)
at java.instrument/sun.instrument.InstrumentationImpl.transform(Unknown Source)
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(Unknown Source)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:233)
... 59 more
In eight lesson (https://codelabs.developers.google.com/codelabs/android-dagger/#7) when we inject userManager and MainViewModel, these fields are private, but dagger doesn't support injection in private fields so I made them public
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.