https://kotlinlang.org logo
#android
Title
# android
s

Sudhir Singh Khanger

06/07/2021, 6:32 AM
I am a little fuzzy on why the following method
someDbCall()
should be a
suspend
function.
Copy code
private fun someDbCall() { //some db call }
Copy code
someScope.launch {
    someDBCall()
}
Do you have a personal philosophy such as don't use suspend keyword unless the compiler forces you to as I have been reading on Stack overflow?
Copy code
someScope.launch {
    withContext(NonCancellable + <http://Dispatchers.IO|Dispatchers.IO>) { someDBCall() }
}
If you do have a philosophy of not using suspend keyword when not needed then how does construct like withContext affect using them with non-suspend keyword function.
g

gildor

06/07/2021, 7:22 AM
This approach with ad-hoc wappign withContext has a very big issue, it’s very easy to forget to do that, so as result suspend function will be blocked by this IO operation
☝️ 1
if you design your code in a way when all IO already abstracted by properly suspend functions (which use non-blocking API, or at least wrap with Dispatchers.IO it’s safe to use such functions from any suspend function and you do not worry that it will block your thread
Also this NonCancellable is pretty nasty code, which I would recommend to avoid, it’s super easy to create leak with it. If you want prevent db request from cancellation, I would recommend different approach where you move yo db calss to a separate class with own scope (so something like MyDbRepo, it has own scope on which it does all those db operations and client just awaits them)
👍 1
s

Sudhir Singh Khanger

06/07/2021, 10:01 AM
Thanks for replying. I think I am a bit lost here. 1. Your first point is that instead of wrapping a function with
withContext
at the time of usage one should prefer using it in the function itself and the reason for it is that one might forget to call it with proper dispatcher at the time of usage and would end up using it like a blocking call.
suspend fun someDbCall() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) { //some db call }
2. Creating a class with its own scope sort of like GlobalScope is something that I want to learn to do. I saw some documents on it here and here which look like the following. Approach A
Copy code
// DO inject an external scope instead of using GlobalScope.
// GlobalScope can be used indirectly. Here as a default parameter makes sense.
class ArticlesRepository(
    private val articlesDataSource: ArticlesDataSource,
    private val externalScope: CoroutineScope = GlobalScope,
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    // As we want to complete bookmarking the article even if the user moves
    // away from the screen, the work is done creating a new coroutine
    // from an external scope
    suspend fun bookmarkArticle(article: Article) {
        externalScope.launch(defaultDispatcher) {
            articlesDataSource.bookmarkArticle(article)
        }
            .join() // Wait for the coroutine to complete
    }
}
Approach B This will likely be moved Dagger module
Copy code
class MyApplication : Application() {
  // No need to cancel this scope as it'll be torn down with the process
  val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}
And
Copy code
class Repository(
  private val externalScope: CoroutineScope,
  private val ioDispatcher: CoroutineDispatcher
) {
  suspend fun doWork() {
    withContext(ioDispatcher) {
      doSomeOtherWork()
      externalScope.launch {
        // if this can throw an exception, wrap inside try/catch
        // or rely on a CoroutineExceptionHandler installed
        // in the externalScope's CoroutineScope
        veryImportantOperation()
      }.join()
    }
  }
}
3. What is your view on when to use suspend function? On a theoretical scale, I can see it means ability to pause and resume code execution but it doesn't make sense to me. Say I run some call on a db do they need to be paused/resumed? 4. Do constructs like
withContext()
treat suspend and non-suspend function differently?
For point 3 and 4 according to this blog post by Google, I might not need to switch context when using room with
suspend
keyword.
Room will use different Dispatchers for transactions and queries. These are derived from the executors you provide when building your Database or by default will use the Architecture Components IO executor. This is the same executor that would be used by LiveData to do background work.
For point 1, I think I will go with approach B which is to create a
CoroutineScope
with
SupervisorJob()
in my Dagger module so it can be injected in classes like my DB manager.
c

Colton Idle

06/07/2021, 3:40 PM
Just as a heads up you could also probably find good advice in #coroutines
✔️ 1
g

gildor

06/08/2021, 5:51 AM
1. Yes, correct. Though, for db calls you probably don’t need this, if db provides non-blocking API 2. Yes, like this, but I wouldn’t uses GlobalScope, and intead always inject scope or create scope on class level, at least it would be possible to cancel it, also easier to test. Careful with such “applicatioScope”, anyone can cancel it from any part of the app and then everything is broken. Also not sure that Approach B will work, maybe I don’t understand what you trying to achieve, but it still canncellable, also io dispatcher there looks redundand 3. Suspend it’s not about pausing/resume functions, you cannot pause your db call, better to thing about it as abstraction for any asyncronous code (or as replacement for callbacks) 4. Not sure what you mean exactly, yes, mecahnism for suspend and not suspend functions is different,withContext just replaces context for suspend functions in block, but in case when you use dispatcher, you also change thread on which this block will be called
s

Sudhir Singh Khanger

06/08/2021, 12:16 PM
That's a good point about
applicationScope
. What I ended up doing is create a
CoroutineScope
in the Database module of Dagger. That way it is unlikely to be cancelled by a future developer.
Copy code
@Singleton
  @Provides
  @Named("DbCoroutineScope")
  fun provideDbCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob())
Then I am using approach A but the only thing that I find weird is that because I am calling
.join()
so I have to make the function a suspend function which would have to be called from another suspend function.
Copy code
class ArticlesRepository(
    private val articlesDataSource: ArticlesDataSource,
    @Named("DbCoroutineScope") private val externalScope: CoroutineScope
) {
suspend fun bookmarkArticle(article: Article) {
    externalScope.launch() { articlesDataSource.bookmarkArticle(article) }.join()
}
g

gildor

06/09/2021, 3:18 AM
It’s fine to make this function suspend, it’s very useful for a client code, so it possible to suspend unti insert operation is complety, so it’s perfect case, without suspend function client wouldn’t know when insert is complete, which may require for many cases