I'm trying to define a StringDef in kotlin:
#Retention(AnnotationRetention.SOURCE)
#StringDef(NORTH, SOUTH)
annotation class FilterType {
companion object {
const val NORTH = "NORTH"
const val SOUTH = "SOUTH"
}
}
I think something is wrong in the code above.
// I can send anything to this method, when using my kotlin stringDef
private fun takeString(#DirectionJava.Direction filterType: String) {
I want the kotlin equivalent of the java below:
public class DirectionJava {
public static final String NORTH = "NORTH";
public static final String SOUTH = "SOUTH";
#Retention(RetentionPolicy.SOURCE)
#StringDef({
NORTH,
SOUTH,
})
public #interface Direction {
}
}
Calling the java defined StringDef works from kotlin
// This works as expected, the Java-written string def
// restricts what I can pass to this method
private fun takeString(#DirectionJava.Direction filterType: String) {
Where have I gone wrong, how do you define a StringDef in Kotlin?
According to jetbrains issue, Lint check plugin for enumerated annotations in kotlin is under development and is not stable yet. Checking android support annotations such as #Nullable, #StringRes, #DrawableRes, #IntRange, ... (which are written in java) works fine and user defined enumerated annotations are not checked properly. So, it seems that we should define them in java then use in kotlin.
#StringDef now works if you define it inside a companion object. See:
https://stackoverflow.com/a/70672074/2465264
Also, consider using a Kotlin enum class instead since the performance issues have been fixed in ART (Android Runtime Machine), which most Android devices are now running.
Related
I am seeing the following error
Platform declaration clash: The following declarations have the same
JVM signature (getHosts()Landroidx/lifecycle/MutableLiveData;):
private final fun <get-hosts>(): MutableLiveData<List> defined
in com.example.xx.viewmodel.HostsViewModel public final fun
getHosts(): MutableLiveData<List> defined in
com.example.xx.viewmodel.HostsViewModel
What am I doing wrong?
class HostsViewModel : ViewModel() {
private val hostsService = HostsService()
private val hosts: MutableLiveData<List<Host>> by lazy {
MutableLiveData<List<Host>>().also {
loadHosts()
}
}
fun getHosts(): MutableLiveData<List<Host>> {
return hosts
}
private fun loadHosts(){
hosts.value = hostsService.getHosts().body()
}
}
For every class property (val), Kotlin generates a getter called getHosts() and for var also a setter called setHosts(MutableLiveData<List<Host>> value) as per Java's convention. It hides it from the Kotlin user as getters and setters are usually just boilerplate code without offering much value. As such, your own getHosts() method clashes with the generated method at compilation. You have multiple possibilities to solve this issue:
Rename private val hosts to something else, e.g. private val internalHosts
Annotate the getHosts method with #JvmName("getHosts2"). If you do that though, consider the possibility that someone might call your code from Java and in that case, the caller would need to call getHosts2() in Java code, which might not be such nice API-design.
Reconsider your api design. In your case, you could simply make val hosts public and remove your getHosts() entirely, as the compiler will auto-generate getHosts() for you.
In addition to that, you might want to consider not exposing MutableLiveData in general as mentioned in the comments.
Edit:
Also, I would recommend that you do this:
val hosts: MutableLiveData<List<Host>> by lazy {
MutableLiveData<List<Host>>().also {
it.value = hostsService.getHosts().body()
}
}
and remove loadHosts to make your code more concise.
My SDK exposes a Java interface that has only static methods, e.g.
public interface MyDevice {
public static void setLocation(Location location) {…}
public static Location getLocation() {…}
}
In Java app that uses the SDK, my customers can use these as if it were a singleton, e.g.
Location currentLocation = MyDevice.getLocation();
When this SDK is integrated into a Kotlin app, it would be natural to express the same as a property:
val currentLocation = MyDevice.location
The problem is, this built-in interop works for non-static methods only.
I can create a singleton in Kotlin and have it handle the translation:
object myDevice {
var location: Location
get() = MyDevice.getLocation()
set(location) = MyDevice.setLocation(location)
}
But won't this single Kotlin file in an otherwise Java-only SDK negatively affect the customers who don't use Kotlin? Can the same be expressed in Java?
Or maybe I should simply convert MyDevice.java to Kotlin? What will be negative effects of such step for the customers who are still on Java?
The problem you described is a lack of meta data Kotlin need to treat static methods as Class extension properties of extension functions.
Together with issue KT-11968 it makes it not possible for now, at least.
The best possible option is API conversion to Kotlin with a support of #JvmStaic/#JvmField and #JvmDefault where necessary for backward compatibility.
interface MyDevice {
companion object {
// nullable type or explicit init
var location: Location?
#JvmStatic get
#JvmStatic set
// kotlin
val x = MyDevice.location
MyDevice.location = x
// java
var x = MyDevice.getLocation();
MyDevice.setLocation(x);
In Kotlin, in order to achieve the same result as static methods in Java interfaces you should use a companion object to declare your static methods. Example:
interface MyDevice {
// instance methods
companion object {
// static methods
#JvmStatic
fun setLocation(location: Location) {...}
#JvmStatic
fun getLocation(): Location {...}
}
}
Note the #JvmStatic annotation. When you're calling those Kotlin functions from a Java class they will be interpreted as static methods. Example:
public void myJavaMethod() {
Location location = MyDevice.getLocation();
}
There are few solution provided by kotlin lang, there we can use to make it simple with your case. It is the nature of kotlin to make better work between Java, for me I didn't see any drawback of using Companion/Object to create the static method similar to Java. The kotlin language itself also provide many convenient helper for developer for the simplicity. As below what we can apply:
Object
object MyDevice {
#JvmStatic
fun getLocation(): Location {
}
#JvmStatic
fun setLocation(location: Location) {
}
}
Companion
class MyDevice {
companion object {
#JvmStatic
fun getLocation(): Location {
}
#JvmStatic
fun setLocation(location: Location) {
}
}
}
Call in Java:
MyDevice.setLocation(location);
final Location location = MyDevice.getLocation();
Having read the answers of the experts, having studied the links they provided, and having decompiled the Kotlin code of the wrapper class and analyzed a demo app (pure Java) which used the Kotlin-wrapped library, I decided to change the Java API.
Now I have a class with a public static object:
public class MyDevice {
public static MyDevice myDevice;
public void setLocation(Location location) {…}
public Location getLocation() {…}
}
now the Java consumers will use
import static com.example.sdk.MyDevice.myDevice;
Kotlin consumers don't need that static:
import com.example.sdk.MyDevice.myDevice
So, I don't need a separate Kotlin flavor of my library!
This project has MVVM, Room, Koin and Coroutines.
Project code:
#Dao
interface MovieDao {
#get:Query("select poster_path from Movie")
val getImgPopularMovieList: LiveData<List<String>>
}
What means "#get:Query" instead "#Query" and "val" instead "fun" in DAO interface?
And how to add WHERE clause without a function to pass parameter or using a constant? (Using mandatorily val instead fun). Example: "select poster_path from Movie WHERE category = :someParameterOrConstant"
I would like to leave this answer, in case someone new in Kotlin like me encounters this.
I was learning codelab about Android, and #get:Query mentioned in the project, which lead me to this question, then after a good research I found this concept relates to Kotlin than Android and it is called Use-site Targets annotation.
Annotation Use-site Targets
Simply put, annotation use-site targets allow any #Annotations in your source code to end up at a very specific place in your compiled bytecode or in the Java code generated by kapt.
Kotlin supports the following values of the use-site targets that correspond to:
delegate – a field storing a delegated property
field – a field generated for a property
file – a class containing top-level functions and properties defined in that file
get/set – the property getter/setter
param – a constructor parameter
property – the Kotlin's property, it is not accessible from Java code
receiver – the receiver parameter of an extension function or property
Let's use simple class:
class Example(#param:ColorRes val resId:Int )
We used the use-site of param with #ColorRes, that will apply #ColorRes to the constructor parameter in the generated Java class:
public final class Example {
private final int resId;
public final int getResId() {
return this.resId;
}
public Example(#ColorRes int resId) {
this.resId = resId;
}
}
Let's change the use-site to field:
class Example(#field:ColorRes val resId:Int )
Now #ColorRes annotation will be applied to the resId field of the generated class:
public final class Example {
#ColorRes
private final int resId;
public final int getResId() {
return this.resId;
}
public ViewModel(int resId) {
this.resId = resId;
}
}
By using the use-site of get:
class Example(#get:ColorRes val resId:Int )
The getter method of resId field will have the #ColorRes annotation:
public final class Example {
private final int resId;
#ColorRes
public final int getResId() {
return this.resId;
}
public Example(int resId) {
this.resId = resId;
}
}
So!
In our Android code:
#Dao
interface MovieDao {
#get:Query("select poster_path from Movie")
val popularMovieImageList: LiveData<List<String>>
}
The annotation use-site of get will require th implementation of MovieDao to apply Query("...") to getter method of popularMovieImageList:
public final class MovieDaoImpl {
private final LiveData<List<String>> popularMovieImageList;
#Query("select poster_path from Movie")
public final LiveData<List<String>> getPopularMovieImageList() {
...
}
}
NOTE: Previous java code is from my imagination, I have no idea how generated implementation for this Dao by Room will look, just to support my explanation.
So far!
Why #get:Query instead of #Query?
Well, from the docs of Android we use #Query for methods:
Marks a method in a Dao annotated class as a query method.
And from the docs of Kotlin we don't use the Annotation Use-site targets for methods but for a property or a primary constructor parameter:
When you're annotating a property or a primary constructor parameter, there are multiple Java elements which are generated from the corresponding Kotlin element, and therefore multiple possible locations for the annotation in the generated Java bytecode.
As far as I learned in Kotlin, property getter cannot have parameter but instead use methods for that.
I think all other confusions should be clear now.
References:
Use-site annotation targets - Mastering Kotlin by Nate Ebel
Kotlin Annotations - Baeldung
Advanced Kotlin - Part 2: Use-Site Targets - American Express
When building a AbstractProcessor in Android studio using kapt/kotlinpoet. When I try to use the repeatable annotation tag it stop getting data back from roundEnv.getElementsAnnotatedWith(AnnotationName::class.java), I am able get the annotated classes info back if the repeatable tag is removed from the annotation
going to try to use other means of reflection
#Target(AnnotationTarget.CLASS)
#Retention(AnnotationRetention.SOURCE)
#Repeatable // <-- issue
annotation class ConfigurableGroup(
val key: String,
val text: String
)
// the processor abbreviated
#AutoService(Processor::class)
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#SupportedOptions(AnnotationProcessorNew.
KAPT_KOTLIN_GENERATED_OPTION_NAME)
class AnnotationProcessorNew : AbstractProcessor(){
override fun process(annotations: MutableSet<out TypeElement>, roundEnv:
RoundEnvironment): Boolean {
return generateConfigurables(roundEnv)
}
override fun getSupportedAnnotationTypes(): MutableSet<String> {
return mutableSetOf(
ConfigurableGroup::class.java.name
}
roundEnv.getElementsAnnotatedWith(ConfigurableGroup::class.java)
.filter { it.kind == ElementKind.CLASS }
.forEach { classElement ->
val classPackageName =
processingEnv.elementUtils.getPackageOf(classElement).toString()
val classSimpleName = classElement.simpleName.toString()
I would expect to get data from reflection both times when the annotation has a #repeatable tag or not.
It seems that Kotlin's #Repeatable annotation is purely a hint for tooling and does not match up with Java's #Repeatable contract.
It seems that the java contract requires a container annotation to be defined so that the repeated annotations can be packaged as a single annotation for retrieval by the annotation processor.
You either need to explicitly add #java.lang.annotation.Repeatable(ConfigurableGroups::class) for a container annotation and accept the warning it generates or define the annotations in Java instead of Kotlin.
I'm trying to figure out if something is possible. Generally, what I'm trying to do is get a class type of a subclass from within the companion object of the superclass ... In the snipped below, treat the __ as what I need
companion object
{
fun fromSnapshot(snapshot: DataSnapshot): __
{
val model = snapshot.getValue(__)
model.key = snapshot.key
// ...
return model
}
}
Some background ... DataSnapshot is from Firebase, and snapshot.getValue() takes a Class<T>. If I were trying to create an instance of, say, a TestModel, code would be as follows
companion object
{
fun fromSnapshot(snapshot: DataSnapshot): TestModel
{
val model = snapshot.getValue(TestModel::java.class)
model.key = snapshot.key
// ...
return model
}
}
I'm not really sure if what I'm asking is possible in Kotlin. I'm pretty sure it isn't in Java. I hate to mention it, but in Swift, this would be accomplished with what I call "big-S self," or Self, which is the class type of instance self. If you don't know Swift, self is equivalent to Java and Kotlin's this.
Any help would be hugely appreciated!
From your code, it seems to be a very generic function. It doesn't matter what T is and in which companion object this function lives, so I have another version:
inline fun <reified T : FirebaseModel> DataSnapshot.toModelOfType() =
getValue(T::class.java).also { it.key = this.key}
It can be used like this:
someSnapshot.toModelOfType<SomeFirebaseModel>()
instead of your
FirebaseModel.fromSnapshot<SomeFirebaseModel>(someSnapshot)
or with imports
fromSnapshot<SomeFirebaseModel>(someSnapshot)
I prefer mine because it's shorter than your version without imports and more fluent than your version with imports.
I personally suggest Prefer extension functions over Java style utility functions.
Even though I sat on this for days without posting a question, I figured it out less than an hour after posting this question. This can be accomplished with a reified generic type, which allows for usage of the generic type from within a function, however these can only be used as inline functions. Here's my solution
companion object
{
inline fun <reified T : FirebaseModel> fromSnapshot(snapshot: DataSnapshot): T
{
val model = snapshot.getValue(T::class.java)
model.key = snapshot.key
return model
}
}