英文:
Coroutine initializes one lateinit var but not the other
问题
todoAdapter
is initialized successfully because it is declared as a lateinit var
, which means it can be initialized later in your code. On the other hand, dropdownAdapter
is declared as private lateinit var dropdownAdapter: ArrayAdapter<User>
, but it is not initialized before it's being used.
In your code, you are trying to initialize dropdownAdapter
inside your coroutine block with the line:
dropdownAdapter = ArrayAdapter<User>(this@MainActivity, R.layout.item_user, userResponse.body()!!)
However, if any exception is thrown before this line (for example, if there's an issue with the network request or if an exception is thrown in the try
block for userResponse
), the dropdownAdapter
will remain uninitialized, and you'll encounter the kotlin.UninitializedPropertyAccessException
when you try to use it.
To fix this issue, you should initialize dropdownAdapter
before your coroutine block or handle exceptions that might occur during its initialization appropriately to ensure that it is initialized in all cases.
英文:
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.
答案1
得分: 0
This line might fail:
binding.dropdownUser.setAdapter(dropdownAdapter)
因为您在launchWhenCreated
中异步初始化了dropdownAdapter
,但在它能够初始化之前,您尝试使用它,例如.setAdapter(dropdownAdapter)
。
您可以选择:
- 在
setupRecyclerView
中创建dropdownAdapter
,就像todoAdapter
一样,然后稍后填充userResponse.body
。 - 或者在
launchWhenCreated
中初始化dropdownAdapter
之后,调用binding.dropdownUser.setAdapter(dropdownAdapter)
。
此外,在从todosResponse
返回之前,您应该取消用户API的调用,因为您不会使用它:usersDeferred.cancel()
。
英文:
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:
- Create
dropdownAdapter
insetupRecyclerView
, just liketodoAdapter
, and fill theuserResponse.body
later. - Or call
binding.dropdownUser.setAdapter(dropdownAdapter)
insidelaunchWhenCreated
, after you initializedropdownAdapter
Also, before you return from todosResponse
with return@launchWhenCreated
, you should cancel users API, because you won't use it: usersDeferred.cancel()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论