Kotlin annotation processing ignores items with similar names - android

I recently converted the majority of my project to kotlin. Now I encounter several unusual errors that all seem to relate to annotation libraries. Needless to say, it didn't happen in Java.
I'll describe the cases - one in Dagger and one in Butterknife.
1.
When having 2 #Provides methods in different models with the same name.
For example in file "FooProvider.kt" having a "provideFooOrBar" method
#Module
class FooProvider(private val view: FooActivity) {
...
#Provides #FooScope fun provideView() = view
#Provides #FooScope fun provideFooOrBar() = Foo()
}
And having another file "BarProvider.kt" with the same method name
#Module
class BarProvider(private val view: BarActivity) {
...
#Provides #BarScope fun provideView() = view
#Provides #BarScope fun provideFooOrBar() = Bar()
}
In this case, Dagger fails to generate some factory libraries and I get the following compilation error:
Error:(27, 32) error: cannot find symbol class FooProvider_ProvideFooOrBarFactory
A sample project reproducing the issue can be found at https://github.com/maxandron/DaggerIssue325
2.
This is an issue when using Butterknife. When having two #Bind annotated variables in two different classes - One of them just fails to initialize at runtime without any compilation error!
For example if I have:
class FooActivity {
#Bind(R.id.foo) lateinit var mFoo: View
}
class NotFooActivity {
#Bind(R.id.not_foo) lateinit var mFoo: View
}
Then one of them (or both?) will just fail to initialize without any error. Causing a kotlin.UninitializedPropertyAccessException: lateinit property mFoo has not been initialized exception to be thrown when the field is accessed.
Is it something I'm doing wrong in configuring Kotlin or is it a kotlin bug?
Thank you in advance!
Ron

I was having this issue, so I started to investigate and it's caused because Kapt is only checking the method name when comparing them, and they are added in a set, thus duplicates are not allowed. The same happens for annotated fields, so currently you can have one method/field name per annotation.
I added the class name to the equals method and the annotations were properly handled now, but the tests broke and I don't know how they work, so I hope someone knows how to fix this.

It turned out to be a bug with kapt.
I posted an issue on Kotlin's bug tracker and the issue is now marked as fixed.
This solution was merged
Should be resolved in Kotlin version 1.0.2

So to somewhat answer the kotlin.UninitializedPropertyAccessException: lateinit issue, I was running into the exact same thing in my project. What I did which "solved the issue" for me was to remove Butterknife from the offending class, in this case it was just a viewHolder for my new expandable RecyclerView, and then run the app again.
Running the app, after switching all my #Bind(R.id.my_view_id) to the "old school" findViewById(R.id.my_view_id) as MyViewType worked, but then subsequently afterwards I switched the same class back to Butterknife and the UninitializedPropertyAccessException went away, and it seems like it won't come back unless something in the class changes, then you'll have to repeat this process again.
My suspicion here is that this has something to do with Kotlin not supporting incremental compilation, and somehow by changing the auto-generated code it was forced to recompile. But I could be way off here, just thought I'd share my experience.

Related

How to use multiple scopes in a #RestrictTo annotation

I'm using #RestrictTo annotation to denote that a function should be used in only in subclasses or tests.
To do that I use the following syntax:
#RestrictTo(value = [SUBCLASSES, TESTS])
public override fun onCleared() {
// Expose protected fun onCleared for tests
}
At first it seemed to be working but my teammates reported Android Studio showing this warning:
I could reproduce this after building the project again.
This error goes away if I remove the TESTS scope from the annotation as if the annotation does not support multiple scopes in values.
Do you think if this is the intended behavior of the annotation?
Can you think another way to restrict a function to the union of two different scopes?
Thanks in advance

Kotlin reflection fails to return function after update to version 1.6.10

I have the following kotlin property declared in a class of my Android app:
private val updateRef: KFunction<*> by lazy {
dao::class.functions.find {it.name.equals("update", true)}!!
}
Where dao is a reference to a Room DAO interface. Since I've updated kotlin to version 1.6.10 it doesn't work anymore, the following wierd exception is thrown every time I try to execute the code above. The same exception is thrown when I evaluate the expression using Android Studio's EVALUATE tool:
"Incorrect resolution sequence for Java method public open suspend fun count(): kotlin.Int defined in it.kfi.lorikeetmobile.db.dao.TablePriorityDao_Impl[JavaMethodDescriptor#d45ec9a]".
Where count() is a suspend method declared in the DAO interface. But I get this for every DAO class I have in my project and for different methods, so I think the method has nothing to do with the real problem here... I cannot figure out what is happening.
Before the update I had no problems at all. Please help.
In the end I discovered that the last version of kotlin-reflect (v. 1.6.10 in my case) doesn't keep backward compatibility for Kotlin classes that extend Java super-classes. Using Kotlin reflection to access member properties or functions of a Java super-class extended by a Kotlin class will fail as described in my question. It works fine if also the super-class is a Kotlin class. That is for me a bug that should be addressed.

Combining `by lazy` and `object` results in compiler-error "cannot find symbol"

