https://kotlinlang.org logo
#coroutines
Title
# coroutines
v

Vincent Williams

08/21/2020, 4:19 PM
Copy code
private fun fetchData() = viewModelScope.launch(dispatcher) {
        repository.fetchData()
                .onEach { 
                   //success
                }
                .catch { Timber.e(it) }
                .launchIn(this)
    }
Why is this still crashing my app when an exception occurs? should
catch
be catching that and not crashing?
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 4:27 PM
What's the exception?
v

Vincent Williams

08/21/2020, 4:28 PM
the exception in this case was just a network error thrown by the repository. Using CoroutineExceptionHandler works as expected and catches the error without crashing the app
I figured
catch
would be equivalent
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 4:29 PM
Hm, I would think so too.
Can you put a breakpoint in
catch
and see if it's even getting the exception?
v

Vincent Williams

08/21/2020, 4:30 PM
yes im trying that now. I think it is getting the exception
ok nvm it is not. breakpoint is not being hit
but the exception is being thrown by
fetchData
network call so catch should definitely be catching that I think...
its also logging
FATAL EXCEPTION: DefaultDispatcher-worker-3
before crashing
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 4:35 PM
Huh, weird
v

Vincent Williams

08/21/2020, 4:35 PM
guess ill stick to CoroutineExceptionHandler for now? This definitely seems like a bug though
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 4:36 PM
I don't think this is related, but one odd thing about your code is you're launching a coroutine that doesn't do anything but immediately launch another coroutine. You can get rid of one of those (eg just call
collect
directly instead of
onEach/launchIn
, and surround the collect with a try/catch)
But yea if you can isolate and reproduce, I would file a bug.
v

Vincent Williams

08/21/2020, 4:43 PM
hmm I switched to using
onEach
instead of
collect
because I had some cases where I needed multiple collects in one launch
Copy code
private fun subscribeToViewModel() = lifecycleScope.launch(Dispatchers.Main) {
        viewModel.viewState.collect { onViewStateEvent(it) }
        viewModel.navigationEvents.collect { onNavigationEvent(it) }
    }
I thought this was equivalent?
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 4:43 PM
Nope, in that case
navigationEvents
won’t start collecting until
viewState
finishes.
But then you can just do
viewModel.viewState.onEach{…}.launchIn(lifecycleScope)
, you don’t need the outer call to
launch{}
v

Vincent Williams

08/21/2020, 4:45 PM
oh I see you dont need to call the launchIn at all... ok I think its possible the exception was for some reason being propagated to the outer launch that had no exception handler
so if I remove the outer launch, it says "fetchData should only be called from a suspend function" even though I have the launchIn
seems like you still need the outer launch
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 5:00 PM
fetchData
shouldn’t be a suspend function if it’s returning a
Flow
.
v

Vincent Williams

08/21/2020, 5:04 PM
Oh ya you are probably right. It's a bit of a messy implementation as I wasnt sure the best way to handle this. I need to fetch data from the API, store it in the DB, then return a flow from the DB to listen for further changes later on
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 5:05 PM
v

Vincent Williams

08/21/2020, 5:05 PM
The suspend part was so that the API call can be made. Maybe there's a better way to handle this to just return flow?
I have not seen that but I'll take a look!
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 5:05 PM
That can all be done inside the Flow itself, but requires multicasting. That Store library does all that for you though.
v

Vincent Williams

08/21/2020, 5:08 PM
That looks really nice but would require a bit of refactoring to implement in my current project. Do you know how to handle this without store?
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 5:09 PM
Something like
Copy code
flow {
  val data = fetchData() // suspending call
  storeDataInDb(data) // suspending call
  val dbStream: Flow<…> = streamDataFromDb() // not suspending
  emitAll(dbStream)
}
v

Vincent Williams

08/21/2020, 5:11 PM
Ok awesome! I knew there was a way to make this a flowable but have only just recently started using flowables and wasn't sure exactly how
s

streetsofboston

08/21/2020, 5:15 PM
The
catch
handles an exception thrown by the returned Flow. From this thread, I understand that the
repository.fetchData()
throws the exception? If that's the case, the
catch
won't handle this, since it'll only handle exceptions from the returned Flow; no Flow is returned, an exception is thrown instead inside the
viewModelScope.launch
call.
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 5:19 PM
Ah, you’re right. I didn’t realize
fetchData
was throwing the exception directly.
v

Vincent Williams

08/21/2020, 5:34 PM
ah ya that makes more sense. So this will work fine right? because the exceptions happen within the flow
Copy code
flow {
  val data = fetchData() // suspending call
  storeDataInDb(data) // suspending call
  val dbStream: Flow<…> = streamDataFromDb() // not suspending
  emitAll(dbStream)
}
ok ya just tested it, this works as expected
z

Zach Klippenstein (he/him) [MOD]

08/21/2020, 6:19 PM
LG
2 Views