kotlin.UninitializedPropertyAccessException: lateinit property bListener has not been initialized, recyclerview android

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

kotlin.UninitializedPropertyAccessException: lateinit property bListener has not been initialized, recyclerview android

问题

class CatalogoDaLeggere : Fragment() {

    private lateinit var binding: FragmentCatalogoDaLeggereBinding
    private lateinit var recyclerView : RecyclerView
    private lateinit var adapter: MyAdapterDL
    private lateinit var listaLibri: ArrayList<LibriDaL>
    private lateinit var select: Spinner
    private val sezioni = arrayListOf("In corso","Letti")
    private var isRecyclerViewPopulated = false

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate<FragmentCatalogoDaLeggereBinding>(inflater,
            R.layout.fragment_catalogo_da_leggere,container,false)

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        listaLibri = ArrayList()

        recyclerView = binding.listaLibriLeggere
        recyclerView.layoutManager = LinearLayoutManager(requireContext())
        recyclerView.setHasFixedSize(true)

        adapter = MyAdapterDL(listaLibri)
        recyclerView.adapter = adapter

        checkBookCatalogo()
    }

    private fun checkBookCatalogo() {
        if (FirebaseAuth.getInstance().currentUser != null) {
            val cUser = FirebaseAuth.getInstance().currentUser!!
            Log.d("TAG", "Sono :")
            val database =
                FirebaseDatabase.getInstance("https://booktique-87881-default-rtdb.europe-west1.firebasedatabase.app/")
            val usersRef = database.reference.child("Utenti")
            val childRef = usersRef.child(cUser.uid)
            val catalogoRef = childRef.child("Catalogo")
            val daLeggereRef = catalogoRef.child("DaLeggere")

            daLeggereRef.addValueEventListener(object : ValueEventListener {
                override fun onDataChange(snapshot: DataSnapshot) {
                    val daLeggereBooks = arrayListOf<LibriDaL>()
                    if (snapshot.exists()) {
                        for (bookSnapshot in snapshot.children) {
                            val libriDaL = bookSnapshot.getValue(LibriDaL::class.java)
                            Log.d("TAG", "VolumeDet : $libriDaL")
                            daLeggereBooks.add(libriDaL!!)
                        }
                    }

                    // Richiama la funzione per i libri "DaLeggere"
                    loadBooks(daLeggereBooks)
                }

                override fun onCancelled(error: DatabaseError) {
                    // Gestisci eventuali errori nella lettura dei dati
                    Log.e("TAG", "Errore nel recupero dei dati", error.toException())
                }
            })
        }
    }

    private fun loadBooks(books: List<LibriDaL>?){
        if (books != null) {
            listaLibri.clear() // Clear existing list before adding new data
            listaLibri.addAll(books)
            adapter.notifyDataSetChanged() // Notify adapter of data change
            isRecyclerViewPopulated = true
        }
    }
}

The code provided should help you resolve the issue you were facing. The key changes made include:

  1. Clearing the existing list (listaLibri) before adding new data fetched from Firebase to ensure that the data isn't duplicated.

  2. Notifying the adapter of the data change using adapter.notifyDataSetChanged() after updating listaLibri. This ensures that the RecyclerView is updated to display the new data.

These changes should help prevent issues with your RecyclerView and adapter when loading data asynchronously from Firebase.

英文:

I am creating a book-themed application in android. Briefly within my application I have three sections where a user can enter books, specifically: Daleggere, Incorso, Letti (this is Italian but translated would be: to be read, in progress, read). Each of these sections is an android recyclerview with an associated Adapter, also this recyclerview is populated through a realtime Firebase database. Now, the problem is that I created an interface in the adapter to create events on the click of buttons that I have in the layout of each individual element of the recyclerview, but when I go to open the fragment the app sends this exception: kotlin.UninitializedPropertyAccessException: lateinit property bListener has not been initialized.
This is the Fragment class CatalogoDaLeggere:

