I’ve been trying to wrap a library that needs to h...
# coroutines
r
I’ve been trying to wrap a library that needs to have an initialize method called. This method takes a callback and calls that callback when an object is ready. Now I also want to expose suspend methods that internally use the object that the callback receives. So these suspend methods should suspend waiting for the object until we can then execute normally. I’m not sure how can I suspend waiting for the object in a class-wide scope, as initialize will only be called once at init block. Any ideas? Thanks!
j
One option would be to use a class property of type
Deferred<YourObject>
. • All suspend functions could call
deferred.await()
, which suspends until the object is ready. • To set the value during initialization, you can use
this.deferred = CompletableDeferred<YourObject>()
and in the initialization callback, use
this.deferred.complete(yourObject)
. Not sure it’s the most elegant option, but I think this solves your problem (if I understood correctly)
Another option (which I think may be better), is to change your API a little bit. Wrap the initialization in a suspending function that uses
suspendCoroutine
and resumes when the initialization callback is called. Its return value would be the object passed to the callback by the library. This way you suspend only once, and then you can create a class from it that exposes normal methods using your object. I guess this is a simpler approach, but I understand that you might have considered it already and were not looking for this.
☝️ 1
r
Yeah, that second one is kind of difficult with the API I’m working on. It’s actually a method in an abstract class that receives the object. I’m actually passing this object onto another class that will expose the suspend’s. Will try the first approach. Thank you!
j
Mmmh so if I understand correctly the library you’re wrapping defines an abstract class that runs initialization upon construction (cannot be controlled), and calls an abstract method as a callback when the object is ready?
r
Yes, it’s an abstract class that calls the abstract method when the object is ready. And I can control when to initialise. It’s actually my wrapper that will call initialise upon construction
j
If you have another class anyway (which doesn’t extend the library’s abstract class), why not use the second approach actually?
Copy code
// LIBRARY CODE

class LibInitResult

abstract class LibClass {
    
    fun initialize() {
        val initResult = LibInitResult()
        onInitialized(initResult)
    }
    
    abstract fun onInitialized(result: LibInitResult)
}

// WRAPPER

class WrapperClass(private val libInitResult: LibInitResult) {
    // expose non-suspending functions using the libInitResult
}

suspend fun buildInitializedWrapper(): WrapperClass = 
    suspendCoroutine { cont ->
        val initializer = object : LibClass() {
            override fun onInitialized(result: LibInitResult) {  
                cont.resume(WrapperClass(result))
            }
        }
        initializer.initialize()
    }
Instead of a top level function
buildInitializedWrapper
, you could also use a suspending static factory method (in the companion object of the wrapper class). This would allow a better syntax
WrapperClass.create()
or
WrapperClass.initialize()
, whatever makes more sense in your actual domain.
r
Well I omitted some details as I thought it would be easier to explain my situation. I’m hiding the initialization inside the wrapper class and not exposing it to the outside. So I have the wrapper that has a private anonymous library instance. Wrapper calls initialise. Wrapper also has a method (this is the method that will have to wait for the object) that when called, gives you another class that exposes the API methods (this class takes the object)
j
Ok I see, from your previous descriptions it sounded like you triggered initialization upon construction of the object, which is IMO bad practice. Users usually expect instantiation to be cheap, that’s why I thought a factory method might be a better API, because factory methods can do some extra work and also (conveniently) can be suspending.
Anyway I guess you have a few options now, so I’ll let you decide on the API you want to expose 😉 If you have trouble exposing the API you want, we can discuss more of course!
r
First option goes perfectly with the mental model I had for this. But I was not sure what was the construct for it. I’ll go with that approach unless I find some other way to model all of this, legacy code does not help 😅
thank you for your help!
👍 1