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.
Related
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.
I have a data class which I need to validate:
import javax.validation.Valid
import whatever.pckg.validation.PkiSignWithBusinessCode
import whatever.pckg.validation.NullOrNotBlank
data class UploadFileReq(
val id: String? = null,
...(other fields)...
#get:Valid
val signaturesInfo: MutableList<Pair<SignatureInfo, Object>> = mutableListOf() # Object here is for simplicity
) {
#PkiSignWithBusinessCode
data class SignatureInfo(
val typeSign: String = "",
#get:NullOrNotBlank
val businessCode: String? = null,
)
}
#NullOrNotBlank annotation is just a simple merge of standard #NotBlank and #Null annotations.
I also have another custom validation annotation #PkiSignWithBusinessCode, its definition is below:
import whatever.pckg.UploadFileReq
import javax.validation.*
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.reflect.KClass
#Constraint(validatedBy = [PkiSignWithBusinessCodeValidator::class])
#Target(AnnotationTarget.CLASS)
#Retention(RUNTIME)
annotation class PkiSignWithBusinessCode(
val message: String = "PKI signature requires filled businessCode",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class PkiSignWithBusinessCodeValidator: ConstraintValidator<PkiSignWithBusinessCode, UploadFileReq.SignatureInfo>> {
override fun isValid(obj: UploadFileReq.SignatureInfo?, context: ConstraintValidatorContext): Boolean {
if (obj != null) {
if ((obj.typeSign == "PKI") && (obj.businessCode == null)) {
return false
}
}
return true
}
Logic of above annotation is quite simple - when typeSign equals PKI and businessCode is null, then validator should treat that as invalid object.
For your reference here's a simple unit-test that tries to check the work of #PkiSignWithBusinessCode:
import org.junit.jupiter.api.Test
import whatever.pckg.UploadFileReq
import javax.validation.Validation
import kotlin.test.assertEquals
class PkiSignWithBusinessCodeTest {
#Test
fun `validate PkiSignWithBusinessCodeTest`() {
val validator = Validation.buildDefaultValidatorFactory().validator
val signatureInfo = UploadFileReq.SignatureInfo(
typeSign = "PKI",
businessCode = null
)
val uploadFileReq = UploadFileReq(
null,
signaturesInfo = mutableListOf(signatureInfo to Object)
)
val result = validator.validate(uploadFileReq)
assertEquals(1, result.size)
assertEquals("PKI signature requires filled businessCode", result.first().messageTemplate)
}
}
But this test obviously fails on first assertion state: java.lang.AssertionError: Expected <1>, actual <0>. So no constraint violations found by validator.
The problem is that Spring ignores validation rule of above annotation. As an assumption I suppose that somehow Pair class wrap prevents Spring from using my validation annotation. Maybe it's a bug?
Or maybe I overlooked something in my code?
Found a workaround on this - need to make own ValidatingPair with #Valid annotations on first and second members of this new Pair:
import javax.validation.Valid
data class ValidatingPair<out A, out B>(
#get:Valid
public val first: A,
#get:Valid
public val second: B
) : java.io.Serializable {
override fun toString(): String = "($first, $second)"
}
And make:
val signaturesInfo: MutableList<Pair<SignatureInfo, Object>>
to become
val signaturesInfo: MutableList<ValidatingPair<SignatureInfo, Object>>
Then validation starts working for list members.
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)
}
We are using database table names which are prefixed with environment names e.g:
instead of just 'Cities' we have 'ci_Cities', 'dev_Cities' and 'prod_Cities'.
The problem is that Schema definitions are based on Kotlin objects, which is nice in an usage, but doesn't allow me to simply inject table prefix in e.g. constructor.
So the question is how to implement such a functionality in Kotlin-Exposed?
In the end I have found solution, which seems to be quite elegant.
But I think, that some improvements could be done also in Kotlin Exposed, so that in most cases solution is more concise.
City.kt
data class City(val id: Int, val name: String, val timestamp: Instant)
Schema.kt
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.`java-time`.timestamp
class CitiesSchema(environment: String) {
val cities = CitiesTable(environment)
}
class CitiesTable(environment: String) : Table(environment + "_Cities") {
val id = varchar("id", 99)
val name = varchar("name", 99)
val timestamp = timestamp("timestamp")
}
CitiesRepository.kt
class CitiesRepository(dataSource: DataSource, private val schema: CitiesSchema) {
private val database = Database.connect(dataSource).defaultFetchSize(10000)
override fun save(city: City): City {
transaction(database) {
schema.cities.insert {
it[schema.cities.id] = city.id
it[schema.cities.name] = city.name
it[schema.cities.timestamp] = city.timestamp
}
}
return city
}
Then in e.g. Spring you can instantiate your schema:
#Bean
public CitiesSchema schema(#Value("${spring.application.env}") String environment) {
return new CitiesSchema(environment);
}
It would be nice to have in Kotlin Exposed ability to rename tables/columns on runtime. Then it would be possible to access Kotlin objects without additional ceremonies.
Such a feature could look like in Jooq:
https://www.jooq.org/doc/3.14/manual-single-page/#settings-render-mapping
I'm trying to convert the following code to Kotlin AND still have one of the classes (Foo) used by Java. What is the proper way of making this conversion?
Original Java:
public class Foo {
public static final String C_ID = "ID";
public static final String C_NAME = "NAME";
public static final String[] VALUES = {"X", "Y", "Z"};
public static String[] getAll() {
return new String[] {C_ID, C_NAME};
}
}
public class Bar {
public void doStuff() {
String var1 = Foo.C_ID;
String[] array1 = Foo.VALUES;
String[] array2 = Foo.getAll();
}
}
Auto conversion fo Foo to Kotlin
object Foo {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
Problem:
Bar class can no longer access C_ID or VALUES (error: "private access")
if I put "const" in front of C_ID, it works... but I cannot do the same with VALUES ("const" can ONLY be used on primatives or String)
Is there a different way I should be doing this (so both Java code and Kotlin code can access everything in Foo)?
The current semantics come from Kotlin Beta Candidate:
#JvmField and objects
We have made the strategy for generating pure fields (as opposed to get/set pairs) more predictable: from now on only properties annotated as #JvmField, lateinit or const are exposed as fields to Java clients. Older versions used heuristics and created static fields in objects unconditionally, which is against our initial design goal of having binary-compatibility-friendly APIs by default.
Also, singleton instances are now accessible by the name INSTANCE (instead of INSTANCE$).
According to this and to the reference, there are three ways of working with properties of a Kotlin object from Java:
Use Foo.INSTANCE.
By default, properties of object won't be static fields for Java, but Java can access the properties through Foo object instance -- Foo.INSTANCE.
So the expression will be Foo.INSTANCE.getC_ID().
Mark a property with #JvmStatic annotation:
object Foo {
#JvmStatic val C_ID = "ID"
//...
}
This will generate static getter for C_ID instead of Foo instance getter which will be accessible as Foo.getC_ID().
Use #JvmField annotation on property declaration:
object Foo {
#JvmField val C_ID = "ID"
//...
}
This will make Kotlin compiler generate a static field for Java instead of property.
Then in Java you can access it as a static field: Foo.C_ID.
But it won't work on properties without backing fields like all in your example.
For primitives, as you stated, one can use const which will have the same effect as #JvmField in terms of visibility in Java.
By the way, when it comes to methods, the situation is the same, and there is #JvmStatic annotation for them.
In your foo class you can put those properties and the method inside a companion object:
class Foo {
companion object {
val C_ID:String = "ID"
val C_NAME:String = "NAME"
#JvmField val VALUES = arrayOf("X", "Y", "Z")
fun getAll():Array<String> {
return arrayOf(C_ID, C_NAME)
}
}
}
Then you can call Foo.getAll(), and Foo.C_ID, Foo.C_NAME and Foo.VALUES.
You should be able to access the values "the kotlin way":
object Foo {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
fun main(args: Array<String>) {
Foo.all.forEach { it->println(it) }
}
With as result:
ID
NAME
Process finished with exit code 0
it's better if you create new kotlin file just for constants.
create Constants.kt file and paste below code.
object Constants {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
in your main activity you can access the constants by the constant name the android studio will automatically import the constants. here is my mainActivity:
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.architecturecompintro.Constants.C_ID
import com.example.architecturecompintro.Constants.C_NAME
import com.example.architecturecompintro.Constants.VALUES
import com.example.architecturecompintro.Constants.all
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val TAG = "info"
Log.i(TAG, C_ID)
Log.i(TAG,C_NAME)
for(item in VALUES) {
Log.i(TAG,item)
}
val arrayItem = all
for(item in arrayItem) {
Log.i(TAG,item)
}
}
}
I was able to get log output successfully