I have the following code that calls children of the KtFile. When I go inside the definition of this children, I can see that it is defined in package com.intellij.psi under the method called getChildren() which returns an array of PsiElement.
import org.jetbrains.kotlin.psi.*
fun KtFile.getFunctionList(): List<KtNamedFunction> {
val functionList = mutableListOf<KtNamedFunction>()
children.forEach {
if (it is KtNamedFunction) functionList.add(it)
}
return functionList
}
However, I get the following error:
Exception in thread "main" java.lang.NoSuchMethodError: 'com.intellij.psi.PsiElement[] org.jetbrains.kotlin.psi.KtFile.getChildren()'
at docGenerator.KtParserKt.getFunctionList(KtParser.kt:36)
...
Why is this happening?
Related
I'm just learning Mutiny and I need to implement retry logic.
I have this code:
fun main() {
getResult()
.onFailure().invoke { t -> println("Got error: $t") }
.onFailure().retry().atMost(2)
.subscribe().with(
{ result -> println(result) },
{ t -> t.printStackTrace() }
)
}
fun getResult(): Uni<String?> {
println("Preparing result...")
return Uni.createFrom().failure(Exception("Some error happened"))
}
So, the getResult() is a function that may misbehave and needs to be called multiple times on failure.
When I run this program, this is what's happening:
Preparing result...
Got error: java.lang.Exception: Some error happened
Got error: java.lang.Exception: Some error happened
Got error: java.lang.Exception: Some error happened
java.lang.Exception: Some error happened
at MainKt.getResult(Main.kt:16)
at MainKt.main(Main.kt:4)
Obiously, the getResult() function is called only once, while the onFailure() stages actually executed three times.
Is there anything that Mutiny could help me to execute getResult() function on each failure? I sure can implement this with a simple loop, but I feel like Mutiny should already have something like this.
Unfortunately, I didn't find anything suitable in the docs.
Your Uni in getResult is created with an "immediate" item, which is cached and never computed again.
Use Uni.createFrom().failure(() -> Exception("Some error happened"))
In this case, it's a supplier, so it won't be cached but called on every attempt.
So, the right solution for this is actually using the Uni.deferred() method like this:
fun main() {
Uni.createFrom().deferred { getResult() }
.onFailure().invoke { t -> println("Got error: $t") }
.onFailure().retry().atMost(2)
.subscribe().with(
{ result -> println(result) },
{ t -> t.printStackTrace() }
)
}
Thanks to Boris the Spider, who suggested to use the deferred(), and to Clement, who clarified its use with null values.
Initially, I misinterpreted the deferred() documentation thinking it's not allowed to return a null value, but actually it's OK for a Supplier to return a Uni of null:
Uni.createFrom.deferred { Uni.createFrom().nullItem() }
What the docs really are prohibiting is returning a null instead of a Uni:
Uni.createFrom().deferred { null }
I'm trying to implement "Try again" functionality, which means, when some request failed, user will be able to tap on "Try Again" button to resend the same request again.
In short, I have BaseViewModel with
lateinit var pendingMethod: suspend () -> Unit
and
fun runAsync(tryFunction: suspend () -> Unit) {
viewModelScope.launch(errorHandler) {
try {
tryFunction()
} catch (ex: Exception) {
pendingMethod = tryFunction
}
}
}
And from view, when "Try Again" button is clicked, I call
viewModel.runAsync { viewModel.pendingMethod() }
First tap works well, but when I tap second time, it throws
StackOverflow error: stack size 8MB
and bunch of invokeSuspend(..) in the logs, which looks like there are suspend functions call each other infinitely.
Any thoughts about this?
Update:
I have fixed this by storing suspend function in extra variable like this
val temp = viewModel.pendingMethod
viewModel.runAsync { temp() }
Instead of
viewModel.runAsync { viewModel.pendingMethod() }
Your issue can be traced by following points
pendingMethod property is not initialized
tryFunction() call throws kotlin.UninitializedPropertyAccessException
Exception is caught and pendingMethod = tryFunction() which effectivelly means pendingMethod = pendingMethod() // this is recursive call
next time when you call runAsync it essentially calls pendingMethod() but this time its initialized, and since pendingMethod does nothing but call itself, you get the stack overflow error
So how to implement the try again functionality
Lets say you have a suspending function which executes the given request, it can be network call(Retrofit supports suspend keyword) or it can be database access (Room also supports suspend keyword). so your function looks something like
suspend fun executeRequest(request: RequestModel): Result
Now update your ViewModel as
// declare a live data property to notfify user that request has failed
val requestFailed: MutableLiveData<Boolean> = MutableLiveData(false)
// update runAsync
fun runAsync(request: RequestModel) = viewModelScope.launch {
try {
executeRequest(request)
}
catch (ex: Exception) {
requestFailed.postValue(true)
}
}
And in your Activity, Fragment observe the LiveData object to display the error to user
viewModel.requestFailed.observe(this, Observer{
if(it) {
// Show error toast
}
}
I am testing an event driven architecture in KTOR. My Core logic is held in a class that reacts to different Event types being emitted by a StateFlow. EventGenerators push Events into the StateFlow which are picked up by the Core.
However, when the Core attempts to respond to an ApplicationCall embedded in one of my Events I receive an ResponseAlreadySentException and I'm not sure why this would be the case. This does not happen if I bypass the StateFlow and call the Core class directly from the EventGenerator. I am not responding to ApplicationCalls anywhere else in my code, and have checked with breakpoints that the only .respond line is not being hit multiple times.
MyStateFlow class:
class MyStateFlow {
val state: StateFlow<CoreEvent>
get() = _state
private val _state = MutableStateFlow<CoreEvent>(CoreEvent.NothingEvent)
suspend fun update(event: CoreEvent) {
_state.value = event
}
}
My Core class:
class Core(
myStateFlow: MyStateFlow,
coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.IO
) {
init {
CoroutineScope(coroutineContext).launch {
myStateFlow.state.collect {
onEvent(it)
}
}
}
suspend fun onEvent(event: CoreEvent) {
when(event) {
is FooEvent {
event.call.respond(HttpStatusCode.OK, "bar")
}
...
}
}
}
One of my EventGenerators is a Route in my KTOR Application class:
get("/foo") {
myStateFlow.update(CoreEvent.FooEvent(call))
}
However, hitting /f00 in my browser returns either an ResponseAlreadySentException or an java.lang.UnsupportedOperationException with message: "Headers can no longer be set because response was already completed". The error response can flip between the two while I'm tinkering with different attempted solutions, but they seem to be saying the same thing: The call has already been responded to before I attempt to call call.respond(...).
If I change my Route instead to call the Core.onEvent() directly, hitting /foo returns "bar" in my browser as is the intended behaviour:
get("/foo") {
core.onEvent(CoreEvent.FooEvent(call))
}
For completeness, my dependency versions are:
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
implementation "io.ktor:ktor-server-netty:1.4.1"
Thank you in advanced for any insight you can offer.
I understand that in Kotlin there is no such thing as "Non-local variables" or "Global Variables" I am looking for a way to modify variables in another "Scope" in Kotlin by using the function below:
class Listres(){
var listsize = 0
fun gatherlistresult(){
var listallinfo = FirebaseStorage.getInstance()
.getReference()
.child("MainTimeline/")
.listAll()
listallinfo.addOnSuccessListener {
listResult -> listsize += listResult.items.size
}
}
}
the value of listsize is always 0 (logging the result from inside of the .addOnSuccessListener scope returns 8) so clearly the listsize variable isn't being modified. I have seen many different posts about this topic on other sites , but none fit my usecase.
I simply want to modify listsize inside of the .addOnSuccessListener callback
This method will always be returned 0 as the addOnSuccessListener() listener will be invoked after the method execution completed. The addOnSuccessListener() is a callback method for asynchronous operation and you will get the value if it gives success only.
You can get the value by changing the code as below:
class Demo {
fun registerListResult() {
var listallinfo = FirebaseStorage.getInstance()
.getReference()
.child("MainTimeline/")
.listAll()
listallinfo.addOnSuccessListener {
listResult -> listsize += listResult.items.size
processResult(listsize)
}
listallinfo.addOnFailureListener {
// Uh-oh, an error occurred!
}
}
fun processResult(listsize: Int) {
print(listResult+"") // you will get the 8 here as you said
}
}
What you're looking for is a way to bridge some asynchronous processing into a synchronous context. If possible it's usually better (in my opinion) to stick to one model (sync or async) throughout your code base.
That being said, sometimes these circumstances are out of our control. One approach I've used in similar situations involves introducing a BlockingQueue as a data pipe to transfer data from the async context to the sync context. In your case, that might look something like this:
class Demo {
var listSize = 0
fun registerListResult() {
val listAll = FirebaseStorage.getInstance()
.getReference()
.child("MainTimeline/")
.listAll()
val dataQueue = ArrayBlockingQueue<Int>(1)
listAll.addOnSuccessListener { dataQueue.put(it.items.size) }
listSize = dataQueue.take()
}
}
The key points are:
there is a blocking variant of the Queue interface that will be used to pipe data from the async context (listener) into the sync context (calling code)
data is put() on the queue within the OnSuccessListener
the calling code invokes the queue's take() method, which will cause that thread to block until a value is available
If that doesn't work for you, hopefully it will at least inspire some new thoughts!
Hello I have following problem.
I am trying to mock call of injected executor
to execute given Callable immediately. Later in test arguments of methods called inside Callable are captured and arguments are asserted. Mock example see bellow.
Maven 3, jdk 10-slim, mockk 1.9
//this task should be executed by executor
private val taskCaptor = slot<Callable<Boolean>>()
private val asyncTaskExecutor: LazyTraceThreadPoolTaskExecutor = mockk<LazyTraceThreadPoolTaskExecutor>().apply {
//this was my 1st try, but resutt was java.lang.InstantiationError: java.util.concurrent.Callable
//every { submit(capture(taskCaptor)) } returns CompletableFuture.completedFuture(taskCaptor.captured.call())
//every { submit(any()) } returns CompletableFuture.completedFuture(true)
every { submit(ofType(Callable::class)) } returns FutureTask<Boolean>(Callable { true })
}
later on I have changed Callable interface to implementation, which I have created in tested class and I got another exception.
With same code as above exceptions was
java.lang.InstantiationError: java.util.concurrent.Future
which is return type of submit method.
Is my approach to mocking wrong?
not sure if this is the best way to implemented but for me it worked this way:
private val taskCaptor = slot<Callable<Boolean>>()
private val asyncTaskExecutor: LazyTraceThreadPoolTaskExecutor = mockk<LazyTraceThreadPoolTaskExecutor>().apply {
every { submit(ofType(Callable::class)) } returns mockFuture
every { mockFuture.get() } returns true
}