I am making two retrofit calls from lifecycle scope. These two calls use async and await to get data from api.
Code :
package com.example.retrofittodo
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.ArrayAdapter
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.retrofittodo.databinding.ActivityMainBinding
import kotlinx.coroutines.async
import retrofit2.HttpException
import retrofit2.Response
import java.io.IOException
const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var todoAdapter: TodoAdapter
private lateinit var dropdownAdapter: ArrayAdapter<User>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Get data
lifecycleScope.launchWhenCreated {
binding.progressBar.isVisible = true
val todosDeferred = async { RetrofitInstance.api.getTodos(null) }
val usersDeferred = async { RetrofitInstance.api.getUsers() }
val todoResponse = try {
todosDeferred.await()
} catch (e: IOException) {
// Most likely no internet connection or maybe output stream closed
Log.e(TAG, "IOException, you might not have internet connection")
binding.progressBar.isVisible = false
return@launchWhenCreated
} catch (e: HttpException) {
// If response code does not start with digit 2 then something is unusual
Log.e(TAG, "HttpException, unexpected response")
binding.progressBar.isVisible = false
return@launchWhenCreated
}
val userResponse = try {
usersDeferred.await()
} catch (e: IOException) {
// Most likely no internet connection or maybe output stream closed
Log.e(TAG, "IOException, you might not have internet connection")
binding.progressBar.isVisible = false
return@launchWhenCreated
} catch (e: HttpException) {
// If response code does not start with digit 2 then something is unusual
Log.e(TAG, "HttpException, unexpected response")
binding.progressBar.isVisible = false
return@launchWhenCreated
}
if (checkResponse(todoResponse)) {
todoAdapter.todos = todoResponse.body()!! //succeeds
}
else {
Log.e(TAG, "Todo response not successful")
}
if (checkResponse(userResponse)) {
dropdownAdapter = ArrayAdapter<User>(this@MainActivity, R.layout.item_user, userResponse.body()!!) // fails
}
else {
Log.e(TAG, "User response not successful")
}
binding.progressBar.isVisible = false
}
setupRecyclerView()
binding.dropdownUser.setAdapter(dropdownAdapter)
}
private fun <T : Any> checkResponse(response: Response<List<T>>) =
response.isSuccessful && response.body() != null
private fun setupRecyclerView() = binding.rvTodos.apply {
todoAdapter = TodoAdapter()
adapter = todoAdapter
layoutManager = LinearLayoutManager(this@MainActivity)
}
}
todoAdapter
is initialized successfully but dropdownAdapter
is not initialized.
Logcat :
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.retrofittodo, PID: 8917
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.retrofittodo/com.example.retrofittodo.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property dropdownAdapter has not been initialized
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3635)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3792)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property dropdownAdapter has not been initialized
at com.example.retrofittodo.MainActivity.onCreate(MainActivity.kt:119)
at android.app.Activity.performCreate(Activity.java:8051)
at android.app.Activity.performCreate(Activity.java:8031)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1329)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3608)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3792)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
W/System: A resource failed to call close.
Why is it so ? I cannot think of any reason why I can modify one variable in the coroutine but not the other.
Probably this line fails:
binding.dropdownUser.setAdapter(dropdownAdapter)
Because, you initialize dropdownAdapter
asynchronously in launchWhenCreated
, but before it can get initialized, you try to use it like .setAdapter(dropdownAdapter)
.
You could either:
dropdownAdapter
in setupRecyclerView
, just like todoAdapter
, and fill the userResponse.body
later.binding.dropdownUser.setAdapter(dropdownAdapter)
inside launchWhenCreated
, after you initialize dropdownAdapter
Also, before you return from todosResponse
with return@launchWhenCreated
, you should cancel users API, because you won't use it: usersDeferred.cancel()