Will Dispatchers.Main inside Work Manager blocks User Main thread? - android

I created a class for worker, inside worker class I need to have api call and save the api response in room database. Before api call, I will check for the connectivity. For that I referred to this article
https://medium.com/#alexzaitsev/android-viewmodel-check-network-connectivity-state-7c028a017cd4
im using this connectivity in multiple places of my application, so im reusing it inside MyWorker class.
My worker looks like below:
class MyWorker(private val context: Context, parameters: WorkerParameters) :
Worker(context, parameters) {
lateinit var connectionLiveData: ConnectionLiveData
override fun doWork(): Result {
connectionLiveData = ConnectionLiveData(applicationContext)
GlobalScope.launch(Dispatchers.Main) {
connectionLiveData.observeForever { isConnected ->
isConnected?.let {
Api.enqueue(object : Callback<Response> {
override fun onFailure(call: Call<Response>, t: Throwable) {
t.printStackTrace()
}
override fun onResponse(call: Call<Response>, response: Response<Response>) {
if (response.isSuccessful) {
// save in db
}
}
}
}
}
}
}
I'm using this api call, so that user can initiate this api call and can navigate to other fragments or activity. Meanwhile, it will update the db in background without disturbing the user.
My question is, from my knowledge I know "Worker" will run in background. And "Dispatchers.Main" will run in Main thread.
If I use "Dispatchers.Main" inside "Worker", then my api call will happen in Main thread? If it runs in main thread, then my usage of worker is of no use? I'm using "Dispatchers.Main" to check the network, so it is not allowing me to use "Dispatchers.IO"(becoz observeForever), so my worker is waste?
I run the app, it works fine. I couldn't notice that it blocks the UI or main thread. I suspect it may lag in the future, when api response takes time. So can you explain which thread it will execute?
Any android studio tool is there to see in which thread all code executing?

Related

Android WorkManager - Can I pass input data to a Worker that runs periodically?

In my app I start a WebSocketWorker tasks that runs periodically every 15 minutes. As the name implies, it contains a WebSocket for listening to a socket in the background:
// MainApplication.kt
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
val work = PeriodicWorkRequestBuilder<WebSocketWorker>(15, TimeUnit.MINUTES).build()
workManager.enqueueUniquePeriodicWork("UniqueWebSocketWorker", ExistingPeriodicWorkPolicy.KEEP, work)
}
The WebSocketWorker contains the following logic:
#HiltWorker
class WebSocketWorker #AssistedInject constructor(
#Assisted appContext: Context,
#Assisted workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
inner class MyWebSocketListener : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
Timber.d("The message sent is %s", text)
// do sth. with the message
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
t.localizedMessage?.let { Timber.e("onFailure: %s", it) }
response?.message?.let { Timber.e("onFailure: %s", it) }
}
}
override suspend fun doWork(): Result {
try{
// code to be executed
val request = Request.Builder().url("ws://***.***.**.***:8000/ws/chat/lobby/").build()
val myWebSocketListener = MyWebSocketListener()
val client = OkHttpClient()
client.newWebSocket(request, myWebSocketListener)
return Result.success()
}
catch (throwable:Throwable){
Timber.e("There is a failure")
Timber.e("throwable.localizedMessage: %s", throwable.localizedMessage)
// clean up and log
return Result.failure()
}
}
}
As you can see, in the Worker class I set the WebSocket and everything is fine. Listening to the socket works.
Now, I also want to add the "sending of messages" functionality to my app. How can I reuse the websocket created in WebSocketWorker? Can I pass input data to the WebSocketWorker that runs in the background ?
Let's say I have a EditText for typing the message and a Button to send the message with a setOnClickListener attached like this:
binding.sendButton.setOnClickListener {
// get message
val message = binding.chatMessageEditText.text.toString()
// check if not empty
if(message.isNotEmpty()) {
// HOW CAN I REUSE THE WEBSOCKET RUNNING PERIODICALLY IN THE BACKGROUND?
// CAN I PASS THE MESSAGE TO THAT WEBSOCKET ?
// OR SHOULD I CREATE A DIFFERENT WORKER FOR SENDING MESSAGES (e.g.: a OneTimeRequest<SendMessageWorker> for sending messages ?
}
}
From the documentation, I know that you need to build Data objects for passing inputs and so on but there was no example which showcased how to pass input to a worker running periodically in the background.
My experience is saying that you can. Basically you "can't" interact with the worker object via the API. It is really annoying.
For example, with the JS you have the option to get a job and check the parameters of the job. There is no such option with the work. For example, I want to check what is the current state of the restrictions - what is satisfied, what is not. Nothing like this. You can just check states, cancel and that is almost all.
My suggestions is that it is because the WorkManager is a "facade/adapter" over other libraries like JS. It has it's own DB to restore JS jobs on device restart and stuff like this, but beside that if you want to interact with the internals I guess it was just too complicated for them to do so they just skipped.
You can just inject some other object and every time the work can ask it for it's data. I don't see other option.

