I use kotlin coroutines in my Spring Boot project and I access MongoDB using Spring Data.
Everything works fine e.g. for findAll(), but when I use pagination, Spring throws an exception on startup
IllegalStateException: Method has to use a either
multi-item reactive wrapper return type or a wrapped Page/Slice type.
My code is as follows:
#Repository
interface CoroutinesActivityRepository : CoroutineCrudRepository<Activity, String> {
suspend fun findAllOrderByTitle(page: Pageable): Flow<Activity>
}
data class Activity(val id: String? = null, val title: String)
Is it possible to apply pagination for CoroutineCrudRepository ?
The only way that worked for me, was to use ReactiveMongoTemplate
#Component
class CoroutinesActivityRepository(
private val mongoTemplate: ReactiveMongoTemplate
) {
suspend fun findAllOrderByTitle(page: Pageable): Flow<Activity> =
mongoTemplate.find(Query().with(page), Activity::class.java).asFlow()
}
I know it's pretty late, but for future reference...
Try the same, but removing suspend:
fun findAllOrderByTitle(page: Pageable): Flow<Activity>
If this solved the error, but Pagination is not working properly, you can always use #Query. Again, don't use suspend.
To use #Query to manually achieve pagination, you can use LIMIT and OFFSET. For example:
#Query("""
select * from table limit :size offset :offset
""")
fun findAll(offset: Int, size: Int): Flow<DataClass>
To calculate offset from page and size variables:
val offset = size * (page - 1)
I hope this helps!
Related
Writing good code between Dao - Repository - ViewModel.
The function always return null value.
from Entity:
#Entity(tableName = "diabete")
class Diabete(
#PrimaryKey(autoGenerate = true)
#NonNull
#ColumnInfo(name = "diabeteId")
var id: Int,
var date: LocalDateTime,
var balise: String,
var taux: Float,
var note: String
)
from Dao:
// Obtenir la moyenne du taux selon la balise
#Query("SELECT IFNULL(avg(taux), 0.0) FROM diabete WHERE balise = :balise")
fun avgByBalise(balise: String): LiveData<Float>
from Repository:
fun avgBaliseAJeun(balise: String): LiveData<Float> {
return dbDao.avgByBalise(balise)
}
from ViewModel:
fun avgBaliseAJeune(balise: String): LiveData<Float> {
val result = MutableLiveData<Float>()
viewModelScope.launch(Dispatchers.IO) {
val retour = repository.avgBaliseAJeun(balise)
result.postValue(retour.value)
}
return result
}
from Fragment:
val avgBaliseAJeun: Float = dpViewModel.avgBaliseAJeune("À jeun").observeAsState(initial = 0F).value
This line always return null when debugging.
All the compilation is ok.
The application crash when running.
What is missing?
Congrats on asking your first question, welcome!
A (not-so) fun thing to find out is that LiveData values can be null even if declared as non-nullable source.
The second thing to keep in mind is that upon app start up live data coming from room has a slight lag to it which means that even if the data is in Room, it will initially be null as seen is possible above. When you take the value of it then you see that it is null. It would intuitively make sense that you could use it like this but it doesn't support that.
Instead it should be used in Activities and Fragments like this:
dpViewModel.avgBaliseAJeune("À jeun").observe(this, result -> {
//Do something with 'result'
});
further reading on live data in fragments
or if you want to use Jetpack Compose then you would use something like
val avgBaliseAJeun by dpViewModel.avgBaliseAJeune("À jeun").observeAsState()
Finally I see your viewModel code is converting live data to mutablelivedata, but instead of that I recommend using the live data as a read only pipe flowing out of the repostiory/Room. Then separately make a function to change the value by inserting/updating to Room through the Dao. You can do that anywhere in the code and then the live data will observe those changes.
Google has some great codelabs to check out some samples with Room, LiveData, Flow, ViewModels (though not always all at once). Feel free to follow up here more as well!
below is my Kotlin extension function :
infix fun Int.send(data: String) {
MsgSendUtils.sendStringMsg(
this,
data
)
}
called like this :
8401 send "hi"
and my requirement is :
the caller must greater than 8400 .
How can i achieve that ?
Thanks !
In theory, you can use the syntax #receiver:IntRange(from = 8400) to apply an annotation to the receiver of a function:
infix fun #receiver:IntRange(from = 8400) Int.send(data: String) {
MsgSendUtils.sendStringMsg(this, data)
}
However, this doesn't seem to trigger the Android Lint error as you would expect. This is probably a missing feature in the linter itself when inspecting Kotlin code.
A workaround would be to declare the function differently (using this value as parameter instead of receiver):
fun send(#IntRange(from = 8400) id: Int, data: String) {
MsgSendUtils.sendStringMsg(id, data)
}
// or
fun String.sendTo(#IntRange(from = 8400) id: Int) {
MsgSendUtils.sendStringMsg(id, this)
}
Otherwise the best you could do in pure Kotlin would be to check the value at runtime:
infix fun Int.send(data: String) {
require(this >= 8400) { "This value must be greater than 8400" }
MsgSendUtils.sendStringMsg(this, data)
}
Note that, depending on what this value represents, I would advise using a more specific type instead of Int. For example, if your Int represents an ID, maybe you should instead declare a value class wrapping this integer. You can then enforce the constraints at construction time (still at runtime, but it's less error-prone).
This approach would also avoid polluting auto-completion on all integers, which can be pretty annoying on a big project given how specific this function is.
Suppose I have an instance of the following class:
data class User(val name: String, val startedOn: LocalDate, val score: BigDecimal)
Its toString() method is provided automatically and it outputs this:
val user = User("Mike", LocalDate.of(2021, 1, 2), BigDecimal.TEN)
println(user)
User(name=Mike, startedOn=2021-01-02, score=10)
Is there another standard function to provide a compilable String for an instance of any data class:
User("Mike", LocalDate.of(2021, 1, 2), BigDecimal("10"))
Of course, I can write something myself, using reflection, but I don't want to reinvent the wheel. This will allow me to write unit tests faster.
Edit: I'm trying to quickly replace real API calls with mocks. So Id like to add something my code calling the API:
val request = getRequest(...)
println(toCompilableString(request))
val response = myApi.call(request)
println(toCompilableString(response))
and use that output in my tests like this:
val request = <output from the first println>
val response = <output from the second println>
every { myApi.call(request) } returns response
TIA!
There is no built-in way to my knowledge to do this, but there are libraries that can help, such as Kotlin Poet. However, you won't get the automatic behaviour you're looking for.
If you really want to do this with pure Kotlin, the only "built-in" way I can think of right now is to override toString(). You don't have to use reflection, but you'll have to construct the String you want by hand. For instance, something like:
data class User(val name: String, val startedOn: LocalDate, val score: BigDecimal) {
override fun toString(): String {
return """User("$name", ${startedOn.toCompilableString()}, BigDecimal("$score"))"""
}
private fun LocalDate.toCompilableString() =
"LocalDate.of($dayOfMonth, $monthValue, $year)"
}
This article says that I can use Completable as return type for #Insert
But as I do that, the error occured:
error: local variable pointToInsert is accessed from within inner class; needs to be declared final
This error happens with AndoridX since Rxjava return types support included only since 2.1 version : https://issuetracker.google.com/issues/63317956#comment25
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPoint(pointToInsert: ControlPoint): Completable
So, how to make this thing work?
As this feature is completely unavailable unless you use version 2.1+, you can actually solve this problem using lower version by making some kind of adapter for you DAO:
#Dao
interface Original {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPoint(pointToInsert: ControlPoint)
}
class AdHocCompletableAdapter(private val dao: Original) {
fun insertPoint(pointToInsert: ControlPoint) =
Completable.create {
dao.insertPoint(pointToInsert)
it.onComplete()
}
}
Or create some more flexible solution (using, for instance, function composition).
I have received a JavaScript object in response to a remote HTTP request. I have a kotlin model (trait) that defines the various fields I expect on the object (the nullable ones are optional).
First, I want to do an is check to make sure my object is in fact of the expected type. I initially tried payload is MyModel but that doesn't work due to the way the is operator is written in kotlin.js.
Second, I want to cast to MyModel so I can get auto-complete, etc. on the object while I work with it. Normally, the is alone would be enough but since that doesn't work I need something for this problem as well.
I would like to avoid manually populating my object from a dynamic. I wouldn't mind doing this so much if I could use by Delegates.mapVal(...) but that requires a Map<String, Any?> and I don't know how to get my dynamic/Any? payload into a Map<String, Any?>.
1) We don't have structure check for is in performance reasons.
I don't sure that we need generic solution for this, but anyway I created issue about it, feel free to vote or star it to get updates.
2) is enough if you use smart cast, like:
if (payload is MyModel) {
// call MyModel members on payload
}
But don't forget about (1) :)
3) You can write something like:
class MapDynamic<out V>(val d: dynamic) {
public fun get(thisRef: Any, desc: PropertyMetadata): V {
return d[desc.name]
}
}
class Foo(data: dynamic) {
val field: Int by MapDynamic(data)
}
fun main(args : Array<String>) {
val f = Foo(object { val field = 123 })
println(f.field)
}
But it looks too verbose, but You can add additional logic for e.g. when data don't have requested field. And if You don't need custom logic I think cast is enough.
For the second part, the cast, you can do:
fun responseHandler(payload: dynamic) {
val myModel = payload as MyModel
}
or
fun responseHandler(payload: dynamic) {
val myModel: MyModel = payload
}
This will throw an NPE if payload is null, but it won't actually validate that the payload matches MyModel. In particular, you may end up with null fields/properties that shouldn't be if the payload was missing those fields/properties.