协程初始化一个lateinit变量,但不初始化另一个。

huangapple go评论60阅读模式
英文:

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 = &quot;MainActivity&quot;

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var todoAdapter: TodoAdapter
    private lateinit var dropdownAdapter: ArrayAdapter&lt;User&gt;

    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, &quot;IOException, you might not have internet connection&quot;)
                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, &quot;HttpException, unexpected response&quot;)
                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, &quot;IOException, you might not have internet connection&quot;)
                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, &quot;HttpException, unexpected response&quot;)
                binding.progressBar.isVisible = false
                return@launchWhenCreated
            }

            if (checkResponse(todoResponse)) {
                todoAdapter.todos = todoResponse.body()!! //succeeds
            }
            else {
                Log.e(TAG, &quot;Todo response not successful&quot;)
            }

            if (checkResponse(userResponse)) {

                dropdownAdapter = ArrayAdapter&lt;User&gt;(this@MainActivity, R.layout.item_user, userResponse.body()!!) // fails
            }
            else {
                Log.e(TAG, &quot;User response not successful&quot;)
            }

            binding.progressBar.isVisible = false
        }

        setupRecyclerView()
        binding.dropdownUser.setAdapter(dropdownAdapter)
    }

    private fun &lt;T : Any&gt; checkResponse(response: Response&lt;List&lt;T&gt;&gt;) =
        response.isSuccessful &amp;&amp; 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)&#160;
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)&#160;
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)&#160;
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)&#160;
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)&#160;
        at android.os.Handler.dispatchMessage(Handler.java:106)&#160;
        at android.os.Looper.loopOnce(Looper.java:201)&#160;
        at android.os.Looper.loop(Looper.java:288)&#160;
        at android.app.ActivityThread.main(ActivityThread.java:7839)&#160;
        at java.lang.reflect.Method.invoke(Native Method)&#160;
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)&#160;
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)&#160;
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)

您可以选择:

  1. setupRecyclerView中创建dropdownAdapter,就像todoAdapter一样,然后稍后填充userResponse.body
  2. 或者在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:

  1. Create dropdownAdapter in setupRecyclerView, just like todoAdapter, and fill the userResponse.body later.
  2. Or call 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()

huangapple
  • 本文由 发表于 2023年4月19日 18:04:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76053222.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定