错误 lateinit var 尚未初始化

时间:2021-05-13 17:41:18

标签: android android-studio kotlin

所以我正在制作 Github 用户搜索应用 Search Github User App 来练习解析 Json Api。每当我尝试搜索用户名时,应用程序就会崩溃并返回此错误:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.dicoding.picodiploma.githubappsub2, PID: 32607
    kotlin.UninitializedPropertyAccessException: lateinit property data has not been initialized
        at com.dicoding.picodiploma.githubappsub2.main.MainViewModel.getData(MainViewModel.kt:17)
        at com.dicoding.picodiploma.githubappsub2.main.MainViewModel.searchUserInfo(MainViewModel.kt:59)
        at com.dicoding.picodiploma.githubappsub2.main.MainViewModel$searchUser$1.onResponse(MainViewModel.kt:29)
        at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1$1.run(DefaultCallAdapterFactory.java:83)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

当我尝试将数据存储到数据类时,它表示尚未初始化 lateinit 属性数据。

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding
private lateinit var adapter: UserAdapter
private lateinit var viewModel: MainViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    adapter = UserAdapter()
    adapter.notifyDataSetChanged()

    viewModel =
        ViewModelProvider(
            this,
            ViewModelProvider.NewInstanceFactory()
        ).get(MainViewModel::class.java)

    showLoading(false)
    recyclerViewSetup()
    searchUser()


}
private fun searchUser(){
    binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
        override fun onQueryTextSubmit(query: String): Boolean {
            if (query.isEmpty()){
                return true
            }else {
                showLoading(true)
                viewModel.searchUser(query)
                getSearchedUser()
            }
            return true
        }

        override fun onQueryTextChange(newText: String): Boolean {
           return false
        }
    })
}

private fun getSearchedUser(){
    viewModel.getSearchedUser().observe(this, {
        if (it!=null){
            adapter.setData(it)
            showLoading(false)
        }
    })
}
private fun recyclerViewSetup() {
    binding.apply {
        recyclerView.layoutManager = LinearLayoutManager(binding.recyclerView.context)
        recyclerView.setHasFixedSize(true)
        recyclerView.adapter = adapter
        recyclerView.addItemDecoration(
            DividerItemDecoration(
                binding.recyclerView.context,
                DividerItemDecoration.VERTICAL
            )
        )
    }
}
private fun showLoading(state: Boolean){
    if(state){
        binding.progressBar.visibility = View.VISIBLE
    }else binding.progressBar.visibility = View.INVISIBLE
}

这是 MainViewModel

class MainViewModel : ViewModel() {

val usersList = MutableLiveData<ArrayList<User>>()
lateinit var data: User

fun searchUser(query: String) {
    RetrofitClient.githubApi.getSearchUser(query).enqueue(object : Callback<UserSearch> {
        override fun onResponse(call: Call<UserSearch>, response: Response<UserSearch>) {
            if (response.isSuccessful) {
                val items = response.body()?.items
                val listTemp = ArrayList<User>()
                if (items != null) {
                    for (i in 0 until items.count()) {
                        val username = items[i].login
                        Log.d("Username", username)
                        val user = searchUserInfo(username)
                        listTemp.add(user)
                    }
                    usersList.postValue(listTemp)
                }
            }
        }

        override fun onFailure(call: Call<UserSearch>, t: Throwable) {
            Log.d("Failure", t.message.toString())
        }

    })
}

fun searchUserInfo(username: String): User{

    RetrofitClient.githubApi.getUserDetail(username).enqueue(object : Callback<User> {
        override fun onResponse(call: Call<User>, response: Response<User>) {
            val userInfo = response.body()
            Log.d("User Info", userInfo.toString())
            if (userInfo != null) {
                data = userInfo
            }
        }

        override fun onFailure(call: Call<User>, t: Throwable) {
            Log.d("Failure", t.message.toString())
        }
    })
    return data
}

fun getSearchedUser(): LiveData<ArrayList<User>> {
    return usersList
}

我在这段代码中做错了什么吗?我怎么能修好呢? 感谢任何帮助,谢谢!

2 个答案:

答案 0 :(得分:0)

lateinit 允许您声明一个变量而不实际初始化它(通过分配一个值) - 您承诺在任何尝试从中读取之前为其设置一个值。但是您的代码正在尝试从中读取

return data

在设置 User 之前

override fun onResponse(call: Call<User>, response: Response<User>) {
    ...
    if (userInfo != null) {
        data = userInfo
    }

有两个问题 - 您在 user 回调中设置 onResponse,这是将异步发生的网络请求的结果,稍后返回。您将其关闭,然后在请求返回以设置值之前很久点击 return data

另一个问题是您的 onResponse 代码仅在 userInfo 不为空时才设置该值 - 这意味着它可能为空,这意味着 user 可能不会被初始化(也许永远不会!)


lateinit 是一种很好的方法,可以避免在声明变量时仅仅为了有一个初始的、一次性的值而使事物可以为空。您实际上可以使用 ::user.isInitialized 检查它是否已初始化,但老实说,这可能是使 user 可为空的一个很好的例子 - 这是一个属性,您实际上没有此函数的值被调用,也许你永远不会。

您的 searchUserInfo 函数至少必须能够处理这种可能性 - 现在它返回一个 User,这意味着它肯定会给您一个结果。但是,如果搜索失败,这意味着什么?您是返回默认值 User,还是需要“无结果”值,例如 null?

答案 1 :(得分:0)

不要在 ViewModel 中使用 lateinit,因为一旦实例化 ViewModel 就可以使用了。这样就可以在分配之前访问您的 lateinit 属性。 lateinit 用于 Activity 之类的类,它在调用 onCreate 之后才可用,因此在调用 onCreate 之前保留未初始化的值是安全的。相反,您应该将 data 设为可空,以便编译器强制您在访问时检查它是否仍然为空。

在这种情况下,您的错误是您在 data 被分配之前返回。您发送了一个 Retrofit 请求,但在返回未初始化的 data 之前不要等待结果。 data 无论如何都没有意义,因为它在 searchUserInfo 函数之外的任何地方都没有使用。

您可以使 searchUserInfo 挂起并返回可为空的 User,其中 null 值表示失败:

suspend fun searchUserInfo(username: String): User? {
    return RetrofitClient.githubApi.getUserDetail(username).await()
        .onFailure { Log.d("Failure", t.message.toString()) }
        .getOrNull()
        ?.let { it.body() }
        ?.also { Log.d("User Info", it.toString()) }
}

然后从协程调用函数。如果你对协程还不满意,你可以使用这样的回调方法:

inline fun searchUserInfo(username: String, crossinline onSuccess: (User)->Unit){
    RetrofitClient.githubApi.getUserDetail(username).enqueue(object : Callback<User> {
        override fun onResponse(call: Call<User>, response: Response<User>) {
            val userInfo = response.body()
            Log.d("User Info", userInfo.toString())
            onSuccess(userInfo)
        }

        override fun onFailure(call: Call<User>, t: Throwable) {
            Log.d("Failure", t.message.toString())
        }
    })
}

然后,无论你从哪里调用它,你都会传递一个对返回的 User 值做出反应的 lambda。