What is the lifetime of coroutineScope in Kotlin? - android

The Code A is from the project architecture samples at https://github.com/android/architecture-samples
1: I don't know if the function activateTask(task: Task) need to be wrapped with runBlocking just like Code B. I'm afraid that activateTask(task: Task) maybe not be run if the object of DefaultTasksRepository is destroyed quickly.
2: Normally I run coroutines in ViewModel.viewModelScope, I don't know whether the ViewModel.viewModelScope will be destroyed when I finish the app, and whether the coroutines running in ViewModel.viewModelScope will be destroyed too. If so, I think it will be bad, some long time coroutines such as writing data to remote server will be cancel.
3: And more, the function activateTask in Code A is a coroutines function, it can invoke another coroutines function directly, so I think the Code A+ is correct, right?
Code A
import kotlinx.coroutines.coroutineScope
...
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher) {
coroutineScope {
launch { tasksRemoteDataSource.activateTask(task) }
launch { tasksLocalDataSource.activateTask(task) }
}
}
override suspend fun clearCompletedTasks() {
coroutineScope {
launch { tasksRemoteDataSource.clearCompletedTasks() }
launch { tasksLocalDataSource.clearCompletedTasks() }
}
}
...
}
Code A+
import kotlinx.coroutines.coroutineScope
...
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher) {
tasksRemoteDataSource.activateTask(task)
tasksLocalDataSource.activateTask(task)
}
override suspend fun clearCompletedTasks() {
tasksRemoteDataSource.clearCompletedTasks()
tasksLocalDataSource.clearCompletedTasks()
}
...
}
Code B
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello,")
}

You should not use runBlocking in any coroutine application, it blocks the thread.
If you really want to make activateTask non-cancellable there is a factory implementation of NonCancellable already in the stdlib
And you should not use coroutineScope wrapper inside the withContext, as a newly created CoroutineScope along with a new job is already passed as receiver within withContext.
Implement your activateTask like this:
override suspend fun activateTask(task: Task) = withContext<Unit>(ioDispatcher + NonCancellable) {
launch { tasksRemoteDataSource.activateTask(task) }
launch { tasksLocalDataSource.activateTask(task) }
}
In this way it will be called on the IODispatcher but will not be cancellable since the Job element of the resulting context does not provide functionality to cancel it.
ViewModelScope runs till your application is destroyed, more info and lifecycle chart is here. If you want to run some very important tasks, then use other dispatchers.
Yes code A+ is completely correct
PS: You should not implement runBlocking in a coroutine application, its default implementation is just the event loop.
runBlocking is the way to bridge synchronous and asynchronous code
Better implementation of main function should be:
suspend fun main() = coroutineScope {
// code here
}
It runs on the CommonPool, and if it suspends another coroutine could reuse the same thread.

Related

How to start a coroutine on main thread without using GlobalScope?

Whenever I want to start a coroutine on a main thread,
fun main(args: Array<String>) {
GlobalScope.launch {
suspededFunction()
}
}
suspend fun suspededFunction() {
delay(5000L) //heavy operation
}
GlobalScope is highlighted, and always taunt that its usage is delicate and require care.
What delicacies are involved with GlobalScope, and importantly how can I start a coroutine without using GlobalScope?
To start coroutine without using a GlobalScope, one can do as:
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
suspededFunction()
}
As mentioned in comments, some classes already have scopes available, like ViewModel class as viewModelScope.
in Activity or Fragment you can as follows:
//not recommended to use co-routines inside fragment or activity class
// this is just for example sack shown here.
// otherwise you have to do all your processing inside viewmodel
class Fragment : CoroutineScope by MainScope() {
...
override fun onDestroy() {
super.onDestroy()
cancel()
}
}
Kotlin already created some scope. you can use it according to your situation. and you also create your own scope. but I suggest in the beginning it is better to use that already created
check official documentation https://developer.android.com/topic/libraries/architecture/coroutines
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
lifecycleScope.launch {
//for activity
}
}
//for viewmodel
suspend fun abc() = viewModelScope.launch {
}

How to Inject Other Dispatchers to MainCoroutineRule?

