I'm giving a try to Kotlin Coroutines inside an Android app, specifically I've imported Kotlin Coroutine Adapter for Retrofit.
Kotlin Coroutine Adapter changes Retrofit interface to return a Deferred<T> instead of Call<T>.
What I don't understand is how to launch this Deferred in a particular CoroutineContext that I want to. Consider following code:
class MyViewModel #Inject constructor(
private val foo: Foo,
#Named("ui") private val uiContext: CoroutineContext,
#Named("network") private val networkContext: CoroutineContext
) : ViewModel() {
fun performSomeJob(param: String) {
launch(uiContext) {
try {
val response = foo.bar(param).await()
myTextView.setText(response.name)
} catch (error: Throwable) {
Log.e(error)
}
}
}
Where foo.bar(param) returns Deferred<SomeModel>.
This code works, but I'm not sure on what CoroutineContext this foo.bar(param) is being executed (CommonPool??).
How to explicitly specify, that I want foo.bar(param) to be executed in a networkContext?
val response = async(networkContext) { foo.bar(param) }.await()
This code doesn't work, because response is evaluated to Deferred<SomeModel> instead of SomeModel (which I want to achieve).
The foo.bar() call doesn't start another coroutine, it just wraps the native Retrofit Call so that its state changes get propagated to Deferred. Retrofit manages its own threads to perform its operations and this works just as it would without the coroutine wrapper. If you have a specific concern, you can manage it by configuring Retrofit in the usual way.
The only thing that should matter to you is that your coroutine is executing in the UI context.
Related
So here's the deal . This is the ApiInterface:
interface ApiInterface {
#GET("api/fetch-some-info")
suspend fun fetchSomeInfo(
#Query("some_query_param") queryParam: String,
): Call<Data>
}
Here is how I generate it
new Retrofit.Builder()
.client(mOkHttpClient)
.addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().build()))
.baseUrl(url)
.build().create(AdMediationV2.class);
here is Data class
#JsonClass(generateAdapter = true)
data class Data(
#Json(name = "data")
val info: String)
Now I am trying to call the function and receive data using KotlinExtensions for Retrofit, I am using implementation 'com.squareup.retrofit2:retrofit:2.9.0'
This is how I am trying to call this function. I want to keep a reference of the job so I might be able to cancel it later.
val job = CoroutineScope(Dispatchers.IO).async{
api.fetchSomeInfo("Param").await().let { data->
//handle response}
But I get this error
java.lang.IllegalArgumentException: Unable to create converter for retrofit2.Call<Data>
According to documentation I dont even need to add Call to interface, but unless I do I cannot call .await(). It is marked as red and this is what it says
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public suspend fun <T : Any> Call<TypeVariable(T)>.await(): TypeVariable(T) defined in retrofit2
public suspend fun <T : Any> Call<TypeVariable(T)?>.await(): TypeVariable(T)? defined in retrofit2
What am I doing wrong?
Note: The Api functions properly, when used in java code, call.enqueue() it works fine.
It also works just fine in this code :
var call : Deferred<Data>? = null
MainScope().launch {
call = async(Dispatchers.IO) {
api.fetchSomeInfo("Param")
}
call?.await().let {//handle reponse}
The function in your interface should return Data instead of Call<Data> since it's a suspend function. And since it's a suspend function, there will be no need for calling await() on anything. You can just call fetchSomeInfo() directly in a coroutine, without any concern about what dispatcher it is called from either.
interface ApiInterface {
#GET("api/fetch-some-info")
suspend fun fetchSomeInfo(
#Query("some_query_param") queryParam: String,
): Data
}
//...
val job = someCoroutineScope.launch {
try {
val data = api.fetchSomeInfo("Param")
// handle data
} catch (exception: Exception) {
// handle errors
}
}
Alternatively, you could remove the suspend keyword from your function so it does have to return a Call<Data> that you will then have to call await() on, but that's just adding convolution, so don't do that.
A couple notes about your coroutine... it is a code smell to create a coroutine scope just to launch a single coroutine and then immediately lose the reference. You should be launching from a persistent CoroutineScope that you presumably will be cancelling when you want it to go out of scope.
And when you call a Retrofit API (either by using a suspend function or calling await() on a Call object), you must wrap it in try/catch to handle any of the many possible errors that might occur.
So, I have a following function which does a basic request using Ktor client to get a list of users,
suspend fun createRequest(): List<User>? {
return withContext(Dispatchers.IO) {
try {
val client = HttpClient(CIO)
val response: HttpResponse = client.get("http://10.0.2.2:9999/users")
client.close()
val str = response.readText()
val itemType = object : TypeToken<List<User>>() {}.type
Gson().fromJson<List<User>>(str, itemType)
} catch (e: Exception) {
null
}
}
}
Now, I use this as following,
runBlocking {
val res = async {createRequest()}
val users = res.await()
Log.v("_APP_", users.toString())
}
But then I read runBlocking should be used in testing and debugging and is not recommended for production. then what do I use instead of runBlocking?
createRequest() is a suspend function and it must be called from a coroutine. There are a couple of coroutine builders:
runBlocking - It is not recommended for production because it blocks the current thread.
launch - launches a new coroutine concurrently with the rest of the code, which continues to work independently.
async - creates a coroutine and returns its future result as an implementation of Deferred.
In Android there are a couple of ways to launch a coroutine, please refer to these docs.
To launch a coroutine you should have an instance of CoroutineScope, it can be viewModelScope (from the docs), lifecycleScope (from the docs) or custom instance. The sample code will look similar to:
scope.launch {
val users = createRequest()
Log.v("_APP_", users?.toString())
}
There is a lot of information out there on architecture components, kotlin and coroutines but nowhere I can find an example using all those things together.
I'm struggling on how to use android's architecture components as described here together with coroutines. I have an idea but feel uncertain if it's the correct way of implementating this architectural style.
I'm trying to use the view model + repository pattern together with retro fit and coroutines.
I have the following repository:
class FooRepostiroy(private val fooHttpService: FooHttpService) {
suspend fun someMethod() : SomeResult {
val response = fooHttpService.someRemotCall() // which is also a suspending method using retrofit-2
// process response, store it using room and return SomeResult data object
Then I use the FooRepository from my ViewModel but because someMethod is a suspending method I need to wrap it in a coroutine scope:
class FooViewModel(private val fooRepositoru : FooRepository) : ViewModel() {
private var someMethodJob : Job? = null
val result : MutableLiveData<SomeResult> = MutableLiveData()
fun someMethod() {
someMethodJob = viewModelScope.launch {
result.value = fooRepositoru.someMethod()
}
}
override fun onCleared() {
super.onCleared()
someMethodJob?.cancel()
}
Then in the fragment or activity I can observe the view model result
fooViewModel.result.observe(viewLifecycleOwner, Observer {
Starting from my repository layer and below everything can be a suspending function. Then from the view model I can call any suspending function but never have a publicly exposed suspending function in my view model.
Is this the correct or proper way to incorporate coroutines with the view model architecture ?
Is this the correct or proper way to incorporate coroutines with the view model architecture?
Yes!
Every instance of ViewModel has its own ViewModelScope.
The purpose of ViewModelScope is to run the jobs during the life cycle of that ViewModel and take care of automatic cancelation of running coroutine jobs in case the parent Activity/Fragment of ViewModel is destroyed.
Any running jobs under ViewModelScope will be canceled when the ViewModel will be destroyed.
Read more here
private var someMethodJob : Job? = null
val result : MutableLiveData<SomeResult> = MutableLiveData()
fun someMethod() {
someMethodJob = viewModelScope.launch {
result.value = fooRepositoru.someMethod()
}
}
You can ditch all of that and just say
val result: LiveData<SomeResult> = liveData {
emit(fooRepository.someMethod())
}
And then observe result.
I have switched my context to Dispatcher.Main to show UI but data fetched on runBlocking but unable to display in RecylerView
runBlocking {
var fruits = fetchFruitList()
withContext(Dispatchers.Main){
recyclerView.adapter = FruitAdapter(fruits);
}
}
what am I doing wrong and what is the appropriate way to return data from one Dispatcher to another.
I have tried another way
GlobalScope.launch {
withContext(Dispatchers.IO){
var fruits = arrayOf("Grapes","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple").toList()
return#withContext
}
recyclerView.adapter = FruitAdapter(fruits)
}
but in above way I have to declare fruits as global whereas I don't want to have it global to work. Is there a way to return data from one 'dispatcher queue to another
I have to fetch data from Api (IO operation) and display that data in RecyclerView(Main Thread Operation)
That's because you must switch the context after the data is fetched:
GlobalScope.launch(Dispatchers.IO){
var fruits = arrayOf("Grapes","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple").toList()
withContext(Dispatchers.MAIN){
recyclerView.adapter = FruitAdapter(fruits)
}
}
Edit According to the comments:
For the runBlocking check out the documentation first paragraph.
Runs a new coroutine and blocks the current thread interruptibly until
its completion. This function should not be used from a coroutine. It
is designed to bridge regular blocking code to libraries that are
written in suspending style, to be used in main functions and in
tests.
Secondly, you ask for the GlobalScope usage. Yea, if you are doing coroutines in Android you should avoid that. Reasons here.
How to launch a coroutine in Android Activity/Fragment?
First I suggest to use in the ViewModel or the Presenter but if you want to launch a coroutine in the Activity/Fragment, you will need a way to control and manage it's cancellation to avoid memory leak.
The solution for this would be:
private val job: Job = Job()
//or Dispatchers.IO
private val fragmentScope = CoroutineScope(Dispatchers.MAIN + job)
//launch a coroutine later in Activity/Fragment
fragmentScope.launch{
//the default coroutine dispatcher would be the defined dispatcher above
}
override fun onDestroy(){
super.onDestroy()
fragmentScope.cancel()
}
As for your question:
what am I doing wrong and what is the appropriate way to return data
from one Dispatcher to another
You can also try this solution if you want to return values from a different context:
someScope.launch(Dispatchers.MAIN){
var data = withContext(Dispatchers.IO){
val someData = fetchSomeData()
return#withContext data
}
if(data.isAvailable()){ //for example
//runing on the main thread
}
}
I didn't understand how kotlin coroutines work.
I need to do a long work on an asynchronous thread and get the result on the UI Thread in an Android app.
Can someone give me some examples?
For example
private fun getCountries(){
viewModelScope.launch {
val a = model.getAllCountries()
countriesList.value = a
}
}
will lunch model.getAllCountries() async but in the end how can i get result to UI Thread?
Well! Adding to #ianhanniballake's answer,
In your function,
private fun getCountries(){
// 1
viewModelScope.launch {
val a = model.getAllCountries()
countriesList.value = a
}
}
You have launched your suspend function from viewModel scope, and the default context is the main thread.
Now the thread on which suspend fun getAllCountries will work will be specified in the definition of getAllCountries function.
So it can be written something like
suspend fun getAllCountries(): Countries {
// 2
return withContext(Disptachers.IO) {
service.getCountries()
}
}
We specify a new thread to call the server using withContext, and after return from withContext block, we are back on main thread.
As per the documentation for viewModelScope:
This scope is bound to Dispatchers.Main.immediate
Where Dispatchers.Main is the Kotlin way of saying 'the main thread'. This means that, by default, all of the code in the launch block runs on the main thread. Your getAllCountries(), if it wants to run on a different thread, would want to use withContext(Disptachers.IO) to move to the IO coroutine dispatcher, as an example.
Therefore in this case, the result of your method is already on the main thread and there's nothing else you need to do.
I need to do a long work on an asynchronous thread
There's no such thing as an asynchronous thread, actually. Whether your network operations are sync or async gets decided by the implementation of the network API you're using.
If you have a blocking network operation, it will stay blocking even when you apply coroutines. The value of coroutines for that use case is limited to making it a bit easier to transfer the result back to the UI thread.
You achieve this by launching a coroutine with the UI dispatcher (the default) and then switching to a thread pool to perform a blocking operation without blocking the UI thread:
viewModelScope.launch {
countriesList.value = withContext(Dispatchers.IO) {
model.getAllCountries()
}
}
Note that a thread inside the thread pool underlying the IO dispatcher will still be blocked, so in terms of the usage of system resources this doesn't make a difference. There will be as many blocked native threads as there are concurrent network calls.
Another solution would be to post your result within a MutableLiveData inside your ViewModel class and observe the LiveData in your view.
Your ViewModel class:
class CountriesViewModel : ViewModel() {
private val parentJob = Job()
val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Default
val viewModelScope = CoroutineScope(coroutineContext)
val countries: MutableLiveData<ArrayList<Country>> = MutableLiveData()
val model = MyModel()
fun getCountries(){
viewModelScope.launch {
val countriesList = model.getAllCountries()
countries.postValue(countries)
}
}
}
Your view class (E.g. a fragment)
class CountriesFragment : Fragment(){
private lateinit var countriesVM : CountriesViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
countriesVM = ViewModelProviders.of(this).get(CountriesViewModel::class.java)
// calling api in your view model here
countriesVM.getCountries()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// observer is notified of the changes on countries livedata
countriesVM.countries.observe(this, Observer { countries ->
// Update ui here
updateUI(countries)
})
}
}