JavaFX - How do you use MapValueFactory in Kotlin? - kotlin

I am trying to use MapValueFactory on a TableView in Kotlin. I found a good example in Java at https://jenkov.com/tutorials/javafx/tableview.html
The following Java code works fine:
package com.example.tableview;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.MapValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class HelloApplication extends Application {
#Override
public void start(Stage stage) throws IOException {
var tableView = new TableView();
TableColumn<Map, String> firstNameColumn = new TableColumn<>("firstName");
firstNameColumn.setCellValueFactory(new MapValueFactory<>("firstName"));
TableColumn<Map, String> lastNameColumn = new TableColumn<>("lastName");
lastNameColumn.setCellValueFactory(new MapValueFactory<>("lastName"));
tableView.getColumns().add(firstNameColumn);
tableView.getColumns().add(lastNameColumn);
ObservableList<Map<String, Object>> items = FXCollections.<Map<String, Object>>observableArrayList();
Map<String, Object> item1 = new HashMap<>();
item1.put("firstName", "Randall");
item1.put("lastName" , "Kovic");
items.add(item1);
Map<String, Object> item2 = new HashMap<>();
item2.put("firstName", "Irmelin");
item2.put("lastName" , "Satoshi");
items.add(item2);
tableView.getItems().addAll(items);
Scene scene = new Scene(tableView, 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
When I cut and paste that code into a Kotlin project in IntelliJ, it attempts to auto-translate from Java to Kotlin. However, the generated code won't compile. It says there is a Type Mismatch trying to add firstNameColumn to tableView.columns.
Type mismatch.
Required: Nothing!
Found: TableColumn<Map<*, *>, String>
Here is the generated Kotlin code that DOES NOT compile:
package com.example.tableview
import javafx.application.Application
import javafx.collections.FXCollections
import javafx.scene.Scene
import javafx.scene.control.TableColumn
import javafx.scene.control.TableView
import javafx.scene.control.cell.MapValueFactory
import javafx.stage.Stage
class HelloApplication : Application() {
override fun start(stage: Stage) {
val tableView: TableView<*> = TableView<Any?>()
val firstNameColumn = TableColumn<Map<*, *>, String>("firstName")
firstNameColumn.setCellValueFactory(MapValueFactory("firstName"))
val lastNameColumn = TableColumn<Map<*, *>, String>("lastName")
lastNameColumn.setCellValueFactory(MapValueFactory("lastName"))
tableView.columns.add(firstNameColumn)
tableView.columns.add(lastNameColumn)
val items = FXCollections.observableArrayList<Map<String, Any>>()
val item1: MutableMap<String, Any> = HashMap()
item1["firstName"] = "Randall"
item1["lastName"] = "Kovic"
items.add(item1)
val item2: MutableMap<String, Any> = HashMap()
item2["firstName"] = "Irmelin"
item2["lastName"] = "Satoshi"
items.add(item2)
tableView.items.addAll(items)
val scene = Scene(tableView, 320.0, 240.0)
stage.title = "Hello!"
stage.scene = scene
stage.show()
}
fun main() {
Application.launch(HelloApplication::class.java)
}
}
Any help would be much appreciated. I have gone down several rat holes on this trying to get Types correct...

Compiler Error
Your compiler error comes from the fact you made tableView a TableView<*>. It doesn't know enough about the types to allow you to add anything to the columns list. Simply changing this line:
val tableView: TableView<*> = TableView<Any?>()
To one of the following:
val tableView: TableView<Map<*, *>> = TableView()
// or
val tableView = TableView<Map<*, *>>()
Will let your Kotlin code compile, and run as expected.
Problem With Your Java Code
Your Java code compiles because there you made tableView the raw type TableView. That means no generics associated with the TableView are used. You should never use raw types unless you're interacting with legacy code older than Java 5.
You should change:
var tableView = new TableView();
To:
var tableView = new TableView<Map<?, ?>>();
Note you're also using raw types when parameterizing the TableColumns. The fix is similar, but unfortunately it breaks the ability to use MapValueFactory due to the fact it declares a raw type when it implements Callback. The real fix is to not use MapValueFactory (see below).
Don't Use *ValueFactory Classes
Classes such as PropertyValueFactory and MapValueFactory were added at a time when lambda expressions (in Java) were not a thing. That meant using an anonymous class. Anonymous classes can be pretty verbose, so they added convenience classes to make the code easier for developers. But they have two disadvantages: they rely on reflection and, more importantly, you lose all compile-time validations (e.g., whether the property exists, the type of the property, etc.).
In both Java and Kotlin, I would recommend using lambda expressions (while also never using raw types).
Java:
// Note everywhere is using a Map<String, String> now
var tableView = new TableView<Map<String, String>>();
TableColumn<Map<String, String>, String> firstNameColumn = new TableColumn<>("firstName");
firstNameColumn.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().get("firstName")));
TableColumn<Map<String, String>, String> lastNameColumn = new TableColumn<>("lastName");
lastNameColumn.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().get("lastName")));
Kotlin:
// Again, note everywhere is using Map<String, String>
val tableView = TableView<Map<String, String>>()
val firstNameColumn = TableColumn<Map<String, String>, String>("firstName")
firstNameColumn.setCellValueFactory { SimpleStringProperty(it.value["firstName"]) }
val lastNameColumn = TableColumn<Map<String, String>, String>("lastName")
lastNameColumn.setCellValueFactory { SimpleStringProperty(it.value["lastName"]) }
Although I made everything use Map<String, String> in both versions, you can obviously parameterize the Map differently to fit your needs.