Kotlin Coroutines how to achieve to call api in right way

Hey I want to call api from object class. I am new in Coroutines. I tried some code, but i am not sure is it correct way of doing it or not.
Inside LoginHelper there is function called logout have more that one function. I want to excute api call first. then i want to excute other function inside logout.
In Mainactivity I am calling LoginHelper.logout it will finish then i need to excute other line. But i don't want to make suspend function because it's using other place as well.
Also i got a errorProcess:
com.dimen.app, PID: 12496
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1605)
Session.kt
interface Session{
#DELETE("/session/delete")
fun deleteSession(): Call<Void>
}
SessionRepository.kt
suspend fun deleteSession(): RequestResult<Void> {
return apiCall(api.deleteSession())
}
RequestResult is a Sealed Class
sealed class RequestResult<out T : Any> {
data class Success<out T : Any>(): RequestResult<T>
data class Error(): RequestResult<Nothing>()
fun result(success: (data: T?) -> Unit),error: (error: Error) -> Unit)
}
MainActivity.kt
private fun setupLogout() {
logoutButton.setOnClickListener {
LoginHelper.logout() // need to wait untill this finish
// more logic here....
}
}
LoginHelper.kt
object LoginHelper {
fun logout() {
logD("logout")
deleteSession() // need to wait untill this finish and then excute more function....
}
private fun deleteSession() {
runBlocking{
apiCall.deleteSession().execute()
}
}
}
Never use runBlocking in an Android app unless you know exactly what you're doing. It's the wrong choice 99% of the time because it defeats the purpose of using coroutines. Blocking means the current thread waits for the coroutine to run its asynchronous code. But you cannot block the main thread because that freezes the UI.
Since your LoginHelper is an object or singleton, it needs its own CoroutineScope if it's going to launch coroutines.
You can make deleteSession() a suspend function so it can call the api.deleteSession() suspend function.
You can make logout() launch a coroutine to sequentially delete the session and subsequently perform other tasks. And you can make it return the launched Job so other classes can choose whether or not to simply start the logout, or to start and wait for the logout in a coroutine.
object LoginHelper {
private val scope = CoroutineScope(SupervisorJob() + CoroutineName("LoginHelper"))
fun logout(): Job = scope.launch {
logD("logout")
deleteSession()
// .... more functions that happen after deleteSession() is complete
}
private suspend fun deleteSession() {
Tokenclass.getToken()?.let {
logE("token ::-> $it")
apiCall.deleteSession(it).execute()
}
}
}
If you want the outside class to be able to wait for the logout to complete, it can call join() on the returned Job in its own coroutine, for example:
logoutButton.setOnClickListener {
lifecycleScope.launch {
LoginHelper.logout().join()
// more logic here....
}
}
If you don't need to wait for it in the activity, you don't need to start a coroutine, and you don't need to call join().

How do I pause execution in Kotlin whilst APIs complete