I cannot compile anymore after the update to Kotlin 1.3.0 (works in 1.2.71) when trying to use by lazy and object. This seems to happen only on my project. A demo-project is working fine.
I want to add an interface to a given class and lazy-load its values.
I've created a small example which is not working in my project but working fine in any other:
open class Foo
interface Bar {
val lazyLoadedString : String
}
class Test {
private val foo by lazy {
object : Foo(), Bar {
override val lazyLoadedString = "Demo"
}
}
}
As soon as I combine object and by lazy, it cannot compile anymore and shows the following error. Using each one alone works.
Test.java:9: error: cannot find symbol
private final my.package.Test$foo$2$1 getFoo()
symbol: class Test$foo$2$1
location: package my.package
When looking closer, you'll see that the generated java file shows this error and not the kotlin-code.
Any ideas on this?
It looks like kapt is broken in Kotlin 1.3.0 for this particular kind of code.
In the code above, it was the annotation processor registered by Realm that triggered it, but any other annotation processor would have resulted in the same error.
The issue is being tracked here: https://youtrack.jetbrains.net/issue/KT-28053

Dagger and databinding

I have a MVVM project where I have ViewModel classes extending BaseObservable. Now if put #Inject class in my ViewModel then compilation fails with many errors like: "error: package xxx.databinding does not exist"
Can I find the actual error that's causing this using some gradle technique? Also is #Inject really supported with databinding?
Edit:
Code is exactly the same as https://github.com/googlesamples/android-architecture/tree/todo-mvvm-databinding/
In that I have added dagger and I'm trying to #Inject a repository into a view model that extends BaseObservable. As soon as I add #Inject into the view model then I cant compile
The general approach to fixing this kind of problem is to find the errors that are not tied to databinding. Once those are fixed, your databinding errors will go away. Databinding just complains loudly because the build failed before it could do its thing. Unfortunately this often feels like finding the needle in the haystack.
If you have a lot of errors you may need to increase the maximum error count displayed, as otherwise the error output may end before it prints the actual root cause. See here: https://stackoverflow.com/a/35707088/436417
Dagger's #Inject is compatible with databinding in general.
Dagger works with data binding, you have something wrong in your setup.
When you get error: package xxx.databinding does not exist it means that code generation failed, and since both data binding and dagger use code generation problem might be in the setup of both components.
Based on your description it looks like you have not configured dagger properly, i.e. not set up how it should provide the object you are injecting.
Make sure that you did the actions under "satisfying dependencies" and "building the graph" from here https://google.github.io/dagger//users-guide.html
Like Uli mentioned, this is due to the number of displayed errors being limited by the compiler.
Do this:
1. Increase the displayed error limit by doing the following
Add this snippet in your submodule gradle file inside the android block.
kapt {
javacOptions {
// Increase the max count of errors from annotation processors.
// Default is 100.
option("-Xmaxerrs", 1000)
}
}
2. Find the errors which are not binding related and fix them.
i.e (Fix the errors from app/src/.. folders and ignore the ones from app/build/generated/.. which are binding related)
Check this thread and this comment for more info.

Dagger 2.10 subcomponent generator - Injector validation fails

I'm trying to create an annotation processor which will process my MVP views (fragments) to auto-generated Subcomponents (similar to https://github.com/lukaspili/Auto-Dagger2, but for the new Dagger 2.10 android injectors)
So far, I've been able to generate appropriate files, but there is a strange error message when compiling generated components
Error:(22, 58) error: #dagger.android.support.FragmentKey methods should bind dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>, not dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>. See google.github.io/dagger/android
The structure of Factory module and Subcomponent files should be correct, because as soon as I copy-paste the generated classes and create a regular classes (both Factory module and Subcomponent) and use real classes instead of generated ones, the message is no longer shown and compilation succeeds
It seems like the problem lies in AndroidMapKeyValidator (link), where !MoreTypes.equivalence().equivalent(returnType, intendedReturnType) call apparently fails, but I don't have much of an experience debugging annotation processors, so I don't know why precisely...
Can maybe anyone help where to search for the problem?
Thanks
FYI: MyFragment does extend android.support.v4.app.Fragment
My files:
Generated Factory
#Module
public interface BuildersModule {
#Binds
#IntoMap
#FragmentKey(MyFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> factory(MySubcomponent.Builder builder);
}
Generated subcomponent
#Subcomponent(modules = MyModule.class)
public interface MySubcomponent extends AndroidInjector<MyFragment> {
MyPresenter presenter();
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MyFragment> {}
}
If anyone is interested in solution:
I found out that for some reason, references of ClassType-s compared during project's compile time are not the same when validating generated method.
And these references, despite the fact that they are pointing to the same class, are checked for equality in auto-common library in EqualVisitor.visitDeclared method. Apparently, this can be a bug in auto-common, because Elements in visitDeclared are compared by object reference, but not type reference.
So workaround here is to use local fixed copy of auto-common library and exclude all dependencies of the original library.
//TODO think if this is the correct solution to cast both elements
//return aElement.equals(bElement)
return ((TypeElement) aElement).getQualifiedName().equals(((TypeElement) bElement).getQualifiedName())
&& equal(a.getEnclosingType(), b.getEnclosingType(), newVisiting)
&& equalLists(a.getTypeArguments(), b.getTypeArguments(), newVisiting);
I still have to check why those references are not the same, and I have to think how the equality check can be fixed properly in auto-common (I use just a quickfix) before filing an issue in auto-common repo.

Categories

Resources