Dagger2 - How to use #Named with #BindsInstance - kotlin

How is #Named used with #BindsInstance? I have the this component
interface AppComponent : AndroidInjector<MyApplication>{
#Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApplication>() {
#BindsInstance
abstract fun preferenceName( #Named("PreferenceName") name : String ) : Builder
}
}
and trying to inject in MyApplication
#Inject
#Named("PreferenceName")
lateinit var prefName : String
But it fails with MissingBinding for String. I could resolve this with a module provider but trying to avoid provider for constants.

Update: Dagger 2.25.2 has eliminated the need for workaround:
Kotlin support
ii. Qualifier annotations on fields can now be understood without
The need for #field:MyQualifier (646e033)
iii. #Module object classes no longer need #JvmStatic on the
provides methods. (0da2180)
This doesn't have anything to do with #BindsInstance, but rather the #Named annotations on fields. You can tell from the "MissingBinding for String", which would otherwise give you an error about a Named string.
As in Svetlozar Kostadinov's article Correct usage of Dagger 2 #Named annotation in Kotlin, you'll need to clarify to Kotlin that you'd like the annotations to apply to the field.
#field:[Inject Named("PreferenceName")]
lateinit var prefName : String;
As Svetlozar puts it:
The reason is because in Kotlin annotations need to be slightly more complicated in order to work as expected from Java perspective. That’s coming from the fact that one Kotlin element may be a facade of multiple Java elements emitted in the bytecode. For example a Kotlin property is a facade of an underlying Java member variable, a getter and a setter. You annotate the property but what Dagger expects to be annotated is the underlying field.
Related: Dagger 2 constructor injection in kotlin with Named arguments

Related

Does Dagger support multibinding with KClass<*> type?

I have a KeyMap like this:
#Target(AnnotationTarget.FUNCTION)
#MapKey
annotation class JsonSerializerKey(val value: KClass<*>)
and want to provide them as:
// #1 doesn't work
Map<KClass<*>, #JvmSuppressWildcards KSerializer<*>>
// #2 works
Map<Class<*>, #JvmSuppressWildcards KSerializer<*>>
Dagger can't find and collect provided items when requesting KClass (with below error) but it works with Class.
error: [Dagger/MissingBinding] java.util.Map<kotlin.reflect.KClass<?>,kotlinx.serialization.KSerializer<?>> cannot be provided without an #Provides-annotated method.
How can I achieve map of KClass in dagger multibinding?
P.S: All provided JsonSerializerKey items are written in Kotlin.

Kotlin parcelize issue with gson

I am using #parcelize for gson
Here is my class
#Parcelize
data class CommunityModel(#SerializedName("post") val post: PostModel,
#SerializedName("is_liked") val isLiked: Boolean,
#SerializedName("post_like") val postLike: QuestionModel,
#SerializedName("polling_options") val pollingOptions: List<PollingModel>,
#SerializedName("post_polled") val postPolled: Boolean) : Parcelable
I got error Unable to invoke no-args constructor for class. Register an InstanceCreator with Gson for this type may fix this problem..
But this error only presents on older android versions (below 5.0)
I tried implementing default constructor :
constructor: this(PostModel(), true, QuestionModel(), emptyList(), true)
But it gave me java.lang.VerifyError instead
I am using retrofit2 with rxjava2 and gson converter Version 2.3
My kotlin version is 1.1.51
Is it known bug? Or did I do something wrong?
No-arg compiler plugin
The no-arg compiler plugin generates an additional zero-argument constructor for classes with a specific annotation.
The generated constructor is synthetic so it can’t be directly called from Java or Kotlin, but it can be called using reflection.
This allows the Java Persistence API (JPA) to instantiate the data class although it doesn't have the zero-parameter constructor from Kotlin or Java point of view (see the description of kotlin-jpa plugin below).
Using in Gradle
The usage is pretty similar to all-open.
Add the plugin and specify the list of annotations that must lead to generating a no-arg constructor for the annotated classes.
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
}
}
apply plugin: "kotlin-noarg"
Source https://kotlinlang.org/docs/reference/compiler-plugins.html

Kotlin default arguments in interface bug?

kotlin file
interface Test {
fun test(message: String, delay: Int =100)
}
class A: Test
{
override fun test(message: String, delay: Int) {
}
}
I find i can't use #JvmOverloads in interface nor class.
if i add a #JvmOverloads in interface,the error is #JvmOverloads annotation cannot be used on interface method,if i add #JvmOverloads in class,the error is platform declaration clash....
However, I seem able to use defaults paramters in kotlin files,like this.
var a=A()
a.test("1234")
But when I use it in a java file, it seems that the method is not overloaded。
A a=new A();
a.test("123");//Compile error
The following version without interface can work
class A
{
#JvmOverloads
fun test(message: String, delay: Int=100) {
}
}
Then I can use it normally in java file
A a=new A();
a.test("123");
But how to maintain the same functionality after add the interface?
This is not a bug. #JvmOverloads annotation simply does not work with abstract methods.
From Kotlin docs:
Normally, if you write a Kotlin function with default parameter values, it will be visible in Java only as a full signature, with all parameters present. If you wish to expose multiple overloads to Java callers, you can use the #JvmOverloads annotation.
The annotation also works for constructors, static methods etc. It can't be used on abstract methods, including methods defined in interfaces.
source: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#overloads-generation
Why?
Because as You can learn from the doc I mentioned, #JvmOverloads instructs compiler to generate bunch of Java overloaded methods, omitting each of the parameters one by one, starting from the last one.
As far as I understand, each overloaded method calls internally method with one more parameter, and this additional parameter has default value. Edit: see comment by #hotkey here
This won't work with abstract methods, because they don't have any body.
Also new Java interface would have more methods, and its implementations would have to implement all of them. Kotlin interface had only one method.
To get to the same result you can make a LegacySupport class in Kotlin that will actually call the function with the default parameter and then you can expose only the return of the function to the java class from this class.