In the Google Codelab for Coroutines, we are shown a MainCoroutineScopeRule. Within the rule, it explains that this rule can be extended to other dispatchers in addition to Dispatchers.Main:
override fun starting(description: Description?) {
super.starting(description)
// If your codebase allows the injection of other dispatchers like
// Dispatchers.Default and Dispatchers.IO, consider injecting all of them here
// and renaming this class to `CoroutineScopeRule`
//
// All injected dispatchers in a test should point to a single instance of
// TestCoroutineDispatcher.
Dispatchers.setMain(dispatcher)
}
My question is, how exactly are we to inject the other dispatchers? Does this assume that we're using dependency injection? If so, what if I'm not using DI, can I still extend this rule to the other dispatchers? I don't see anything in the kotlinx-coroutines-test library that allows me to set the TestCoroutineDispatcher to the other dispatchers. So, there's this:
Dispatchers.setMain(dispatcher)
...but not this:
Dispatchers.setIO(dispatcher) // Or Default, etc.
Am I instead expected to rewrite my suspend functions to take in a dispatcher as a parameter:
suspend doSomeIO(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
launch(dispatcher) {
// Some long-running IO operation
}
}
You are correct in that this does assume you are injecting your dispatchers. If you are not using the main dispatcher, you should be injecting the dispatcher in order to test it properly.
The way you wrote the suspend function is one way to do it, if you want to force that particular function to be on the Dispatchers.IO thread. However, then you will end up having nested launches.
Instead of that, I would just pass the dispatcher in to a viewModel, and let the viewmodel decide how to call the suspend function.
//Your version:
suspend fun doSomeIO(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
launch(dispatcher) {
// Some long-running IO operation
}
}
class MyViewModel(val dispatcher: CoroutineDispatcher = Dispatchers.IO: ViewModel() {
init {
viewModelScope.launch {
doSomeIO(dispatcher) // here you are launching one coroutine inside the other
}
}
}
// Instead try this:
suspend fun doSomeIO() {
// Some long-running IO operation
}
class MyViewModel(val dispatcher: CoroutineDispatcher = Dispatchers.IO: ViewModel() {
init {
viewModelScope.launch(dispatcher) {
doSomeIO()
}
}
}

Kotlin coroutines block main thread in Android

I am new in Kotlin and coroutines. I have a fun in my activity and inside it, check User username and password and if its true, return Users object.
every thing is Ok. but when I press button, my activity blocked and wait for response of Users login.
I use this fun:
private fun checkLogin() : Boolean {
runBlocking {
coroutineScope {
launch {
user = viewModel.getUserAsync(login_username.text.toString(), login_password.text.toString()).await()
}
}
if(user == null){
return#runBlocking false
}
return#runBlocking true
}
return false
}
It's my ViewModel :
class LoginViewModel(app: Application) : AndroidViewModel(app) {
val context: Context = app.applicationContext
private val userService = UsersService(context)
fun getUserAsync(username: String, password: String) = GlobalScope.async {
userService.checkLogin(username, password)
}
}
UsersService:
class UsersService(ctx: Context) : IUsersService {
private val db: Database = getDatabase(ctx)
private val api = WebApiService.create()
override fun insertUser(user: Users): Long {
return db.usersDao().insertUser(user)
}
override suspend fun checkLogin(username: String, pass: String): Users? {
return api.checkLogin(username, pass)
}
}
interface IUsersService {
fun insertUser(user: Users) : Long
suspend fun checkLogin(username: String, pass: String): Users?
}
And it is my apiInterface:
interface WebApiService {
#GET("users/login")
suspend fun checkLogin(#Query("username") username: String,
#Query("password")password: String) : Users
How can I resolve issue of blocking my activity when waiting for retrieve data from server?
You should never use runBlocking in an Android app. It is only meant to be used in the main function of a JVM app or in a test to allow the use of coroutines that complete before the app exits. It otherwise defeats the purpose of coroutines, because it blocks until all of its lambda returns.
You also shouldn't use GlobalScope, because it makes it tricky to cancel your jobs when the Activity closes, and it starts the coroutine in a background thread instead of the main thread. You should use a local scope for the Activity. You can do this by creating a property in your activity (val scope = MainScope()) and canceling it in onDestroy() (scope.cancel()). Or if you use the androidx.lifecycle:lifecycle-runtime-ktx library you can just use the existing lifecycleScope property.
And if you always await your async job before returning, then your whole function will block until you get the result, so you have taken a background task and made it block the main thread.
There are a couple ways you can go about fixing this.
Make the ViewModel expose a suspend function, and the activity calls it from a coroutine.
class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
// withContext(Dispatchers.Default) makes the suspend function do something
// on a background thread and resumes the calling thread (usually the main
// thread) when the result is ready. This is the usual way to create a simple
// suspend function. If you don't delegate to a different Dispatcher like this,
// your suspend function runs its code in the same thread that called the function
// which is not what you want for a background task.
suspend fun getUser(username: String, password: String) = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
}
//In your activity somewhere:
lifecycleScope.launch {
user = viewModel.getUser(login_username.text.toString(), login_password.text.toString())
// do something with user
}
With proper viewmodel encapsulation, the Activity really shouldn't have to launch coroutines like this. The user property should be a LiveData in the ViewModel that the activity can observe. So then the coroutines only need to be launched from within the ViewModel:
class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
init {
fetchUser()
}
private fun fetchUser(username: String, password: String) = viewModelScope.launch {
val result = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
_user.value = result
}
}
//In your activity somewhere:
viewModel.user.observe(this) { user ->
// do something with user
}
I know internet/wifi fetching or post needs to be in somekind of background/asynctask.
Have you tried using #Background/#Uithread from Android Annotations.
It will requiere you to put some dependencies in the gradle.
But it's one way i have been dealing with services.
Here is the original link for the original DOC of it
https://github.com/androidannotations/androidannotations/wiki/WorkingWithThreads#background

Why is ViewModelScoped coroutine unusable after ViewModel onCleared() method called

I am sharing an ActivityScoped viewModel between multiple Fragments in my current Android application.
The viewModel employs Coroutine Scope viewModelScope.launch{}
My issue is the .launch{} only works until the owning ViewModel onCleared() method is called.
Is this how ViewModel scoped coroutines are supposed to work?
Is there an approach I can use to "Reset" the viewModelScope so that .launch{} works following the onCleared() method being called?
heres my code::
Fragment
RxSearchView.queryTextChangeEvents(search)
.doOnSubscribe {
compositeDisposable.add(it)
}
.throttleLast(300, TimeUnit.MILLISECONDS)
.debounce(300, TimeUnit.MILLISECONDS)
.map { event -> event.queryText().toString() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe { charactersResponse ->
launch {
viewModel.search(charactersResponse.trim())
}
}
.
.
.
override fun onDetach() {
super.onDetach()
viewModel.cancelSearch()
compositeDisposable.clear()
}
ViewModel
suspend fun search(searchString: String) {
cancelSearch()
if (TextUtils.isEmpty(searchString)) {
return
}
job = viewModelScope.launch {
repository.search(searchString)
}
}
fun cancelSearch() {
job?.cancelChildren()
}
.
.
.
override fun onCleared() {
super.onCleared()
repository.onCleared()
}
What am I doing wrong?
UPDATE
If I amend my launch code to this
job = GlobalScope.launch {
repository.search(searchString)
}
It solves my issue, however is this the only way to achieve my desired result?
I was under the impression GlobalScope was "Bad"
following a cal to onCleared() my viewModelScoped cororoutine Launch stops executing
That's a feature, not a bug.
Once the ViewModel is cleared, you should not be doing anything in that ViewModel or whatever its LifecycleOwner was. All of that is now defunct and should no longer be used.
however is this the only way to achieve my desired result?
The correct solution is to get rid of the code from the ViewModel. If you are expecting some background work to go past the lifetime of an activity or fragment, then that code does not belong in the activity/fragment or its associated viewmodels. It belongs in something that has a matching lifetime to the work that you are trying to do.
repository.onCleared()
This method should not belong to the Repository.
In fact, the Repository should not be stateful.
If you check Google's samples, the Repository creates a LiveData that contains a Resource, and the reason why this is relevant is because the actual data loading and caching mechanic is inside this resource, triggered by LiveData.onActive (in this sample, MediatorLiveData.addSource, but technically that's semantically the same thing).
.subscribe { charactersResponse ->
launch {
viewModel.search(charactersResponse.trim())
The Fragment shouldn't be launching coroutines. It should say something like
.subscribe {
viewModel.updateSearchText(charactersResponse.trim())
}
and also
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java, factory)
viewModel.searchResults.observe(viewLifecycleOwner, Observer { results ->
searchAdapter.submitList(results)
})
}
Then ViewModel would
class MyViewModel(
private val repository: MyRepository
): ViewModel() {
private val searchText = MutableLiveData<String>()
fun updateSearchText(searchText: String) {
this.searchText.value = searchText
}
val searchResults: LiveData<List<MyData>> = Transformations.switchMap(searchText) {
repository.search(searchText)
}
}
And that's all there should be in the ViewModel, so then the question of "who owns the coroutine scope"? That depends on when the task should be cancelled.
If "no longer observing" should cancel the task, then it should be LiveData.onInactive() to cancel the task.
If "no longer observing but not cleared" should retain the task, then ViewModel's onCleared should indeed govern a SupervisorJob inside the ViewModel that would be cancelled in onCleared(), and the search should be launched within that scope, which is probably only possible if you pass over the CoroutineScope to the search method.
suspend fun search(scope: CoroutineScope, searchText: String): LiveData<List<T>> =
scope.launch {
withContext(Dispatchers.IO) { // or network or something
val results = networkApi.fetchResults(searchText)
withContext(Dispatchers.MAIN) {
MutableLiveData<List<MyData>>().apply { // WARNING: this should probably be replaced with switchMap over the searchText
this.value = results
}
}
}
}
Would this work? Not sure, I don't actually use coroutines, but I think it should. This example however doesn't handle the equivalent of switchMap-ing inside the LiveData, nor with coroutines.

Testing coroutines in the presenter class

I'm struggling to test my presenter which is calling a suspended function from the repository layer as follow:
override fun viewCreated() {
launch {
val hasPermission = permissionChecker.execute() //suspended function
if (hasPermission) {
foo()
} else {
view.bar()
}
}
The presenter is also extending this interface:
interface CoroutinePresenter: CoroutineScope {
val job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
fun stopAllActiveJobs() {
coroutineContext.cancelChildren()
}
And the suspended function is defined as follow:
suspend fun execute() : Boolean = withContext(Dispatchers.IO) {
return#withContext class.foo()
}
Everything is working as expected in the app but when I tried to write some unit test I noticed that whenever I call the piece of code inside launch the thread is switched but the test doesn't wait for the execution. This is the implementation of the test:
#Test
fun `Test of Suspended Function`() = runBlocking {
presenter.viewCreated()
then(view).should().bar()
...
}
I also added the suggested library for testing kotlinx-coroutines-test but the result is still the same with it. I also tried to follow this suggestion and also implementing something like this but still no luck.
I think the problem is the actual creation of another thread whenever the launch is invoked in the presenter and the test doesn't actually know how to wait for it. I also tried to return a Job and invoking the job.join() but it fails with a NullPointerException.
Hope you guys can help me.
I found a solution for that:
following this tutorial, I've setup both
#Before
fun setup() {
Dispatchers.setMain(Dispatchers.Unconfined)
...
}
#After
fun tearDown() {
Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
}
And by running the entire launch block of the presenter class inside a runBlocking statement in the test. The problem was related also to a not reported exception inside the suspended function that was actually not mocked but it was invisible to my eyes.
Now everything is working fine.
Firstly, I strongly recommend that give your coroutineContext as a Parameter like that:
class CoroutinePresenter(coroutineContext: CoroutineContext): CoroutineScope {
init{
_coroutineContext = coroutineContext
}
override val coroutineContext: CoroutineContext
get() = _coroutineContext
// Your Methods
}
In your real environment:
#YourScope
#Provides
fun providesCoroutinePresenter(coroutineContext:CoroutineContext ){
return CoroutinePresenter()
}
#YourScope
#Provides
fun providesCoroutineContext(){
return Dispatchers.Main + job
}
During the unit test:
#Before
fun setUp() {
coroutinePresenter CoroutinePresenter(Dispatchers.Unconfined)
}
#Test
fun `Should do something`(){
//WHEN
coroutinePresenter.doSomething(params)
//THEN
do your assertions
}
For more please check SOLID Principles and for this case D

Categories

Resources