Hi all, got a question on Kotlin/Coroutine, does v...
# android
d
Hi all, got a question on Kotlin/Coroutine, does viewModelScope cancel all child coroutine onCleared()? Here's my view model (use in Profile Fragment)
Copy code
class profileViewModel: ViewModel() {
    val profileRepository: ProfileRepository
    fun subscribeToProfile(id: Int) {
        viewModelScope.launch {
            profileRepository.subscribeToProfile(id)
        }
    }
}
My repository class
Copy code
class ProfileRepository {
    fun subscribeToProfile(id: Int) {
        withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
            .....       // Does this get cancelled onCleared()?
        }
    }
}
It seems to me the function in repository class still running despite I've navigated away from Profile Fragment. I've tried to debug on the view model and override the onCleared() function and call
.cancel()
Copy code
override fun onCleared() {
    super.onCleared()
    viewModelScope.cancel()
}
I was able to breakpoint in
onCleared()
and execute the
.cancel()
but it doesn't cancel the job in repository. Am I missing any steps here?
On side note, within the
withContext
is a flow, the flow is still collecting after the
viewModelScope
is cancelled. Doing some google search shows I need to explicitly cancel the flow as well?
g
Copy code
fun subscribeToProfile(id: Int) {
        withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
it’s invalid code, subscribeToProfile must be suspend to use withContext
shows I need to explicitly cancel the flow as well
What kind flow?
also is operation cancellable or not depends what kind code your wrap, you cannot cancel blocking function
d
Yup sorry it was a suspend function, I've cleared out most of the unrelevant stuff
g
now it depends on what is inside of your with context
d
It's calling to a backend api using gRPC, and it returns a
flow<UserProfile>()
which I store/update my current user profile locally
g
yes, it should be cancelled, if flow implemented correctly and collection should be stopped
you can try to check it on kotlin playground
d
Copy code
stub.subscribeToUserProfile(request)
    .asFlow()
    .collect { updateUserProfile(it) }
Haven't play with the playground before, let me give it a go
Just one thing I'm wondering, do I need to explicitly cancel the flow? Or it's part of the cancellation of the coroutine?
g
Copy code
viewModelScope.cancel()
it looks unnecessary, standard built-in view model scope cancelled automatically onCleared
do I need to explicitly cancel the flow
No, collect is cancellable suspend function, flow will be cancelled if collect is cancelled
essentially all suspend functions are cancellable if implemented correctly
https://kotlinlang.slack.com/archives/C0B8M7BUY/p1601551599238100?thread_ts=1601549231.224400&amp;cid=C0B8M7BUY it’s more interesting how flow created in subscribeToUserProfile is implemented
d
That's actually generated by the gRPC protoful file...
Suspecting it's not implemented properly?
looks like probably something to do with the generated gRPC client. I've tried
Copy code
.collect {
    cancel()
}
But it's still collecting
I'll look into it further, thanks!
g
Would be nice to see a full example to verify
d
Sure,
Copy code
class profileViewModel: ViewModel() {
    val profileRepository: ProfileRepository
    fun subscribeToProfile(id: Int) {
        viewModelScope.launch {
            profileRepository.subscribeToProfile(id)
        }
    }
}

class ProfileRepository {
    suspend fun subscribeToProfile(id: Int) {
        withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
             val channel = ManagedChannelBuilder.forAddress(serviceUrl, 5001)
                .usePlaintext()
                .build()
             val stub = UserProfileGrpc.newBlockingStub(channel)
             val request = SubscribeUserProfileRequest
                .newBuilder()
                .setId(id)
                .build()
             stub.subscribeToUserProfile(request)
                .asFlow()
                .collect { updateUserProfile(it) }
        }
    }
}
I'm using dagger-hilt for DI and I made the repository singleton (is this a good design?). Not sure if that makes a difference.
g
too many implementation-delated things
😅 1
but in general looks that it should work
Though, withContext(Dispatchers.IO) looks suspicios, it shouldn’t be needed
d
My bad, was trying to make sure to include things might be relevant
Hmm isn't viewModelScope is default to Dispatchers.Default? And it might be executing on the main thread? I thought using
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
would make sure the following code executes in IO thread instead
g
viewModelScope is default to Dispatchers.Main, not Default
d
Let me give it a shot without the explicit context
g
no, it shouldn’t change anything
my point is that if this function is not blocking, IO context is not neede
d
It is blocking, my UI freezes when I navigate to profile fragment
g
soo, if it’s blocking, then you cannot cancel it
😨 1
blocking code is not cancellable
coroutine is cancelled, but there is no way to cancel thread which is blocked
d
Hmm are you familiar with gRPC? What should be the setup?
g
only if blocking call supports thread interruption, but coroutine doesn’t interrupt thread
d
I'm doing a server side streaming...
g
a bit
i would try to understand what is blocking
d
Alright at least I learned something!
g
I believe you problem is `UserProfileGrpc.newBlockingStub
it’s blocking
d
Yeah I'm thinking the same, but the documentation says it's for unary and server side streaming calls
g
you probably need newStub
d
I'll give it a shot!
g
newStub “Creates a new async stub that supports all call types for the service”
as I see subscribeToUserProfile is essentially blocking thread, until stream is cancelled, and asFlow there works just as listener
d
Now it requires me to pass in a
streamObserver
along with the request
g
right, it asyncronous
d
Alright this sounds really the cause of the problem!
Okay I'll look it up how to do that
g
I mentioned this problem with blocking calls in one of my first messages %) https://kotlinlang.slack.com/archives/C0B8M7BUY/p1601551257236700?thread_ts=1601549231.224400&amp;cid=C0B8M7BUY
👍 1
d
Guess I took the easy route doing it on blockingStub
My apologies, I didn't realized it was actually a blocking call
Thought it was just the way how gRPC name the client, doesn't have anything to do with actual "blocking"
And the doco says support server side streaming, thought server side streaming is non-blocking by nature
g
no, not necessary
it still can block thread where you listen it
it’s just also notify callback about new messages
d
Cool cool, thank heaps! I'm learning a lot from you, as you answer my previous question as well!
I'm still having trouble to make to work correct, could you help me take a look again?
Copy code
class profileViewModel: ViewModel() {
    val profileRepository: ProfileRepository
    fun subscribeToProfile(id: Int) {
        viewModelScope.launch {
            profileRepository.subscribeToProfile(id)
        }
    }
}

class ProfileRepository {
    suspend fun subscribeToProfile(id: Int) {
        val stub = UserProfileGrpc.newStub(channel)

        // Build request

        stub.subscribeToUserProfile(request, object: StreamObserver<UserProfile> {
            override fun onNext(value: UserProfile?) {
                CoroutineScope(coroutineContext).launch {   // This is correct? updateUserProfile is a suspend fun
                    updateUserProfile(value)
                }
            }
        })
    }
}
I tried
CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>)
and it works, but it has the same profile, the
updateUserProfile()
still get executed when I navigate away from profile fragment
All good, got it figured out. I need to use
UserProfileGrpcKt.UserProfileCoroutineStub
which is generated after importing the dependency
io.grpc:grpc-kotlin-stub
. This comes with build in coroutine support. I've been using the java stub the whole time.
g
Yep, I remember, there is a new official coroutines support
👍 1