Chris Fillmore
01/10/2022, 2:47 PMJob
state changes? (i.e. going from New -> Active -> Cancelled/Completed)Joffrey
01/10/2022, 2:49 PMJoffrey
01/10/2022, 2:50 PMNew -> Active
transitionChris Fillmore
01/10/2022, 2:50 PMJoffrey
01/10/2022, 2:52 PMChris Fillmore
01/10/2022, 2:52 PMinvokeOnStart
Chris Fillmore
01/10/2022, 2:55 PMJoffrey
01/10/2022, 2:56 PMJob
API for this, though? It looks like your use case calls for a custom interfaceChris Fillmore
01/10/2022, 2:59 PMChris Fillmore
01/10/2022, 3:00 PMChris Fillmore
01/10/2022, 3:01 PMChris Fillmore
01/10/2022, 3:03 PMgildor
01/10/2022, 3:45 PMChris Fillmore
01/10/2022, 3:46 PM.join()
will also start the jobZach Klippenstein (he/him) [MOD]
01/10/2022, 8:47 PMChris Fillmore
01/10/2022, 9:11 PMYou’re trying to model a separate lifecycle without a coroutine job?No. To give a concrete example, I already have this implemented for Apollo subscriptions. It looks roughly like:
class ApolloSubscription(
private val client: ApolloClient,
private val coroutineScope: CoroutineScope,
) {
private val _job = MutableStateFlow(subscriptionJob())
val job = _job.asStateFlow()
private var subscriptionCall: ApolloSubscriptionCall? = null
init {
job
.onEach {
it.invokeOnCompletion {
subscriptionCall?.cancel()
subscriptionCall = null
if (/* should reconnect */) {
_job.value = subscriptionJob()
}
}
}
.launchIn(coroutineScope)
}
private fun subscriptionJob() = coroutineScope.launch(start = CoroutineStart.LAZY) {
suspendCancellableCoroutine {
subscriptionCall = client.subscribe(...).apply {
execute(object: ApolloSubscriptionCall.Callback {
// Callback methods here that handle connection/websocket events,
// and resume the continuation on completion/failure
})
}
}
}
}
Chris Fillmore
01/10/2022, 9:14 PMval subscription = ApolloSubscription(...)
// Connect to the websocket
subscription.job.value.start()
// OR
subscription.job
.onEach {
it.start()
}
.launchIn(myClientScope)
Chris Fillmore
01/10/2022, 9:17 PMJob
is just meant to model, at a high level, whether or not there is an open connection, and its state. This seems in line with the definition for Job
, from the docs
Conceptually, a job is a cancellable thing with a life-cycle that culminates in its completion.
Zach Klippenstein (he/him) [MOD]
01/10/2022, 9:18 PMZach Klippenstein (he/him) [MOD]
01/10/2022, 9:18 PMtheThat was my point, that a coroutine job shouldn’t model anything other than a coroutine jobis just meant to modelJob
Chris Fillmore
01/10/2022, 9:22 PMChris Fillmore
01/10/2022, 9:24 PMZach Klippenstein (he/him) [MOD]
01/10/2022, 9:28 PMJob
type, even if that ends up just wrapping a coroutine job internally, to give yourself room to maneuver in the future.Chris Fillmore
01/10/2022, 9:28 PMJob
is not stable for inheritance, according to the docs. That’s not something I want to pursue.Zach Klippenstein (he/him) [MOD]
01/10/2022, 9:29 PMZach Klippenstein (he/him) [MOD]
01/10/2022, 9:31 PMApolloSubscription
is responsible for creating, cancelling, and recreating subscription calls, but not starting them. Why the asymmetry?Chris Fillmore
01/10/2022, 9:31 PMStateFlow<Job>
just allows client code to observe an open connection as it changes state, reconnects, etc. This seems unremarkable to me.
My original ask was whether I could observe a Job going from New -> Active state.Zach Klippenstein (he/him) [MOD]
01/10/2022, 9:33 PMChris Fillmore
01/10/2022, 9:33 PMZach Klippenstein (he/him) [MOD]
01/10/2022, 9:42 PMApolloSubscription
uses coroutines to manage calls, and how it does so, is an implementation detail of that class) – this is a pretty well-documented design best practice.
But this whole discussion is moot anyway: If you need to observe when a coroutine job is started, i don’t think there are any hooks for that. You’ll have to do that some other way.Chris Fillmore
01/10/2022, 9:57 PMinterface StatefulConnection {
fun connect()
fun disconnect()
val state: StateFlow<State>
sealed interface State {
object Initializing
object Connecting
object Connected
... others ...
}
}
I find this creates a lot of intermediate state and is a risk for bugs. What I really want to know is if the connection is still alive or if there was an error, and I want to be able to cancel the Job. Tracking the Job that’s running the socket connection does this just fine. It doesn’t feel like shoehorning at all. (I’ve done plenty of shoehorning before… this doesn’t feel like it, though I could be wrong.)Chris Fillmore
01/10/2022, 9:59 PMChris Fillmore
01/10/2022, 10:00 PM// Working from memory here...
suspend fun HttpClient.wss(request: HttpRequest, block: suspend CoroutineScope.() -> Unit)
Chris Fillmore
01/10/2022, 10:01 PMblock
, which I found I liked.Chris Fillmore
01/10/2022, 10:02 PMlaunch { /* entire lifetime of your connection is contained here */ }