尝试通过改造获取数据并使用recyclerview进行显示时出现KotlinNullPointerException

时间:2019-11-29 10:49:56

标签: android android-fragments kotlin android-recyclerview android-jetpack

我正在尝试使用改造库显示来自github的存储库列表,然后使用recyclerView库显示它。在添加recyclerView之前,我已经检查了是否成功获取了数据。但是在添加了recyclerView之后,我得到了NullPointerException。

下面是我的Viewmodel,片段,适配器和数据类的代码

ListViewModel.kt

package com.kunalrai.githubtrends

import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class ListViewModel : ViewModel() {
    lateinit var repoList: MutableLiveData<List<Repo>>

    fun getRepos(): MutableLiveData<List<Repo>>{

            repoList = MutableLiveData()
            loadRepos()

        return repoList
    }

    private fun loadRepos() {
        Api.RETROFIT_SERVICE.getRepos().enqueue( object: Callback<List<Repo>> {
            override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
                Log.i("Failure: ", t.message)
            }

            override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
                if(response.body() != null){
                    repoList.value = response.body()!!
                    Log.i("response.body :",""+response.body()!!)
                }

            }
        })
    }

}

ListFragment.kt

package com.kunalrai.githubtrends

import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kunalrai.githubtrends.databinding.ListFragmentBinding


class ListFragment : Fragment() {

    companion object {
        fun newInstance() = ListFragment()
    }

    private val viewModel: ListViewModel by lazy {
        ViewModelProviders.of(this).get(ListViewModel::class.java)
    }

    private lateinit var binding: ListFragmentBinding
    var recyclerView: RecyclerView? = null
    lateinit var recyclerAdapter: RecyclerAdapter
    var repoList: List<Repo>? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = ListFragmentBinding.inflate(inflater, container, false)
        binding.lifecycleOwner

        binding.viewmodel = viewModel
        setHasOptionsMenu(true)

        recyclerView = view?.findViewById(R.id.rv_repo_list)
        recyclerView?.layoutManager = LinearLayoutManager(context)
        recyclerView?.setHasFixedSize(true)

        viewModel.getRepos().observe(this,
            Observer<List<Repo>> { repoList ->
                recyclerAdapter = RecyclerAdapter(context, repoList!!)
                recyclerView!!.adapter = recyclerAdapter
            })


        return binding.root
    }

}

ListAdapter.kt

package com.kunalrai.githubtrends

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions

class ListAdapter(private val context: Context?, private val repoList: List<Repo>) : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {

        val view = LayoutInflater.from(parent.context).inflate(R.layout.repo_item,parent,false)
        return MyViewHolder(view)
    }

    override fun getItemCount(): Int {
        return repoList.size
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {

        holder.author.text = repoList[position].author

        holder.repo.text = repoList[position].name

        Glide.with(context!!).load(repoList[position].avatar)
            .apply(RequestOptions().centerCrop())
            .into(holder.image)
    }

    class MyViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView!!) {

        val author: TextView = itemView!!.findViewById(R.id.owner_name)
        val image: ImageView = itemView!!.findViewById(R.id.owner_image)
        val repo: TextView = itemView!!.findViewById(R.id.repo_name)


    }
}

Repo.kt(数据类)

package com.kunalrai.githubtrends

import com.squareup.moshi.Json

data class Repo(
    @Json(name = "author")
    var author: String,
    @Json(name = "name")
    var name: String,
    @Json(name = "description")
    var desc: String,
    @Json(name = "avatar")
    var avatar: String,
    @Json(name = "language")
    var language: String,
    @Json(name = "url")
    var url: String,
    @Json(name = "stars")
    var stars: String,
    @Json(name = "forks")
    var forks: String
)

我的错误堆栈:- (已更新)

2019-11-29 16:43:57.686 20703-20703/? E/Zygote: isWhitelistProcess - Process is Whitelisted
2019-11-29 16:43:57.688 20703-20703/? E/Zygote: accessInfo : 1
2019-11-29 16:44:03.865 20703-20703/com.kunalrai.githubtrends E/RecyclerView: No adapter attached; skipping layout

2 个答案:

答案 0 :(得分:1)

在Kotlin中使用!!时要小心,因为它可能导致NullPointerException。将其替换为variable?.let{}variable?.

对于此代码块:

viewModel.getRepos().observe(this,
        Observer<List<Repo>> { repoList ->
            recyclerAdapter = RecyclerAdapter(context, repoList!!)
            recyclerView!!.adapter = recyclerAdapter
        })

您可以更改为:

class ListFragment {
    //Define adapter as global variable
    RecyclerAdapter recyclerAdapter: RecyclerAdapter? = null

    override fun onCreateView(...) {
        ...

        //Init adapter only once
        recyclerAdapter = RecyclerAdapter(context, repoList)
        //Use binding to make sure the view exist. You can remove findViewById command
        binding.recyclerView.adapter = recyclerAdapter

        viewModel.getRepos().observe(this,
            Observer<List<Repo>> { 
                it?.let { repoList ->
                    recyclerAdapter?.swapData(repoList)
                }
            })
    }
}

variableName?.let{}仅在variableName不为空时在{}块内执行命令

编辑适配器:

class ListAdapter() {
    private val repoList: ArrayList<Repo> = ArrayList()

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(repoList[position])
    }

    fun swapData(repoList: List<Repo>) {
        this.repoList.clear()
        this.repoList.addAll(repoList)
        notifyDataSetChanged()
    }

    class MyViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView!!) {
        fun bindView(repo: Repo) {
            //Do you binding here, you can get context from itemView.context
        }
    }
}

答案 1 :(得分:0)

替换

    recyclerView = view?.findViewById(R.id.rv_repo_list)

使用

    val recyclerView = findViewById<RecyclerView>(R.id.rv_repo_list)
    val adapter =  ListAdapter(this)
    recyclerView.adapter = adapter 

因此您可以避免使用所有空安全代码,因为它会同时实例化布局和适配器