Room - Using #get:Query instead #Query - android

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

Related

Platform declaration clash kotlin

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.

Is roundEnv.getElementsAnnotatedWith(AnnotationName::class.java) reflection broken when used with a #Repeatable #Retention(AnnotationRetention.Source)

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.

Define StringDef in kotlin

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.

Room not finding lombok generated constructor

I'm using lombok to generate constructors, getters and setters for my models. When i try to use lombok to generate the constructor for my entity class, I get this error
Error:(14, 8) error: Entities and Pojos must have a usable public
constructor. You can have an empty constructor or a constructor whose
parameters match the fields (by name and type).
Tried the following constructors but they failed to match:
Region(int,java.lang.String,java.lang.String) -> [param:arg0 -> matched
field:unmatched, param:arg1 -> matched field:unmatched, param:arg2 ->
matched field:unmatched]
but writing the constructor manually works. Can anyone help me figure out what's wrong?
My entity class is shown below
#Value
#Entity
public class Region {
#PrimaryKey
private int regionId;
private String name;
private String code;
}
Room version: 1.1.0
Lombok version: 1.16.20
The matching seems to fail because the constructor parameter names are not available at runtime.
Since version 1.16.20 lombok does not generate #ConstructorProperties annotations any more (which would carry those names).
Try adding lombok.anyConstructor.addConstructorProperties = true to your lombok.config, and lombok will generate a #ConstructorProperties annotation for your constructor. (See https://projectlombok.org/features/configuration for details on how to configure lombok.)
EDIT: The problem is the annotation processing during compilation. Both Room and lombok hook into javac as annotation processors, and they do not work nicely in combination. So at the moment, the only stable solution is to delombok first.
You can use the following setup:
#Entity
#Getter
#Setter
#AllArgsConstructor(onConstructor = #__({#Ignore}))
#NoArgsConstructor
public class Region {
#PrimaryKey
private int regionId;
private String name;
private String code;
}
This will make Room use the default constructor and set the value via the provided setters. Additionally you have a constructor that accepts all arguments for object instantiation, but will be ignored by Room.
Note: Object won't be immutable that way
Please try this as below with #Data annotation.
#Value
#Entity
#Data
public class Region {
#PrimaryKey
private int regionId;
private String name;
private String code;
}

How to use Android Support typedef annotations in kotlin?

I develop Android applications and often use annotations as compile time parameter checks, mostly android's support annotations.
Example in java code:
public class Test
{
#IntDef({Speed.SLOW,Speed.NORMAL,Speed.FAST})
public #interface Speed
{
public static final int SLOW = 0;
public static final int NORMAL = 1;
public static final int FAST = 2;
}
#Speed
private int speed;
public void setSpeed(#Speed int speed)
{
this.speed = speed;
}
}
I don't want to use enums because of their performance issues in Android. The automatic converter to kotlin just generates invalid code. How do I use the #IntDef annotation in kotlin?
Edit: In case you miss the comments on the question or this answer, it's worth noting that the following technique compiles,
but does not create the compile-time validation you would get in
Java (which partially defeats the purpose of doing this). Consider using an enum
class
instead.
It is actually possible to use the #IntDef support annotation by defining your values outside of the annotation class as const vals.
Using your example:
import android.support.annotation.IntDef
public class Test {
companion object {
#IntDef(SLOW, NORMAL, FAST)
#Retention(AnnotationRetention.SOURCE)
annotation class Speed
const val SLOW = 0L
const val NORMAL = 1L
const val FAST = 2L
}
#Speed
private lateinit var speed: Long
public fun setSpeed(#Speed speed: Long) {
this.speed = speed
}
}
Note that at this point the compiler seems to require the Long type for the #IntDef annotation instead of actual Ints.
There's currently no way to achieve exactly this in Kotlin, since an annotation class cannot have a body and thus you cannot declare a constant in it which would be processed by IntDef. I've created an issue in the tracker: https://youtrack.jetbrains.com/issue/KT-11392
For your problem though, I recommend you use a simple enum.
Update:
Forget #IntDef and #StringDef, Now, with ART, you can use enums instead.
From the official GoogleIO:
https://www.youtube.com/watch?v=IrMw7MEgADk&feature=youtu.be&t=857
Plus, if you're still not sure if you should use enums, you can hear a bunch of people yelling at each other in the comments of the first answer over here:
https://stackoverflow.com/a/37839539/4036390
Old answer:
Just create the #IntDef class as a java class and access it via kotlin code.
Example:
Create your type class:
public class mType {
#IntDef({typeImpl.type1, typeImpl.type2, typeImpl.type3})
#Retention(RetentionPolicy.SOURCE)
public #interface typeImpl {
int type1 = 0;
int type2 = 1;
int type3 = 2;
}
}
Put this function in any Kotlin object:
object MyObject{
fun accessType(#mType.typeImpl mType: Int) {
...
}
}
then access it:
fun somOtherFunc(){
MyObject.accessType(type1)
}
**Notice: you don't have to put the access method inside an object.
Use this:
companion object {
const val FLAG_PAGE_PROCESS = 0L//待处理
const val FLAG_PAGE_EXCEPTION = 1L//设备异常
const val FLAG_PAGE_UNCHECKED = 2L//未审核
const val FLAG_PAGE_AUDIT = 3L//统计
val FLAG_PAGE = "FLAG_PAGE"
fun newInstance(#FlagPageDef flagPage: Int): RepairFormsListFragment {
val fragment = RepairFormsListFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
#Retention(AnnotationRetention.SOURCE)
#IntDef(FLAG_PAGE_PROCESS, FLAG_PAGE_EXCEPTION, FLAG_PAGE_UNCHECKED, FLAG_PAGE_AUDIT)
annotation class FlagPageDef
}

Categories

Resources