I have this code in a fragment:
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
create_adoption_btn.setOnClickListener {
val temp = Intent(activity!!.baseContext, AdoptionCreationActivity::class.java)
activity!!.startActivityFromFragment(this, temp, 1)
}
val mLayoutManager = GridLayoutManager(activity!!.baseContext, 1)
recycler_view.layoutManager = mLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator()
//recycler_view.adapter = adapter
//AppController.instance!!.getAdoptionList().await()
GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT) {
Log.i("TestAdapter", "Beginning fetch")
val adapter = AlbumsAdapter(activity!!, AppController.instance!!.getAdoptionList()) //Skips this line, but still executes it
Log.i("TestAdapter", "Adapter: ${adapter.itemCount}")
recycler_view.adapter = adapter
adapter.notifyDataSetChanged()
Log.i("TestAdapter", "Adapter updated on thread")
}
}
And this for a class that extends Application
class AppController : Application() {
private var adoptionCardList: MutableList<AdoptionCard> = mutableListOf()
override fun onCreate() {
super.onCreate()
instance = this
}
fun getAdoptionList(): MutableList<AdoptionCard> {
if(adoptionCardList.count() == 0) {
val service = GetVolley()
val apiController = ApiController(service)
val path = "adoptions/read.php"
apiController.get(path, JSONArray()){ response ->
if (response != null) {
var x = 0
while(x <= response.length() - 1){
val jsonObject = (response[x] as JSONObject)
adoptionCardList.add(AdoptionCard(
jsonObject.getInt("id"),
jsonObject.getString("adoption_title"),
jsonObject.getString("user_id").toBigInteger(),
jsonObject.getString("adoption_created_time")))
x+=1
}
}
}
}
return adoptionCardList
}
private val requestQueue: RequestQueue? = null
get() {
if (field == null) {
return Volley.newRequestQueue(applicationContext)
}
return field
}
fun <T> addToRequestQueue(request: Request<T>, tag: String) {
request.tag = if (TextUtils.isEmpty(tag)) TAG else tag
requestQueue?.add(request)
}
fun <T> addToRequestQueue(request: Request<T>) {
request.tag = TAG
requestQueue?.add(request)
}
fun cancelPendingRequests(tag: Any) {
if (requestQueue != null) {
requestQueue!!.cancelAll(tag)
}
}
companion object {
private val TAG = AppController::class.java.simpleName
@get:Synchronized var instance: AppController? = null
private set
}
The "launch" coroutine should wait until Volley retrieves all information from the server but it just skips that line and the Recycler View doesn't update, since the MutableList is empty. If I reload the Fragment, it will do this successfully since there's an already stored list. I read all documentation I could on Kotlin Coroutines and questions asked but I can't make this work. Could anyone help me?
The debug: Debug log
On the first load, as you can see, the adapter has 0 elements, so the view gets nothing; on the second load, it already has 3 elements, so the Recycler view loads those 3.
ApiController:
class ApiController constructor(serviceInjection: RESTapi): RESTapi {
private val service: RESTapi = serviceInjection
override fun get(path: String, params: JSONArray, completionHandler: (response: JSONArray?) -> Unit) {
service.get(path, params, completionHandler)
}
}
Interface:
interface RESTapi {
fun get(path: String, params: JSONArray, completionHandler: (response: JSONArray?) -> Unit)
}
GetVolley class:
class GetVolley : RESTapi {
val TAG = GetVolley::class.java.simpleName
val basePath = "http://192.168.0.161/animals/"
override fun get(path: String, params: JSONArray, completionHandler: (response: JSONArray?) -> Unit) {
val jsonObjReq = object : JsonArrayRequest(Method.GET, basePath + path, params,
Response.Listener<JSONArray> { response ->
Log.d(TAG, "/get request OK! Response: $response")
completionHandler(response)
},
Response.ErrorListener { error ->
VolleyLog.e(TAG, "/get request fail! Error: ${error.message}")
completionHandler(null)
}) {
@Throws(AuthFailureError::class)
override fun getHeaders(): Map<String, String> {
val headers = HashMap<String, String>()
headers["Content-Type"] = "application/json"
return headers
}
}
AppController.instance?.addToRequestQueue(jsonObjReq, TAG)
}
The problem arises in your
apiController.get()
call, which returns immediately and not after the network operation is complete. You supply your response callback to it. It will run eventually, once the REST call has got its response.This is how you should adapt your function to coroutines:
This is now a
suspend fun
and it will suspend itself in case the adoption list isn't already populated. In either case the function will ensure that by the time it returns, the list is populated.I would also advise you to stop using
GlobalScope
in order to prevent your network calls running in the background, possibly holding on to the entire GUI tree of your activity, after the activity is destroyed. You can read more about structured concurrency from Roman Elizarov and you can follow the basic example in the documentation ofCoroutineScope
.