What is legitimate way to get annotations of a pure Kotlin property via reflection, are they always missing?

I'm trying to get annotations from Kotlin data class
package some.meaningless.package.name
import kotlin.reflect.full.memberProperties
annotation class MyAnnotation()
#MyAnnotation
data class TestDto(#MyAnnotation val answer: Int = 42)
fun main(args: Array<String>) {
TestDto::class.memberProperties.forEach { p -> println(p.annotations) }
println(TestDto::class.annotations)
}
I need to process class annotation to make a custom name serialization of GSON however no matter how I declare annotation class it never gets detected
The program always outputs
[]
[#some.meaningless.package.name.MyAnnotation()]
which means only class level annotations are present
Ok,
it seems that the culprit was, that Kotlin annotations have default #Target(AnnotationTarget.CLASS) which is not stressed enough in documentation.
After I added #Target to the annotation class it now works properly
#Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
annotation class MyAnnotation()
Now it prints out
[#some.meaningless.package.name.MyAnnotation()]
[#some.meaningless.package.name.MyAnnotation()]
As a side affect it will force the compiler to check that the annotation is applied as required, in current version of Kotlin, if explicit #Targetis not present only class level annotations are kept but no validity checks performed.
As Kotlin reference said as below:
If you don't specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following: param > property > field.
To make the annotation annotated on a property, you should use site target, for example:
#MyAnnotation
data class TestDto(#property:MyAnnotation val answer: Int = 42)
However, annotations with property target in Kotlin are not visible to Java, so you should double the annotation, for example:
#MyAnnotation // v--- used for property v--- used for params in Java
data class TestDto(#property:MyAnnotation #MyAnnotation val answer: Int = 42)

Use of Parceler with Kotlin data class with constructor for serialization

Is there a way to use Parceler with Kotlin data classes and constructor for serialization without using #ParcelProperty annotation for each field?
If I try and use library like this:
#Parcel
data class Valve #ParcelConstructor constructor(val size: Int)
I get Error:Parceler: No corresponding property found for constructor parameter arg0. But if I add #ParcelProperty("size") it works just fine.
Why is that?
Update:
There are other another way to use this library.
I could just remove #ParcelConstructor annotation, but then I will get error
Error:Parceler: No #ParcelConstructor annotated constructor and no default empty bean constructor found.
I think (haven't tested it) I also could make all constructor parameters optional and add #JvmOverloads but that has a side effect that I have to check all properties of the class if they are null or not.
Update 2:
This is what worked for me:
#Parcel
data class Valve(val size: Int? = null)
In short generated Java class must have default empty constructor. One way to achieve that is to do as above - all variables should have default values.
According to the docs, Parceler by default works with public fields. But a usual Kotlin data class (as in your example) is rather a "traditional getter/setter bean", since every Kotlin property is represented by a private field and a getter/[setter].
TL; DR: I think this will work:
#Parcel(Serialization.BEAN)
data class Valve(val size: Int = 10)
Note the default value, it allows Kotlin to automatically generate an additional empty constructor, which is required by the Java Been specification.
Another way would be to mark the constructor that we already have:
#Parcel(Serialization.BEAN)
data class Driver #ParcelConstructor constructor(val name: String)
The specific document: https://github.com/johncarl81/parceler#gettersetter-serialization
I know this question already has an answer, but for future viewers who are also struggling to get Parceler to work with kotlin data objects, I wrote a new annotation processor to generate the Parcelable boilerplate for Kotlin data classes. It's designed to massively reduce the boilerplate code in making your data classes Parcelable:
https://github.com/grandstaish/paperparcel
Usage:
Annotate your data class with #PaperParcel, implement PaperParcelable, and add a JVM static instance of the generated CREATOR e.g.:
#PaperParcel
data class Example(
val test: Int,
...
) : PaperParcelable {
companion object {
#JvmField val CREATOR = PaperParcelExample.CREATOR
}
}
Now your data class is Parcelable and can be passed directly to a Bundle or Intent
Edit: Update with latest API
Just add the default constructor:
#Parcel
data class Valve(val size: Int) {
constructor() : this(0)
}
if you use Kotlin 1.1.4 or above it's easier to use #Parcelize annotation
For doing this first add this to build.gradle
android {
//other codes
//for using latest experimental build of Android Extensions
androidExtensions {
experimental = true
}
}
Then change your class like this
#Parcelize
data class Valve(val size: Int? = null) : Parcelable