This is the object where all my API calls are made. Sometimes when I make a call to this function, I receive a NetworkOnMainThread exception. It doesn't happen every time. I'm confused because I've made this function asynchronous... why am I still getting this exception?
object APICaller{
private const val apiKey = "API_KEY_HERE"
//Live Data Objects
var errorCode = MutableLiveData<Int>()
var fetchedResponse = MutableLiveData<Response>()
//Asynchronous network call
suspend fun networkCall(query: String) = withContext(Dispatchers.Default){
val apiURL = "API_URL_HERE"
try{
//Get response
val response = OkHttpClient().newCall(Request.Builder().url(apiURL).build()).execute()
if(response.isSuccessful){
//UI changes (including changes to LiveData values) must be performed on main thread.
Handler(Looper.getMainLooper()).post{
fetchedResponse.value = response
}.also{
Log.i("Response Succ", response.toString())
}
} else {
Handler(Looper.getMainLooper()).post{
errorCode.value =
ToastGenerator.REQUEST_ERROR
}.also{
Log.i("Response Fail", response.toString())
}
}
//Catch any thrown network exceptions whilst attempting to contact API
} catch(e: Exception){
Handler(Looper.getMainLooper()).post{
errorCode.value =
ToastGenerator.NETWORK_ERROR
}.also{
Log.i("Network Fail", e.message.toString())
}
}
}
}
There are three other classes that make utilise return value from the APICaller networkCall() function. The first is a ViewModel that references it directly.
class BrowseViewModel: ViewModel() {
//LiveData Objects
//Transformations listen to LiveData in APICaller and map it to LiveData in this ViewModel
var errorCode: LiveData<Int>? = Transformations.map(APICaller.errorCode){ code ->
return#map code
}
var obtainedResponse: LiveData<String> = Transformations.map(APICaller.fetchedResponse){ response ->
return#map response.body()?.string()
}
//Upon a search request, make a network call
fun request(query: String) {
GlobalScope.launch{
APICaller.networkCall(query)
}
}
//Convert API response to GameData object
fun handleJSONString(jsonString: String, file: String) : List<GameData>{
return DataTransformer.JSONToGameData(JSONObject(jsonString), JSONObject(file))
}
}
The second is an fragment that calls the ViewModel's function.
fun request(query: String){
browseViewModel.request(query)
progressSpinner?.visibility = View.VISIBLE
}
The third is an Activity that calls upon the Fragment's function.
private fun makeRequest(query: String){
browseFragment.let{
supportFragmentManager.beginTransaction().replace(R.id.fragmentContainer, it).commit()
it.request(query)
}
}
Could it be related to these other functions?
Any feedback is greatly appreciated. Thank you in advance :)
I don't see exactly what could go wrong in your code, but it could definitely be much cleaner, which possibly would resolve your issue, or at least make it easier to find the error.
A good strategy with suspend functions is to design them to always be called from a Main dispatcher. Then you can freely make UI calls in them and wrap only the background parts with withContext. So your above function could move the withContext() down to only wrap the execute() call, and all your Handler usage could be removed. (Incidentally, those would have been cleaner using withContext(Dispatchers.Main).)
However, Retrofit already provides a suspend function version of making a call, so you don't even need the withContext wrapper. Just use await() instead of execute() and your suspend function would collapse down to:
suspend fun networkCall(query: String) {
val apiURL = "API_URL_HERE"
try{
val response = OkHttpClient().newCall(Request.Builder().url(apiURL).build()).await()
if (response.isSuccessful){
Log.i("Response Succ", response.toString())
fetchedResponse.value = response
} else {
Log.i("Response Fail", response.toString())
errorCode.value = ToastGenerator.REQUEST_ERROR
}
} catch(e: Exception){
Log.i("Network Fail", e.message.toString())
errorCode.value = ToastGenerator.NETWORK_ERROR
}
}
And then instead of using GlobalScope, you should use a viewModelScope or lifecycleScope so your coroutines don't leak UI components. And the await() suspending function above supports cancellation, so if for instance your ViewModel is destroyed due to the associated Fragment or Activity going out of scope, your network call will be cancelled automatically for you.
fun request(query: String) {
viewModelScope.launch {
APICaller.networkCall(query)
}
}
If the problem persists, study your stack trace closely to see where you might be making the error.
Related
In all cases that I have been using corrutines, so far, it has been executing its "lines" synchronously, so that I have been able to use the result of a variable in the next line of code.
I have the ImageRepository class that calls the server, gets a list of images, and once obtained, creates a json with the images and related information.
class ImageRepository {
val API_IMAGES = "https://api.MY_API_IMAGES"
suspend fun fetch (activity: AppCompatActivity) {
activity.lifecycleScope.launch() {
val imagesResponse = withContext(Dispatchers.IO) {
getRequest(API_IMAGES)
}
if (imagesResponse != null) {
val jsonWithImagesAndInfo = composeJsonWithImagesAndInfo(imagesResponse)
} else {
// TODO Warning to user
Log.e(TAG, "Error: Get request returned no response")
}
...// All the rest of code
}
}
}
Well, the suspend function executes correctly synchronously, it first makes the call to the server in the getRequest and, when there is response, then composes the JSON. So far, so good.
And this is the call to the "ImageRepository" suspension function from my main activity:
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { neoRepository.fetch(this#MainActivity) }
Log.i(TAG, "After suspend fun")
}
The problem is that, as soon as it is executed, it calls the suspension function and then displays the log, obviously empty. It doesn't wait for the suspension function to finish and then display the log.
Why? What am I doing wrong?
I have tried the different Dispatchers, etc, but without success.
I appreciate any help.
Thanks and best regards.
It’s because you are launching another coroutine in parallel from inside your suspend function. Instead of launching another coroutine there, call the contents of that launch directly in your suspend function.
A suspend function is just like a regular function, it executes one instruction after another. The only difference is that it can be suspended, meaning the runtime environment can decide to halt / suspend execution to do other work and then resume execution later.
This is true unless you start an asynchronous operation which you should not be doing. Your fetch operation should look like:
class ImageRepository {
suspend fun fetch () {
val imagesResponse = getRequest(API_IMAGES)
if (imagesResponse != null) {
val jsonWithImagesAndInfo = composeJsonWithImagesAndInfo(imagesResponse)
} else {
// TODO Warning to user
Log.e(TAG, "Error: Get request returned no response")
}
... // All the rest of code
}
}
-> just like a regular function. Of course you need to all it from a coroutine:
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { neoRepository.fetch() }
Log.i(TAG, "After suspend fun")
}
Google recommends to inject the dispatcher into the lower level classes (https://developer.android.com/kotlin/coroutines/coroutines-best-practices) so ideally you'd do:
val neoRepository = ImageRepository(Dispatchers.IO)
lifecycleScope.launch {
val result = neoRepository.fetch()
Log.i(TAG, "After suspend fun")
}
class ImageRepository(private val dispatcher: Dispatcher) {
suspend fun fetch () = withContext(dispatcher) {
val imagesResponse = getRequest(API_IMAGES)
if (imagesResponse != null) {
val jsonWithImagesAndInfo = composeJsonWithImagesAndInfo(imagesResponse)
} else {
// TODO Warning to user
Log.e(TAG, "Error: Get request returned no response")
}
... // All the rest of code
}
}
My aim to call five apis and to get headers from those api response.
I have added my code below
Api service class
#GET("users")
suspend fun getUserList(): Call<List<FriendListModel>>
Repo class
suspend fun getList(): Response<List<FriendListModel>> {
return apiService.getUserList().execute()
}
ViewModel class
fun getFriends() {
viewModelScope.launch(Dispatchers.IO) {
val data =
async {
try {
val data = friendListRepo.getList()
val header = data.headers().get("id")
/*
* need to add header logic
*/
Resource.success(data)
} catch (throwable: Throwable) {
when (throwable) {
is HttpException -> {
Resource.error(false, throwable.response()?.message()?:"")
}
else -> {
Resource.error(false, "")
}
}
}
}
val res = data.await()
mutableFriendsList.postValue(res)
}
}
My question is, am I doing it in right way because I am getting a warning in repo class saying that "Inappropriate blocking method call" since I am calling execute() method though I am calling it in suspend function.
[I referred] Kotlin coroutines await for 2 or more different concurrent requests.
Is there any other approach to achieve this?
You should not combine suspend with Call. Call is for asynchronous work. suspend does asynchronous work synchronously by suspending. It can't be both at once. execute does a blocking synchronous fetch of the data, which shouldn't be done in a coroutine.
So, your functions should look like:
#GET("users")
suspend fun getUserList(): List<FriendListModel>
suspend fun getList(): List<FriendListModel> {
return apiService.getUserList()
}
Then when you use it in a coroutine, you don't need async because you're just calling a synchronous suspend function. You also don't need to fool with Dispatchers.IO since you're only using a suspend function (not doing blocking work). I also simplified your catch block in this example, but that's not related to the solution (I just couldn't help myself).
fun getFriends() {
viewModelScope.launch {
mutableFriendsList.value = try {
val data = friendListRepo.getList()
val header = data.headers().get("id")
/*
* need to add header logic
*/
Resource.success(data)
} catch (throwable: Throwable) {
Resource.error(false, (throwable as? HttpException)?.response()?.message.orEmpty())
}
}
}
Side note, even when you are calling blocking code, you should never need to use async immediately followed by an await() call on it. That is just a convoluted alternative to withContext.
Usually I'm returning from my dao suspend function:
#Dao
interface DataDao {
#Query("SELECT * FROM data")
fun getAllData(): List<Data>
}
And handle the call within the repository:
class DataRepository(
private val dataDao: DataDao
) {
fun getAllData(): Flow<DataState> = flow {
val cacheResult = safeDatabaseCall(dispatcher = Dispatchers.IO) { dataDao.getAllData() }
//handle cacheResult, convert to DataState, emit DataState values
}.flowOn(Dispatchers.IO)
}
With generic fun:
suspend fun <T> safeDatabaseCall(
dispatcher: CoroutineDispatcher,
cacheCall: suspend () -> T?
): CacheResult<T?> {
return withContext(dispatcher) {
try {
withTimeout(10000L) {
CacheResult.Success(cacheCall.invoke())
}
} catch (t: Throwable) {
when (t) {
is TimeoutCancellationException -> {
CacheResult.Error("Timeout error")
}
else -> {
CacheResult.Error("Unknown error")
}
}
}
}
}
The problem is that I want return fun getAllData(): Flow<List<Data>> instead of fun getAllData(): List<Data> In order to get immediate updates, But if I'm returning Flow from the Dao, I can't handle the call with safe call and catch errors.
I thought about collecting the data, but if i'm collecting the data the call already done without error handling
Basically I need the cache result return CacheResult<Data> and not CacheResult<Flow<Data>>
How can I solve the problem And make a generic safeDatabaseCall while returning Flow from Dao?
So if I understand correctly you just want to handle the query and return of information safely in a flow. My only question is around the types. I can sorta assume Data DataState and CacheResult are not the same types so I use a "magic" function that converts the intermediary values to the correct one. You will need to adjust accordingly
class DataRepository(
private val dataDao: DataDao
) {
fun getAllData(): Flow<DataState> = flow {
val result = safeDatabaseCall(dispatcher = Dispatchers.IO) {
dataDao.getAllData()
}
// Emit the result
emit(result)
}.catch { t : Throwable ->
// Do our transformation like before
val result = when (t) {
is TimeoutCancellationException -> {
CacheResult.Error("Timeout error")
}
else -> {
CacheResult.Error("Unknown error")
}
}
// And because catch is actually extending a FlowCollector
// We can emit the result in the stream
emit(result)
}.map { cacheResult ->
convertToDataOrDataState(cacheResult)
}
You shouldn't need flowOn with a dispatcher here since the work inside this flow doesn't require thread dispatching
to Dispatcher.IO. The code we are putting in our flow, is purely exception handling and invoking a function. The only place that seems to require any manual dispatch changing is, safeDatabaseCall(). I am not familiar with this function but if it does exist and takes a dispatcher for the result of actualing making the db calls on an IO thread, then all should be good without flowOn. Otherwise you will be switching dispatchers from original dispatcher -> IO and then to IO again. It's not much but the extra no-op context switch doesn't add anything other than confusion later on.
The flow itself traps any upstream issues and you then make them part of the resulting flow
I am calling an API in an asynchronous function, and would like to store the response from the callback.
Specifically, I am using the API category of AWS Amplify in an Android app, and would like to assign the return value to a LiveData variable in a ViewModel.
fun getMuscleGroup(id: String): ExampleData {
var exampleData = ExampleData.builder().name("").build()
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
exampleData = response.data
},
{ error -> Log.e("AmplifyApi", "Query failure", error) }
)
return exampleData
}
I can receive the response and it gets logged correctly, but the response is not assigned to the exampleData variable, since the function returns early.
In Android Studio, the variable exampleData is highlighted with the text:
Wrapped into a reference object to be modified when captured in a closure
As I am not that familiar with the multithreading APIs in kotlin, I am not sure, how to block the function until the remote API returns its asynchronous response.
The most basic way of doing this would be with standard Java thread safety constructs.
fun getMuscleGroup(id: String): ExampleData {
var exampleData = ExampleData.builder().name("").build()
val latch = CountDownLatch(1)
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
exampleData = response.data
latch.countDown()
},
{ error ->
Log.e("AmplifyApi", "Query failure", error)
latch.countDown()
}
)
latch.await()
return exampleData
}
Since this is on Android, this is probably a bad solution. I'm going to guess that getMuscleGroup is being called on the UI thread, and you do not want this method to actually block. The UI would freeze until the network call completes.
The more Kotlin way of doing this would be to make the method a suspend method.
suspend fun getMuscleGroup(id: String): ExampleData {
return suspendCoroutine { continuation ->
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
continuation.resume(response.data)
},
{ error ->
Log.e("AmplifyApi", "Query failure", error)
// return default data
continuation.resume(ExampleData.builder().name("").build())
}
}
}
This use Kotlin coroutines to suspend the coroutine until an answer is ready and then return the results.
Other options would be to use callbacks instead of return values or an observable pattern like LiveData or RxJava.
You cannot block to wait for the asynchronous result. At worst it can cause an Application Not Responding (ANR) error message to appear for the user, and at best makes your app look stuttery and feel unresponsive.
You can instead add a callback for this function:
fun getMuscleGroup(id: String, callback: (ExampleData) -> Unit) {
var exampleData = ExampleData.builder().name("").build()
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
callback(response.data)
},
{ error -> Log.e("AmplifyApi", "Query failure", error) }
)
}
And then in the spot where you call the code, you put your subsequent actions in the callback:
fun onMuscleGroupClicked(id: String) {
getMuscleGroup(id) { exampleData ->
// do something with the data after it arrives
}
}
Coroutines are another option. They're nice because you don't have to nest your sequential actions in callbacks. To set it up, I would create a suspend extension function version of the API library query function by using suspendCancellableCoroutine. Then you can freely use it in other suspend functions in an orderly way. But you'll need to read the documentation on coroutines. It's to much to explain here from scratch.
I'm trying to use the Android MVVM pattern with a repository class and Retrofit for network calls. I have the common problem that I can't get the coroutine to wait for the network response to return.
This method is in my ViewModel class:
private fun loadConfigModel() {
val model = runBlocking {
withContext(Dispatchers.IO) {
configModelRepository.getConfigFile()
}
}
configModel.value = model
}
In ConfigModelRepository, I have this:
suspend fun getConfigFile(): ConfigModel {
val configString = prefs.getString(
ConfigViewModel.CONFIG_SHARED_PREF_KEY, "") ?: ""
return if (configString.isEmpty() || isCacheExpired()) {
runBlocking { fetchConfig() }
} else {
postFromLocalCache(configString)
}
}
private suspend fun fetchConfig(): ConfigModel {
return suspendCoroutine { cont ->
dataService
.config() // <-- LAST LINE CALLED
.enqueue(object : Callback<ConfigModel> {
override fun onResponse(call: Call<ConfigModel>, response: Response<ConfigModel>) {
if (response.isSuccessful) {
response.body()?.let {
saveConfigResponseInSharedPreferences(it)
cont.resume(it)
}
} else {
cont.resume(ConfigModel(listOf(), listOf()))
}
}
override fun onFailure(call: Call<ConfigModel>, t: Throwable) {
Timber.e(t, "config fetch failed")
cont.resume(ConfigModel(listOf(), listOf()))
}
})
}
}
My code runs as far as dataService.config(). It never enters onResponse or onFailure. The network call goes and and returns properly (I can see this using Charles), but the coroutine doesn't seem to be listening for the callback.
So, my question is the usual one. How can I get the coroutines to block such that they wait for this callback from Retrofit? Thanks.
The problem must be that response.body() returns null since that is the only case that is missing a call to cont.resume(). Make sure to call cont.resume() also in that case and your code should at least not get stuck.
But like CommonsWare points out, even better would be to upgrade to Retrofit 2.6.0 or later and use native suspend support instead of rolling your own suspendCoroutine logic.
You should also stop using runBlocking completely. In the first case, launch(Dispatchers.Main) a coroutine instead and move configModel.value = model inside of it. In the second case you can just remove runBlocking and call fetchConfig() directly.