class CatalogoDaLeggere : Fragment() {
private lateinit var binding: FragmentCatalogoDaLeggereBinding
private lateinit var recyclerView : RecyclerView
private lateinit var adapter: MyAdapterDL
private lateinit var listaLibri: ArrayList&lt;LibriDaL&gt;
private lateinit var select: Spinner
private val sezioni = arrayListOf(&quot;In corso&quot;,&quot;Letti&quot;)
private var isRecyclerViewPopulated = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate&lt;FragmentCatalogoDaLeggereBinding&gt;(inflater,
R.layout.fragment_catalogo_da_leggere,container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listaLibri = ArrayList()
recyclerView = binding.listaLibriLeggere
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.setHasFixedSize(true)
adapter = MyAdapterDL(listaLibri)
recyclerView.adapter = adapter
checkBookCatalogo()
if(isRecyclerViewPopulated) {
adapter.setOnCLickItemListener(object : MyAdapterDL.onItemClickListener {
override fun onItemClick(position: Int) {
}
override fun moveBook(spinner: Spinner, send: ImageButton) {
select = spinner
val btn = send
val arrayAdapter = ArrayAdapter&lt;String&gt;(
requireContext(),
android.R.layout.simple_spinner_dropdown_item
)
select.adapter = arrayAdapter
arrayAdapter.addAll(sezioni)
select.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView&lt;*&gt;?,
view: View?,
position: Int,
id: Long
) {
val selectedItem = parent?.getItemAtPosition(position).toString()
}
override fun onNothingSelected(parent: AdapterView&lt;*&gt;?) {
Toast.makeText(
requireContext(),
&quot;Seleziona una sezione!&quot;,
Toast.LENGTH_SHORT,
).show()
}
}
btn.setOnClickListener {
if (select.visibility == View.INVISIBLE) {
select.visibility = View.VISIBLE
} else {
select.visibility = View.INVISIBLE
}
}
}
})
}
}
private fun checkBookCatalogo() {
if (FirebaseAuth.getInstance().currentUser != null) {
val cUser = FirebaseAuth.getInstance().currentUser!!
Log.d(&quot;TAG&quot;, &quot;Sono :&quot;)
val database =
FirebaseDatabase.getInstance(&quot;https://booktique-87881-default-rtdb.europe-west1.firebasedatabase.app/&quot;)
val usersRef = database.reference.child(&quot;Utenti&quot;)
val childRef = usersRef.child(cUser.uid)
val catalogoRef = childRef.child(&quot;Catalogo&quot;)
val daLeggereRef = catalogoRef.child(&quot;DaLeggere&quot;)
/*
val lettiRef = catalogoRef.child(&quot;Letti&quot;)
val inCorsoRef = catalogoRef.child(&quot;InCorso&quot;)
*/
daLeggereRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val daLeggereBooks = arrayListOf&lt;LibriDaL&gt;()
if (snapshot.exists()) {
for (bookSnapshot in snapshot.children) {
val libriDaL = bookSnapshot.getValue(LibriDaL::class.java)
Log.d(&quot;TAG&quot;, &quot;VolumeDet : $libriDaL&quot;)
daLeggereBooks.add(libriDaL!!)
}
}
// Richiama la funzione per i libri &quot;DaLeggere&quot;
loadBooks(daLeggereBooks)
}
override fun onCancelled(error: DatabaseError) {
// Gestisci eventuali errori nella lettura dei dati
Log.e(&quot;TAG&quot;, &quot;Errore nel recupero dei dati&quot;, error.toException())
}
})
}
}
private fun loadBooks(books: List&lt;LibriDaL&gt;?){
if (books != null) {
listaLibri.addAll(books)
Log.d(&quot;TAG&quot;,&quot;LIBRI: $listaLibri&quot; )
adapter = MyAdapterDL(listaLibri)
Log.d(&quot;TAG&quot;,&quot;LIBRI:11: $listaLibri&quot; )
recyclerView.adapter = adapter
isRecyclerViewPopulated = true
}
}
}

And this is the Adapter, MyAdapterDL:

class MyAdapterDL(private val listaLibri : ArrayList&lt;LibriDaL&gt;) :
RecyclerView.Adapter&lt;MyAdapterDL.MyViewHolder&gt;() {
private lateinit var bListener : onItemClickListener
interface onItemClickListener{
fun onItemClick(position: Int)
fun moveBook(spinner: Spinner, send : ImageButton)
}
fun setOnCLickItemListener(listener: onItemClickListener){
bListener = listener
}
class MyViewHolder(itemView : View, listener: onItemClickListener) : RecyclerView.ViewHolder(itemView){
val cover : ImageButton = itemView.findViewById(R.id.coverDL)
val titolo : TextView = itemView.findViewById(R.id.titoloDL)
val autore : TextView = itemView.findViewById(R.id.autoreDL)
init {
itemView.setOnClickListener {
listener.onItemClick(adapterPosition)
}
itemView.findViewById&lt;ImageButton&gt;(R.id.sendDL).setOnClickListener {
listener.moveBook(itemView.findViewById(R.id.spinnerDL),itemView.findViewById(R.id.sendDL))
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.lista_libri_da_leggere,parent,false)
return MyViewHolder(itemView,bListener)
}
override fun getItemCount(): Int {
return listaLibri.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = listaLibri[position]
Glide.with(holder.itemView.context)
.load(currentItem.copertina)
.into(holder.cover)
holder.titolo.text = abbreviaInfo(currentItem?.titolo ?: &quot;&quot;,25)
holder.autore.text = abbreviaInfo(currentItem?.autori ?: &quot;&quot;,25)
}
fun abbreviaInfo(stringa: String, lunghezzaMassima: Int): String {
return if (stringa.length &lt;= lunghezzaMassima) {
stringa
} else {
val sottostringa = stringa.take(lunghezzaMassima)
&quot;$sottostringa...&quot;
}
}
}

I am sure that the database call returns the books correctly, as before adding the listener to the adapter the recyclerview opened correctly and showed the books.
I also tried with "fake" books i.e. created directly in the class and in this case the listener gives no problems and works.
What I thought is that since the call to the database is asynchronous, the books are loaded after the page is opened and therefore the recyclerview at the time of creation has no elements on which to then be able to apply the listener and that is why it crashes. In fact I tried running the code:

            adapter.setOnCLickItemListener(object : MyAdapterDL.onItemClickListener {

only after setting the adapter, but it doesn't go.
I tried this with a flag private var isRecyclerViewPopulated = false and setting it true at the end of the function loadBooks but nothing.
The only thing I have done more than when it worked is the spinner, but I don't think that is the problem, as commenting on it doesn't work anyway. I don't know if maybe something is wrong in the call to database, I am new to this area. Thank you all very much in advance!

答案1

得分: 1

以下是翻译好的部分:

你的问题在这里:

if(isRecyclerViewPopulated) {

这部分代码在loadBooks 被调用之前执行,因为 checkBookCatalogo 是异步的,所以

adapter.setOnCLickItemListener

在你设置监听器的地方从未被调用过。所以你要么需要移除if条件检查,要么在loadBooks 被调用之后设置监听器。

还有一些其他问题需要修复。

MyViewHolder 中移除监听器参数,并更新 onBindViewHolder 如下:

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

将代码移到视图持有者的 bind 函数中,在这个 bind 函数中也将视图持有者内的所有代码移到里面,同时移除 init 块,并最后将视图持有者类标记为 inner

英文:

Your issue is here:

if(isRecyclerViewPopulated) {

this part of code executes before loadBooks is ever called as checkBookCatalogo is asynchronous, so

adapter.setOnCLickItemListener

where you set the listener is never called.
So you either need to remove the if condition check or set the listener after loadBooks is called

Some other issues you need to fix.

Remove listener parameter from MyViewHolder and update onBindViewHolder to:

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

and move the code to the bind function in the viewholder, move all code in viewholder inside this bind function also remove the init block, and finally mark the viewholder class as inner

答案2

得分: 0

我成功了,问题在于我要在这里创建一个新的适配器:

 private fun loadBooks(books: List<LibriDaL>?){
if (books != null) {
listaLibri.addAll(books)
Log.d("TAG","LIBRI: $listaLibri" )
adapter = MyAdapterDL(listaLibri)
Log.d("TAG","LIBRI:11: $listaLibri" )
recyclerView.adapter = adapter
isRecyclerViewPopulated = true
}
}

这没有设置监听器,因此将该函数更改为以下函数:

 private fun loadBooks(books: List<LibriDaL>?){
if (books != null) {
listaLibri.addAll(books)
adapter.notifyDataSetChanged()
}
}
英文:

I succeeded, the problem was that I was going to create a new adapter here:

 private fun loadBooks(books: List&lt;LibriDaL&gt;?){
if (books != null) {
listaLibri.addAll(books)
Log.d(&quot;TAG&quot;,&quot;LIBRI: $listaLibri&quot; )
adapter = MyAdapterDL(listaLibri)
Log.d(&quot;TAG&quot;,&quot;LIBRI:11: $listaLibri&quot; )
recyclerView.adapter = adapter
isRecyclerViewPopulated = true
}
}

That had not set the listener, however, and so changing that function to this one:

 private fun loadBooks(books: List&lt;LibriDaL&gt;?){
if (books != null) {
listaLibri.addAll(books)
adapter.notifyDataSetChanged()
}
}

huangapple
  • 本文由 发表于 2023年7月3日 19:26:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76604291.html
匿名

发表评论

匿名网友

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

确定