I am trying to write a simple app in Android Studio using Kotlin. It is a very steep learning curve for me, but I am almost there. My final problem is getting the app to wait for the APIs to complete before moving the next Intent.
I have three calls each uploading data via my API. They are called from a button and only when the three are uploaded, should the button send the user to the next intent/screen.
My API calls are working and I can see the data in the database. However, since enqueue is asynchronous the calls are firing and the code is moving on the start the next intent before the data is present.
The code below is executed 3 times (once for each upload). I realise this is probably not the best way to do it, but I'm trying to get it working before I finesse the code.
I thought that perhaps I could have a variable, UploadedReadCount, that I increment in the onResponse, but this doesn't seem to be working properly.
Could someone offer some advice as to how I should be pausing the code until the APIs complete? For example, is there an enqueue methos that isn't async?
ReadInterface.create().AddRead("new", rFuel, rRegister, rReadDate, rRead)
.enqueue(object : Callback<UploadedRead> {
override fun onFailure(call: Call<UploadedRead>, t: Throwable) {
Log.d("Err: ", t.localizedMessage!!)
t.printStackTrace()
}
override fun onResponse(call: Call<UploadedRead>, response: Response<UploadedRead>) {
Log.d("Response: ", response.body().toString())
val p = response.body()?.APIResult!![0]
msgShow("Gas read " + rRead.toString() + " uploaded")
UploadedReadCount += 1
}
})
while ( UploadedReadCount < 3) {
Log.d("Waiting ", UploadedReadCount.toString() + " reads uploaded...")
}
val intent = Intent(this, Billing::class.java).apply {
putExtra("ReadDate", txtReadDate.text.toString())
}
startActivity(intent)
In most cases you don't want to pause execution while API call returns, Instead you want to follow the reactive model, that is when you call API you specify some callbacks (onResponse, onFailure), and once these callbacks are invoked then you react.
code is moving on the start the next intent before the data is
present.
Move all of your code that depends on data received from API in onResponse or onFailure methods (callbacks), When API is ready with some response one of those callbacks will be invoked and then depending on the data that you receive from API you can continue your work.
is there an enqueue methos that isn't async?
There are options available to call an API in blocking manner but I don't think that is good idea. Instead of doing a blocking API call, you should try to do reactive programming that is as soon as any callback (onResponse, onFailure) is called only then you continue.
There is an alternative to enqueue that is suspending instead of async, so you can call your code sequentially without blocking the main thread in a coroutine. The function is await() and it returns the successful result or throws an HttpException on failure.
But to run three requests in parallel, you need to use the async coroutine builder. This can be done by mapping a list of Calls to async calls that await the individual results, and then using awaitAll() on the list of Deferreds to wait for all three. So, it's more complicated than just running sequential code in a coroutine, but I think this is still easier than trying to run and wait for three parallel calls using callbacks.
I'm not exactly sure what your other two calls are so I'll just make up some and assume this function already has all the data it needs to make the calls. I also don't know how you want to handle failure, so I'm just making it stop early if any of the three calls fail.
lifecycleScope.launch {
val requests: List<Call<UploadedRead>> = listOf(
ReadInterface.create().AddRead("new", rFuel, rRegister, rReadDate, rRead),
ReadInterface.create().AddRead("new2", rFuel, rRegister, rReadDate, rRead),
ReadInterface.create().AddRead("new3", rFuel, rRegister, rReadDate, rRead)
)
val responses: List<UploadedRead> = try {
coroutineScope { // any failure in this block cancels them all
requests.map { async { it.await() } } // run them simultaneously with async
.awaitAll()
}
} catch (e: HttpException) {
Log.d("Err: ", e.localizedMessage.toString())
printStackTrace(e)
return#launch
}
// Do something with the list of three UploadedReads here.
}
I just duplicated the functionality of your code above, but it doesn't look like you're using the response for anything and you have an unused variable p.
Edit: If this is a pattern you use frequently, this helper function might be useful. I didn't check this thoroughly or test it.
/**
* Await the result of all the Calls in parallel. Any exception thrown by any item
* in the list will cancel all unfinished calls and be rethrown.
*/
suspend fun <T: Any> Iterable<Call<T>>.awaitAll(): List<T> =
coroutineScope { map { async { it.await } }.awaitAll() }
//...
lifecycleScope.launch {
val requests: List<Call<UploadedRead>> = listOf(
//...
)
val responses: List<UploadedRead> = try {
requests.awaitAll()
} catch (e: HttpException) {
//...
return#launch
}
//...
}

