0

I am currently working on an api for my client application which needs to process http requests (using unirest) asynchronously as of now. I am new to CompletableFuture and haven't worked with anything similar up to this point. I was wondering whether the following structure makes sense:

// Request.kt (simplified)

class Request<T>(
   // other variables relevant to the request such as body or path ...
   private val responseType: Class<T>
) {

  fun prepareRequest(action: (HttpRequest<*>) -> U): U {
     // preprocesses the request, adds body if necessary and returns the request itself
  }

  fun executeAsync(action: (HttpResponse<T>) -> Unit) {
     prepareRequest { req ->
        action(req.asObjectAsync(responseType).get()) // Unirest call that (still) freezes the UI
     }
  }

  // Builder logic ...
}

// ApiClient.kt (simplified)

abstract class ApiClient {

   protected fun <T> executeAsync(req: Request<T>, action: (T) -> Unit) {
      req.executeAsync { res ->
         if (res.isSuccess){
            action(res.body)
         } else {
            throw RuntimeException("res != 200")
         }
      }
   }
}

// AuthClient.kt (simplified)

class AuthClient : ApiClient() {

   fun signin(email: String, password: String, onSuccess: () -> Unit) {
      executeAsync(
         Request.builder(TokenModel::class.java)
            .post("/signin")
            .body(SignInModel(email, password))
            .build()
      ) {
         onSuccess() // this is going to refresh the UI, once the http request has been executed
      }
   }
}

As the call to get on CompletableFuture freezes the UI I thought of including an Executor or a Thread instead so that executeAsync in Request becomes the following:

fun executeAsync(action: (HttpResponse<T>) -> Unit) {
   prepareRequest { req ->
      Executors.newSingleThreadScheduledExecutor().execute {
         action(it.asObjectAsync(responseType).get())
      }
   }
}

Is my structure overly complex or does it have to be like that? Do I need the Thread/Executor or can this be achieved in a different way?

Thomas Thaler
  • 55
  • 1
  • 5
  • 1
    Related: https://stackoverflow.com/a/30250308/2189127 – James_D Feb 14 '23 at 12:00
  • So that basically means CompletableFuture itself is not threaded and needs to be packed inside of one? – Thomas Thaler Feb 14 '23 at 12:21
  • 1
    `CompletableFuture` is "threaded" (its async methods are executed on a background thread), but `CompletableFuture.get()` is a blocking call, so it must not be called on the FX Application Thread. It's probably more convenient to encapsulate the work done by `prepareRequest()` in a `Task` in your JavaFX application. Then you can leverage `Task.setOnSucceeded()` etc, and just execute the `Task` via an executor. – James_D Feb 14 '23 at 12:50
  • Okay, that makes sense. However, the time-consuming Task is not preformed by `prepareRequest()` but rather by the call to the api (`req.asObject()` respectively `req.asObjectAsync()` if performed asynchronously) – Thomas Thaler Feb 14 '23 at 13:01
  • 1
    Yes, sorry. (I don't write code in kotlin so it was a little difficult for me to untangle where the time-consuming code was.) One other note; don't create a new executor on every call (no matter whether you work with `Task`s or `CompletableFutures`). Create something like a cached thread pool executor and reuse it. Creating an executor is a fairly heavy process. – James_D Feb 14 '23 at 13:54
  • Ok, thanks. Yes, I took that already into consideration. This was only a draft to make it work in the first place. One other question is still open: what is the point of having `CompletableFuture` then at all? I could easily call unirest (i.e. `req.asObject()`) in synchronous mode inside the executor without having to rely on asynchronous mode. – Thomas Thaler Feb 14 '23 at 14:25
  • 1
    Correct. The `Task` basically replaces the `CompletableFuture`, in that they both encapsulate the long-running process. The `Task` is aware of the FX Application Thread, so it "knows" how to process the results on the correct thread to update the UI. – James_D Feb 14 '23 at 15:23
  • 3
    When you want to update the UI, you must do it on the JavaFX Application Thread. When you have a `CompletableFuture` encapsulating an operation already running in a background thread, you’d do that like `theFuture.thenAcceptAsync(result -> /*update UI using result*/, Platform::runLater);` (just translated to Kotlin). No additional executor needed. – Holger Feb 14 '23 at 16:15
  • With respect to Kotlin, you can use coroutines to do blocking operations on the FXAT. It works quite well, but I'm not sure it's a replacement for Task. Here's an example: https://www.pragmaticcoding.ca/kotlin/kotlin_for_javafx#coroutines – DaveB Feb 14 '23 at 16:20
  • Maybe some ideas from [here](https://stackoverflow.com/questions/51955550/remove-tableview-entries-when-status-change) can help. – SedJ601 Feb 14 '23 at 16:25

0 Answers0