Related

Kotlin Room repository calls to DAO 'unresolved reference' in Android studio

I'm building my first Room project and need a fresh pair of eyes to see what I'm doing wrong.
Android studio keeps telling me the call to insertBopa or deleteBopa in the BopaRoomDao is an unresolved reference. My code seeme to match other examples I've looked at and tutorials but I just can't work out what I'm doing wrong.
This is my repository.kt
package com.example.mytestapp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
class BopaRepository(private val bopaRoomDao: BopaRoomDao) {
val allBopaRoomEntry: LiveData<List<BopaRoomEntry>> = bopaRoomDao.allBopas()
val searchResults = MutableLiveData<List<BopaRoomEntry>>()
private val coroutineScope = CoroutineScope(Dispatchers.Main)
fun insertBopaEntry(newbopa: BopaRoomEntry) {
coroutineScope.launch(Dispatchers.IO) {
BopaRoomDao.insertBopa(newbopa)
}
}
fun deleteBopa(name: String) {
coroutineScope.launch(Dispatchers.IO) {
BopaRoomDao.deleteBopa(name)
}
}
fun findBopa(name: String) {
coroutineScope.launch(Dispatchers.Main) {
searchResults.value = asyncFind(name).await()
}
}
fun allBopas(): LiveData<List<BopaRoomEntry>> {
return bopaRoomDao.allBopas()
}
private fun asyncFind(name: String): Deferred<Flow<List<BopaRoomEntry>>> =
coroutineScope.async(Dispatchers.IO) {
return#async bopaRoomDao.findBopa(name)
}
}
This is my Dao
package com.example.mytestapp
import androidx.lifecycle.LiveData
import androidx.room.*
//import java.util.concurrent.Flow
import kotlinx.coroutines.flow.Flow
#Dao
interface BopaRoomDao {
//add new entry to db
#Insert
fun insertBopa(bopaRoomEntry: BopaRoomEntry)
//change entry on db
#Update
fun updateBopa(bopaRoomEntry: BopaRoomEntry)
#Delete
fun deleteBopa(bopaRoomEntry: BopaRoomEntry)
//open list of previous entries from db
#Query("SELECT * FROM bopa_table")
fun findBopa(name: String): Flow<List<BopaRoomEntry>>
#Query("SELECT * FROM bopa_table")
fun allBopas(): LiveData<List<BopaRoomEntry>>
}
This is the BopaRoomEntry class
package com.example.mytestapp
import androidx.annotation.NonNull
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.lang.reflect.Constructor
#Entity (tableName = "BOPA_TABLE")
class BopaRoomEntry {
#PrimaryKey(autoGenerate = true)
#NonNull
#ColumnInfo(name = "bopaId")
var id: Int = 0
#ColumnInfo(name = "bopa_topic")
var bopaTopic: String = ""
#ColumnInfo(name = "bopa_content")
var bopaContent: String = ""
constructor(){}
constructor(bopatopic: String, bopacontent: String) {
//this.id = id
this.bopaTopic = bopatopic
this.bopaContent = bopacontent
}
}
I'm adding the database class to see if it helps clarify one of the answers...
package com.example.mytestapp
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
#Database(entities = [(BopaRoomEntry::class)], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun bopaRoomDao(): BopaRoomDao
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase? {
synchronized(this) {
var instance = INSTANCE
if (INSTANCE == null) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"bopa-database.db"
).fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
Any help appreciated :-P
After a closer look:
Could it be you're missing the BopaRoomDao.insertBopa(newbopa) vs the lower-case version: bopaRoomDao.insertBopa(newbopa)?
Do you have a abstract class XXXX : RoomDatabase() { where you define your abstract bopaDao() = BopaRoomDao and is annotated with
#Database(
entities = [
BopaRoomEntry::class,
],
version = 1,
exportSchema = false
)
If so you should be using the "daos" provided by this:
val db = ... //obtain your DB
db.bopaDao().allBopas()
Update
After cloning your project, I see a few issues:
MainViewModel:
You obtain your DB here, in an attempt to construct the Repository. This is fine (though with Hilt/DependencyInjection you would not need to worry) but your Repository is -correctly- expecting a non-nullable version of your DB. So
val bopaDb = AppDatabase.getInstance(application)
val bopaDao = bopaDb.bopaRoomDao()
repository = BopaRepository(bopaDao)
Should really be changed to ensure getInstance cannot return null.
(maybe make INSTANCE a lateinit since you must have a DB to function it appears).
If having a DB is optional, then the repository must either deal with it or the viewmodel must not attempt to use/create a repository. As you can see this can get weird really fast. I'd say having a DB cannot fail or you have other issues.
If you still leave it as optional, then the sake of this demo, change it to:
val bopaDb = AppDatabase.getInstance(application)
val bopaDao = bopaDb?.bopaRoomDao() //add the required `?`
repository = BopaRepository(bopaDao!!) //not good to force unwrap !! but will work.
Alternatively you can make your BopaRepository nullable BopaRepository? and use
repository = bopaDao?.let { BopaRepository(it) } ?: null
but then you have to add ? every time you want to use it... and this in turn will make this more messy.
I'd say your DB method should not return null, if it is null for some random other problem (say the filesystem is full and the DB cannot be created) then you should handle this gracefully elsewhere as this is an exception outside of your control. OR... your repository should fetch the DB and work with a different storage internally, you, the caller, should not care.
Anyway.. after taking care of that...
Let's look at BopaRepository
You have it defined like
class BopaRepository(private val bopaRoomDao: BopaRoomDao) {
The important bit is bopaRoomDao.
(note: I would pass the DB here, not a specific DAO, since the repo may need access to other Daos (though you could argue then it should receive the other Repositories instead) so... your choice).
Red Line 1:
val allBopaRoomEntry: LiveData<List<BopaRoomEntry>> = bopaRoomDao.allBopaEntries()
The problem is that allBopaEntries doesn't exist. In the BopaRoomDao interface, the method is called: allBopas()
So change that to
val allBopaRoomEntry: LiveData<List<BopaRoomEntry>> = bopaRoomDao.allBopas()
Red Line #2
In fun insertBopaEntry(newbopa: BopaRoomEntry) {
BopaRoomDao.insertBopaEntry(newbopa) should be:
bopaRoomDao.insertBopa(newbopa)
Red Line #3:
coroutineScope.launch(Dispatchers.IO) {
BopaRoomDao.deleteBopaEntry(name)
}
}
The DAO in the repo doesn't have a delete method (forgot?)
but should look like bopaRoomDao.delete(theBopaYouWantToDelete)
So:
#Delete
fun deleteBopa(bopaRoomEntry: BopaRoomEntry)
This means you cannot pass a name to the delete method (you could) but then because as far as I remember Room doesn't support a #Delete(...), you need to change it to a "custom" query:
#Query("DELETE FROM bopa_table WHERE bopa_topic=:name")
fun deleteByTopic(topic: String);
In truth, you should probably FETCH the row you want to delete and pass that to the original method.
For more info take a look at this SO answer.
Red Line #4
fun findBopa(name: String) {
You need to collect the flow:
fun findBopa(name: String){
coroutineScope.launch(Dispatchers.Main) {
val result = asyncFind(name).await()
result.collect {
searchResults.postValue(it)
}
}
}
This will have another issue though. You're not using the name you pass to find:
So it should look like:
//open list of previous entries from db
#Query("SELECT * FROM bopa_table WHERE bopa_topic=:name")
fun findBopa(name: String): Flow<List<BopaRoomEntry>>
(assuming name is the bopa_topic).
Red Line #5
fun allBopas(): LiveData<List<BopaRoomEntry>> {
Should do return bopaRoomDao.allBopas() (incorrect name)
This one is strange as allBopaRoomEntry is a public variable, you should either make that one private or remove it, since you have this method that returns the reference to the same thing.
Red Line #6
Last but not least,
fun asyncFind(name: String): Deferred<Flow<List<BopaRoomEntry>>>
returns a Flow (deferred but flow) so I think you'd want to do this:
= coroutineScope.async(Dispatchers.IO) {
return#async bopaRoomDao.findBopa(name)
}
Given that findBopa returns a Flow<List<BopaRoomEntry>> already.
With these changes, the project almost built correctly, but there's another issue in MainActivity:
//button actions
binding.saveBopaEntry.setOnClickListener{
//code for sending editText to db
BopaRoomDao.updateBopa(bopaTopic = R.id.bopaTopic, bopaContent = R.id.bopaContent)
}
This shouldn't be there. The click listener should tell the ViewModel: The User pressed save on this item.
viewModel.onSaveBopa(...)
And the ViewModel should launch a coroutine in its scope:
fun onSaveBopa(bopa: Bopa) {
viewModelScope.launch {
repo.updateBopa(bopa)
}
}
Keep in mind this is pseudo-code. If you pass the topic/content directly, then also pass the ID so the viewModel knows what BOPA must be updated in the database...
fun onSaveBopa(id: String, topic: String, content: String)
That's a more plausible method to call from your activity. But it really depends on what you're trying to do. in any case the activity should not need to deal with DB, Room, Daos, etc. Rely on your ViewModel, that's what it's doing there.
Anyway, commenting that in the Activity... made the project finally build
I hope that helps you ;) Good Luck.

Serialize generic class using kotlinix.serialization in Kotlin/JS fails

Serializing a generic class with kotlinx.serialization succeeds in JVM but fails in JavaScript with message TypeError: tmp$.serializer is not a function. Please, see the following Unit Test.
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlin.test.Test
#Serializable
data class SerializationTest<T>(val myValue: T)
#Serializable
data class A(val st : SerializationTest<Int>)
class SerializationTests {
#Test
fun serializeWithKotlinx() {
// Succeeds in JVM but throws in JavaScript with "TypeError: tmp$.serializer is not a function"
Json.encodeToString(SerializationTest(3))
}
#Test
fun serializeWithKotlinxWithBox() {
// Succeeds always
Json.encodeToString(A(SerializationTest(3)))
}
}
How can I serialize a generic class in JavaScript?
See the docs here
Please note that this example works only on JVM because of serializer function restrictions. For JS and Native, explicit serializer should be used: format.encodeToString(PolymorphicSerializer(Project::class), data) You can keep track of this issue here.
In your case, this code:
Json.encodeToString(SerializationTest(3))`
uses generics, which is only available on JVM.
You'll have to manually pass the serializer to encodeToString(...)
#Test
fun serializeWithKotlinx() {
val encoded =
Json.encodeToString(
SerializationTest.serializer(Int.serializer()),
SerializationTest(3),
)
println(encoded)
}
Or use a SerializersModule (documented here):
#Test
fun serializeWithKotlinxSerializersModule() {
val module = SerializersModule {
contextual(SerializationTest::class) { args ->
SerializationTest.serializer(args[0])
}
}
val mapper = Json { serializersModule = module }
val encoded = mapper.encodeToString(
SerializationTest(3),
)
println(encoded)
}
As per this GitHub issue:
Unfortunately, this is a known problem and we do not support JS legacy anymore. Please use IR backend if possible
With JS IR it works well, giving the same results as JVM.

aws-lambda handler with InputStream com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.io.InputStream

I put my AWS Lambda behind API gateway, and now trying to make an end-to-end call.
import java.io.InputStream
import com.amazonaws.services.lambda.runtime.{Context, RequestHandler}
import com.fasterxml.jackson.databind
case class MyClass(a: String, b: String)
class MyHandler extends RequestHandler[InputStream, Boolean] {
val scalaMapper: databind.ObjectMapper = {
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
new ObjectMapper().registerModule(new DefaultScalaModule)
}
def handleRequest(input: InputStream, context: Context): Boolean = {
val myClass = scalaMapper.readValue(input, classOf[MyClass])
isValid(myClass)
}
It works when I test locally by providing the handler with a string, but when in a Lambda, the handler can't use the input stream. I'm getting the error
Endpoint response body before transformations: {
"errorMessage":"An error occurred during JSON parsing",
"errorType":"java.lang.RuntimeException",
"stackTrace":[],
"cause": {
"errorMessage":"com.fasterxml.jackson.databind.JsonMappingException:
Can not construct instance of java.io.InputStream,
problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information\n
at [Source: lambdainternal.util.NativeMemoryAsInputStream#2698dc7; line: 1, column: 1]",...
A Java Lambda has two possible signatures:
public interface RequestHandler<I, O> {
public O handleRequest(I input, Context context);
}
and
public interface RequestStreamHandler {
public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException;
}
I'm not sure if you're trying to mix the two but you're trying to tell Lambda to deserialize an InputStream.
While I try hard not to do anything in Scala, I believe you want:
case class MyClass(a: String, b: String)
class MyHandler extends RequestStreamHandler {
val scalaMapper: databind.ObjectMapper = {
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
new ObjectMapper().registerModule(new DefaultScalaModule)
}
def handleRequest(input: InputStream, output: OutputStream context: Context): Unit = {
val myClass = scalaMapper.readValue(input, classOf[MyClass])
output.write( isValid(myClass) )
}
Though I have not tested this. Another, perhaps better way would be:
case class MyClass(a: String, b: String)
class MyHandler extends RequestHandler[MyClass, Boolean] {
def handleRequest(myClass: MyClass, context: Context): Boolean = {
isValid(myClass)
}

Using data class in micronaut properties

I'm writing configuration properties and would like to use data class to hold the data.
Problem is, data classes have a primary constructor and are immutable, and micronaut tries to inject the values as beans.
Example:
#ConfigurationProperties("gerencianet")
data class GerenciaNetConfiguration(
val clientId: String,
val clientSecret: String,
val apiUrl: String,
val notificationUrl: String,
val datePattern: String = "yyyy-MM-dd HH:mm:ss"
)
Error: Caused by: io.micronaut.context.exceptions.NoSuchBeanException: No bean of type [java.lang.String] exists. Make sure the bean is not disabled by bean requirements
Is there support for it?
You can inject the values as constructor parameters using #Parameter. It avoids common mistakes with #Value.
For example, if your application.yml looks like this:
group:
first-value: asdf
second-value: ghjk
Then a Kotlin class might look like:
import io.micronaut.context.annotation.Property
import javax.inject.Singleton
#Singleton
class MyClass(#Property(name = "group.first-value") val firstValue: String) {
fun doIt(): String {
return firstValue
}
}
Or, similarly, a method:
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Property
import javax.inject.Singleton
#Factory
class MyFactory {
#Singleton
fun getSomeValue(#Property(name = "group.first-value") firstValue: String): SomeClass {
return SomeClass.newBuilder()
.setTheValue(firstValue)
.build()
}
}
One option you have is to do something like this:
import io.micronaut.context.annotation.ConfigurationBuilder
import io.micronaut.context.annotation.ConfigurationProperties
#ConfigurationProperties("my.engine")
internal class EngineConfig {
#ConfigurationBuilder(prefixes = ["with"])
val builder = EngineImpl.builder()
#ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "crank-shaft") / <3>
val crankShaft = CrankShaft.builder()
#set:ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "spark-plug")
var sparkPlug = SparkPlug.builder()
}
That is from our test suite at https://github.com/micronaut-projects/micronaut-core/blob/1c3e2c3280da200c96e629a4edb9df87875ef2ff/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/config/builder/EngineConfig.kt.
You can also inject the values as constructor parameters using #Value.
I hope that helps.

I can't generate a class using Kotlin processor

I'm implementing a processor to generate kotlin code using custom annotations. The problem is that I cannot find a way to relate the annotation to the field it was declared for, and I cannot find a way to understand if a field is of a nullable type. The processor doesn't succeed to generate the code because the getAnnotationsByType doesn't return the annotations for the current field (the list it's empty). Not even the order is good, fields are passed first and the annotations after all the fields.
package it.kfi.xml.binding.processor
import com.google.auto.service.AutoService
import com.squareup.kotlinpoet.*
import it.kfi.xml.binding.annotations.XmlClass
import it.kfi.xml.binding.annotations.XmlProperty
import java.io.File
import java.lang.reflect.Type
import javax.annotation.Nullable
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.NullType
import javax.lang.model.type.TypeMirror
import javax.print.DocFlavor
import javax.tools.Diagnostic
import kotlin.reflect.KClass
import kotlin.reflect.full.createType
#AutoService(Processor::class)
class XmlBinder : AbstractProcessor() {
companion object {
const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
}
override fun getSupportedAnnotationTypes(): MutableSet<String> {
return mutableSetOf(XmlClass::class.java.name)
}
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment): Boolean {
roundEnv.getElementsAnnotatedWith(XmlClass::class.java)
.forEach {
if (it.kind != ElementKind.CLASS) {
processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated")
return true
}
processClass(it)
}
return false
}
private fun processClass(element: Element) {
val className = element.simpleName.toString() + "Model"
val packageName = processingEnv.elementUtils.getPackageOf(element).toString()
val classBuilder = TypeSpec.classBuilder(className)
classBuilder.addModifiers(KModifier.PUBLIC)
val initFromXml = FunSpec.builder("initFromXml")
initFromXml.addModifiers(KModifier.PUBLIC)
initFromXml.addParameter(ParameterSpec.builder("xml", String::class).build())
val properties = element.enclosedElements
var x: Int = 1
//Look for elements annotated with XmlField and add those elements to the generated class
for (property in properties) {
val annotation = property.getAnnotationsByType(XmlProperty::class.java)
val v = 10
classBuilder.addProperty(PropertySpec.varBuilder(property.simpleName.toString(), String::class, KModifier.PUBLIC).initializer(v.toString()).build())
initFromXml.addStatement("this.${property.simpleName} = \"${v.toString()}\"")
}
classBuilder.addFunction(initFromXml.build())
val fileName = "kfi_generated_$className"
val file = FileSpec.builder(packageName, fileName).addType(classBuilder.build()).build()
val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
file.writeTo(File(kaptKotlinGeneratedDir))
}
}
Can anyone help me found a way to relate annotations to their fields or properties ?