Why does viewModelScope.launch run on the main thread by default

While I was learning coroutines and how to properly use them in an android app I found something I was surprised about.
When launching a coroutine using viewModelScope.launch { } and setting a breakpoint inside the launch lambda I noticed my app wasn't responsive anymore because it was still on the main thread.
This confuses me because the docs of viewModelScope.launch { } clearly state:
Launches a new coroutine without blocking the current thread
Isn't the current thread the main thread ? What is the whole purpose of launch if it doesn't run on a different thread by default ?
I was able to run it on anther thread using viewModelScope.launch(Dispatchers.IO){ } which works as I was expecting, namely on another thread.
What I am trying to accomplish from the launch method is to call a repository and do some IO work namely call a webservice and store the data in a room db. So I was thinking of calling viewModelScope.launch(Dispatchers.IO){ } do all the work on a different thread and in the end update the LiveData result.
viewModelScope.launch(Dispatchers.IO){
liveData.postValue(someRepository.someWork())
}
So my second question is, is this the way to go ?
ViewModelScope.launch { } runs on the main thread, but also gives you the option to run other dispatchers, so you can have UI & Background operations running synchronously.
For you example:
fun thisWillRunOnMainThread() {
viewModelScope.launch {
//below code will run on UI thread.
showLoadingOnUI()
//using withContext() you can run a block of code on different dispatcher
val result = novel.id = withContext(Dispatchers.IO) {
withsomeRepository.someWork()
}
//The below code waits until the above block is executed and the result is set.
liveData.value = result
finishLoadingOnUI()
}
}
For more reference, I would say there are some neat articles that will help you understand this concept.
Medium link that explains it really neat.
So my second question is, is this the way to go ?
I would expect two things to be different in your current approach.
1.) First step would be to define the scheduler of the background operation via withContext.
class SomeRepository {
suspend fun doWork(): SomeResult = withContext(Dispatchers.IO) {
...
}
}
This way, the operation itself runs on a background thread, but you didn't force your original scope to be "off-thread".
2.) Jetpack Lifecycle KTX provides the liveData { coroutine builder so that you don't have to postValue to it manually.
val liveData: LiveData<SomeResult> = liveData {
emit(someRepository.someWork())
}
Which in a ViewModel, you would use like so:
val liveData: LiveData<SomeResult> = liveData(context = viewModelScope.coroutineContext) {
withContext(Dispatchers.IO) {
emit(someRepository.someWork())
}
}
And now you can automatically trigger data-loading via observing, and not having to manually invoke viewModelScope.launch {}.
The idea behind main thread being default is you can run UI operations without having to change the context. It is a convention I guess Kotlin coroutine library writers have chosen
Suppose if by default if the launch runs on IO thread then the code would look like this
viewmodelScope.launch {
val response = networkRequest()
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Suppose if by default if the launch runs on Default thread then the code would look like this
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Since the default launch is on main thread, now you have to do below
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
renderUI(response)
}
To avoid the messy code initializing the response with null, we can also make the networkRequest as suspend and wrap the business logic of networkRequest() function in withContext(Dispatchers.IO) and that's how lot of people write their networkRequest() function as well! Hope this makes sense
One of the main reasons it runs on Main thread, is because it's more practical for general use in ViewModel, like murali kurapati wrote. It was a design choice.
It's also important to note that all suspending functions should be "main safe" according to best pracices. That means, that your repository should switch coroutine context accordingly, like so:
class someRepository(private val ioDispatcher: CoroutineDispatcher) {
suspend fun someWork() {
withContext(ioDispatcher) {
TODO("Heavy lifting")
}
}
}

Making Espresso wait for network calls, database work and other asynchronous tasks

I have an application that uses UseCases to access a Repository. The UseCases subscribe on a thread pool, the repository makes network requests using OkHttp's thread pool and the repository also writes to a database that uses the Schedulers.io() thread pool.
For certain versions of Android I am having difficulties running UI tests using Espresso because my Idling Resources seem to go idle before all of the network requests / database writes are finished.
AFAIK, my setup uses three distinct thread pools. My thought was that if, in my UI test, I create an Idling Resource that watches each thread pool, then when I make a call to my Repository via my UseCase, Espresso will wait until all of the thread pools are idle before it advances my test code.
Here's an example of my test code:
val wrapped: IdlingResourceScheduler = Rx2Idler.wrap(application.component.providesIoScheduler(), "IO Scheduler")
val otherWrapped: IdlingResourceScheduler = Rx2Idler.wrap(application.component.providesJobScheduler(), "J O B")
val resource = arrayOf<IdlingResource>(okHttpIdlingResource, wrapped, otherWrapped)
idlingRegistry.goIdle(*resource) {
onView(withId(R.id.toolbar_main))
.check(matches(isDisplayed()))
}
What I think I'm doing in the above code is registering an idling resource for my database thread pool, my OkHttp thread pool and my UseCase thread pool. So my thought was that at least one of those thread pools would always be 'active' when a call is made to my Repository and once the Repository has returned its value, all of the thread pools would be idle and Espresso would know that it is ok to advance the test. Like I said above, that seems to be true for devices running Marshmallow (and maybe even higher versions) but does not work for devices on Pie.
Here is a simplified example of my UseCase:
repository
.authenticate(params.username, params.password)
.subscribeOn(My Use Case Thread Pool)
.observeOn(Main Thread)
Here is a simplified example of the auth method in my Repository class:
Single.zip(networkLayer.authenticate(username, password),
userDao.getUserInfo(),
BiFunction { t1, t2 -> Result(t1,t2) }
.flatMap {
Single.just(deleteAllDataFromDatabase())
.flatMap {
networkLayer.getRestOfData()
}
}
What I really can't understand is why the test runs on some versions but not others. I also can't understand why, if I watch all of the thread pools for idleness, how can Espresso advance the test? Won't at least one of my thread pools be doing something (either fetching from the network, writing to the database, or doing something in the Use Case? What am I missing?
You need to move all the async logic in the same thread. You can do that using this testing rule for RxJava/RxAndroid
class ImmediateSchedulersRule : TestRule {
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
#Throws(Throwable::class)
override fun evaluate() {
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
try {
base.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
}
}
}
And add this rule to your testing classes:
#get:Rule
var immediateRule = ImmediateSchedulersRule()
By the way, if you can refactor your use case, a better solution could be to pass a Scheduler object to each of your use cases.
Something like that:
Have the schedulers in its own file and create a default one:
interface AppScheduler {
fun io(): Scheduler
fun computation(): Scheduler
fun main(): Scheduler
}
class DefaultAppScheduler : AppScheduler {
override fun io() = Schedulers.io()
override fun computation() = Schedulers.computation()
override fun main() = AndroidSchedulers.mainThread()
}
Your use case has the AppScheduler injected (or passed manually if you are not using Dagger or similar), and in normal application you will pass the DefaultAppScheduler implementation.
class TestUseCase #Inject constructor(
private val scheduler: AppScheduler,
private val yourRepository: YourRepository
) : BaseUseCase<Unit, List<Object>>() {
//I have omitted some details here but you can get the point
fun createObservable(params: Unit): Flowable<List<Object>> {
return yourRepository.loadDataList()
.subscribeOn(scheduler.io())
}
}
Then in your test code you will create a new AppScheduler implementation just for testing using the trampoline one:
class TestAppScheduler : AppScheduler {
override fun io() = Schedulers.trampoline()
override fun computation() = Schedulers.trampoline()
override fun main() = Schedulers.trampoline()
}
In this way your test will wait before proceeding.

Categories

Resources