RecyclerView динамически добавляет элемент. NotifyDataChanged не работает

#android #kotlin #android-recyclerview

#Android #kotlin #android-recyclerview

Вопрос:

В этом приложении я добавляю адреса в учетную запись и сохраняю их в базе данных реального времени (firebase).Я также хочу отобразить их в recyclerview, но они не видны.

Вот визуализация моей проблемы: https://youtu.be/OdlZNUQnA-k

Код должен работать следующим образом

Addressfragment содержит AddressesRecyclerview

AddAddressfragment для добавления нового адреса, и он возвращается к AddressFragment при добавлении нового адреса.

Также, когда я пытался отобразить все элементы из одного массива, например, почтовый индекс.Отображается только последний добавленный элемент. Даже для каждого цикла. Как и последний элемент, он удаляется после добавления

Я понимаю, что для этого нужно что-то вроде notifyDataSetChanged(), но здесь это не работает

Вот код:

AddressFragment:

         package com.querto.fragments.address

    import android.os.Bundle
    import androidx.fragment.app.Fragment
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.Toast
    import androidx.lifecycle.ViewModelProvider
    import androidx.recyclerview.widget.LinearLayoutManager
    import com.google.firebase.auth.FirebaseAuth
    import com.google.firebase.database.DatabaseReference
    import com.google.firebase.database.FirebaseDatabase
    import com.querto.R
    import com.querto.adapters.AddressAdapter
    import com.querto.viewmodel.MainActivityViewModel
    import kotlinx.android.synthetic.main.activity_main.*
    import kotlinx.android.synthetic.main.fragment_address.view.*


    class AddressFragment : Fragment() {
        private lateinit var mMainActivityViewModel: MainActivityViewModel
        private lateinit var database: DatabaseReference
        private lateinit var mAuth: FirebaseAuth
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                                  savedInstanceState: Bundle?): View? {
            // Inflate the layout for this fragment
            var view =  inflater.inflate(R.layout.fragment_address, container, false)
            database = FirebaseDatabase.getInstance().reference
            mAuth = FirebaseAuth.getInstance()


           mMainActivityViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().application).create(
                MainActivityViewModel::class.java)
            if(mAuth.currentUser==null){
                Toast.makeText(requireContext(), "To add address please login",Toast.LENGTH_SHORT).show()
                activity?.nav_view?.setCheckedItem(R.id.login)
                activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.loginFragment)?.commit()

            }

            view.recyclerViewAddress.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
            view.recyclerViewAddress.adapter = AddressAdapter(requireContext(), mMainActivityViewModel.list_of_addresses)



            view.add_address_btn.setOnClickListener {
                activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addAddressFragment)?.commit()
            }
            return view
        }


    }
 

Адаптер:

     package com.querto.adapters
    
    import android.app.Application
    import android.content.Context
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import androidx.lifecycle.ViewModelProvider
    import androidx.recyclerview.widget.RecyclerView
    import com.google.firebase.auth.FirebaseAuth
    import com.google.firebase.database.DatabaseReference
    import com.google.firebase.database.FirebaseDatabase
    import com.querto.R
    import com.querto.model.Address
    import com.querto.viewmodel.MainActivityViewModel
    import kotlinx.android.synthetic.main.my_address_row.view.*
    
    class AddressAdapter(contextAdapter: Context, addresses: ArrayList<Address>):
    
    
        RecyclerView.Adapter<AddressAdapter.MyViewHolder>() {
    
        private var mMainActivityViewModel: MainActivityViewModel
        private val context: Context = contextAdapter
        private val local_addreses : ArrayList<Address> = addresses
        private  var database: DatabaseReference
        private  var mAuth: FirebaseAuth
    
        init {
            mMainActivityViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(context.applicationContext as Application).create(
                MainActivityViewModel::class.java)
            database = FirebaseDatabase.getInstance().reference
            mAuth = FirebaseAuth.getInstance()
        }
    
    
    
        class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
            val currentTitle = itemView.address_title
            val currentId = itemView.address_id
            val currentStreet = itemView.address_street
            val currentPostcode = itemView.address_postcode
            val currentHouseNumber = itemView.address_number
            val currentAddressCityName = itemView.address_city
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    
            return MyViewHolder(LayoutInflater.from(context).inflate(R.layout.my_address_row, parent, false))
        }
    
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            holder.currentTitle.text =  local_addreses[position].name
            holder.currentId.text = (position   1).toString()
            holder.currentStreet.text =local_addreses[position].street
            holder.currentPostcode.text =local_addreses[position].postcode
            holder.currentHouseNumber.text = local_addreses[position].house_number
            holder.currentAddressCityName.text = local_addreses[position].city_name
        }
    
        override fun getItemCount(): Int {
            return local_addreses.size
        }
    
        fun addAddress(address: Address){
            mMainActivityViewModel.list_of_addresses.add(address)
            notifyDataSetChanged()
    
        }
    }
 

AddAddress:

     package com.querto.fragments.address
    
    import android.os.Bundle
    import androidx.fragment.app.Fragment
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.Toast
    import androidx.lifecycle.ViewModelProvider
    import com.google.firebase.auth.FirebaseAuth
    import com.google.firebase.database.DatabaseReference
    import com.google.firebase.database.FirebaseDatabase
    import com.querto.R
    import com.querto.adapters.AddressAdapter
    import com.querto.model.Address
    import com.querto.viewmodel.MainActivityViewModel
    import kotlinx.android.synthetic.main.fragment_add_address.view.*
    
    class AddAddressFragment : Fragment() {
        private lateinit var database: DatabaseReference
        private lateinit var mAuth: FirebaseAuth
        private lateinit var mMainActivityViewModel: MainActivityViewModel
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
           var view = inflater.inflate(R.layout.fragment_add_address, container, false)
    
    
    
            mMainActivityViewModel =
                ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)
                    .create(MainActivityViewModel::class.java)
    
            view.addAddressButton.setOnClickListener {
    
                val addressName = view.addAddressName.text.toString()
                val addressStreet = view.addAddressStreet.text.toString()
                val addressNumber = view.addAddressHouseNumber.text.toString()
                val addressZipCode = view.addAddressCityZipCode.text.toString()
                val addressCityName = view.addAddressCityName.text.toString()
    
                if(inputCheck(addressName,addressStreet,addressNumber,addressZipCode, addressCityName)){
                    mAuth = FirebaseAuth.getInstance()
                    database = FirebaseDatabase.getInstance().reference
    
                    addAddress(addressName, addressStreet, addressNumber, addressZipCode, addressCityName)
                }else{
                    Toast.makeText(requireContext(), "Please enter all fields", Toast.LENGTH_SHORT).show()
                }
            }
            return view
        }
    
        private fun addAddress(addressName: String, addressStreet: String, addressNumber: String,addressZipCode: String, addressCityName: String) {
    
            val address = Address(mAuth.currentUser?.uid, addressName, addressStreet,addressZipCode, addressNumber, addressCityName)
            database.child("addresses").child(database.push().key.toString()).setValue(address).addOnCompleteListener {
                if(it.isSuccessful){
    
                    val addressAdapter= AddressAdapter(requireContext(),  mMainActivityViewModel.list_of_addresses)
                    addressAdapter.addAddress(address)
                    activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addressFragment)?.commit()
                    Toast.makeText(requireContext(), "Added address", Toast.LENGTH_SHORT).show()
    
                }else{
                    Toast.makeText(requireContext(), "Fail at creating address", Toast.LENGTH_SHORT).show()
                }
            }
    
    
    
        }
    
        private fun inputCheck(addressName: String, addressStreet: String, addressNumber: String,addressZipCode: String, adressCityName: String)=
            addressName.isNotEmpty() amp;amp; addressStreet.isNotEmpty() amp;amp; addressNumber.isNotEmpty() amp;amp; addressZipCode.isNotEmpty() amp;amp; adressCityName.isNotEmpty() amp;amp; addressZipCode.length==5
    
    
    
    }
 

MainActivityViewModel:

 class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
    private lateinit var database: DatabaseReference
    private lateinit var mAuth: FirebaseAuth
    val homeFragment = HomeFragment()
    val loginFragment = LoginFragment()
    val registerFragment = RegisterFragment()
    val detailsFragment = DetailsFragment()
    val addressFragment = AddressFragment()
    val addAddressFragment = AddAddressFragment()
 
 
    var pizza_names: Array<String> = application.resources.getStringArray(R.array.pizza_titles)
    var pizza_desc: Array<String> = application.resources.getStringArray(R.array.pizza_desc)
    val pizza_small_price: IntArray = application.resources.getIntArray(R.array.pizza_small_price)
    val pizza_medium_price: IntArray = application.resources.getIntArray(R.array.pizza_medium_price)
    val pizza_big_price: IntArray = application.resources.getIntArray(R.array.pizza_big_price)
    var pizza_img: Array<Int> = arrayOf(R.drawable.napoletana, R.drawable.margherita, R.drawable.estate, R.drawable.pepperone, R.drawable.pancetta, R.drawable.ortolana, R.drawable.marinara, R.drawable.diavola, R.drawable.messicana, R.drawable.quattro_formaggi, R.drawable.sugoza, R.drawable.semola, R.drawable.capriciossa, R.drawable.vulcano, R.drawable.romana, R.drawable.capodanno, R.drawable.primavera, R.drawable.regina, R.drawable.quattro_stagioni, R.drawable.cilento, R.drawable.tirolese, R.drawable.michele, R.drawable.pollo, R.drawable.havana, R.drawable.siciliana, R.drawable.sandra, R.drawable.bari, R.drawable.gringo, R.drawable.angelo, R.drawable.spinaci)
 
 
    var focaccia_names: Array<String> = application.resources.getStringArray(R.array.foaccia_titles)
    var focaccia_desc: Array<String> = application.resources.getStringArray(R.array.foaccia_desc)
    val focaccia_price: IntArray = application.resources.getIntArray(R.array.foaccia_price)
    var focaccia_img: Array<Int> = arrayOf(R.drawable.base, R.drawable.nutella)
 
 
    var calzone_names: Array<String> = application.resources.getStringArray(R.array.calzone_titles)
    var calzone_desc: Array<String> = application.resources.getStringArray(R.array.calzone_desc)
    val calzone_price_normal: IntArray = application.resources.getIntArray(R.array.calzone_normal_price)
    val calzone_price_big: IntArray = application.resources.getIntArray(R.array.calzone_big_price)
    var calzone_img: Array<Int> = arrayOf(R.drawable.calzone)
 
 
    var panuozzo_names: Array<String> = application.resources.getStringArray(R.array.panuozzo_titles)
    var panuozzo_desc: Array<String> = application.resources.getStringArray(R.array.panuozzo_desc)
    val panuozzo_price_normal: IntArray = application.resources.getIntArray(R.array.panuozzo_normal_price)
    val panuozzo_price_big: IntArray = application.resources.getIntArray(R.array.panuozzo_big_price)
    var panuozzo_img: Array<Int> = arrayOf(R.drawable.panuozzo)
 
    val sosy_names: Array<String> = application.resources.getStringArray(R.array.sosy_titles)
    val sosy_price: IntArray = application.resources.getIntArray(R.array.sosy_price)
 
    val napoje_names: Array<String> = application.resources.getStringArray(R.array.napoje_titles)
    val napoje_price: IntArray = application.resources.getIntArray(R.array.napoje_price)
    val napoje_first_kind: Array<String> = application.resources.getStringArray(R.array.napoje_kinds_one)
    val napoje_second_kind: Array<String> = application.resources.getStringArray(R.array.napoje_kinds_two)
 
    val dodatki_names: Array<String> = application.resources.getStringArray(R.array.dodatki_titles)
    val dodatki_small_price: IntArray = application.resources.getIntArray(R.array.dodatki_small_price)
    val dodatki_medium_price: IntArray = application.resources.getIntArray(R.array.dodatki_medium_price)
    val dodatki_big_price: IntArray = application.resources.getIntArray(R.array.dodatki_big_price)
 
    var list_of_addresses = ArrayList<Address>()
 
 
    private val mutableLoginStatus = MutableLiveData<Boolean>()
    val loginStatus: LiveData<Boolean>
        get() = mutableLoginStatus
 
    fun checkLogin(username: String, password: String) {
        viewModelScope.launch(Dispatchers.IO) {
 
            database = FirebaseDatabase.getInstance().reference
            mAuth = FirebaseAuth.getInstance()
 
                mAuth.signInWithEmailAndPassword(username,password).addOnCompleteListener{
                    if(it.isSuccessful){
                        mutableLoginStatus.postValue(true)
                    }else{
                        mutableLoginStatus.postValue(false)
                    }
                }
        }
    }
 
 
 
    fun shareApp(context: Context) {
 
        val openURL = Intent(android.content.Intent.ACTION_VIEW)
 
        openURL.data = Uri.parse("https://www.facebook.com/1488596184507308/")
        context.startActivity(openURL)
 
 
    }
 
 
 
 
    fun sendMail(context: Context) {
        val sendEmail = Intent(Intent.ACTION_SEND)
        val email: Array<String> = arrayOf("kontakt@cilento.pl")
        sendEmail.setData(Uri.parse("mailto: kontakt@cilento.pl "))
        sendEmail.putExtra(Intent.EXTRA_SUBJECT, "Problem z Usługą")
        sendEmail.putExtra(Intent.EXTRA_TEXT, "Pizza którą zamówiłem nie przyszła na czas.nnnMoje Dane Kontaktowe: nnImie: nNazwisko: nAdres: ")
        sendEmail.setType("message/rfc822")
        sendEmail.putExtra(Intent.EXTRA_EMAIL, email)
        val chooser = Intent.createChooser(sendEmail, "Send mail using")
        context.startActivity(chooser)
    }
 
}
 

Класс адреса:

  package com.querto.model

 data class Address(
    val userId: String?,
     val name: String?,
    val street: String?,
      val postcode: String?,
    val house_number: String?,
     val city_name: String?
       )
 

Комментарии:

1. Я не знаю kotlin, но обычно я могу понять, что происходит, но это слишком сложно с вашим кодом. Прежде всего, не стоит передавать в адаптер много разных массивов вместо одного. Тогда предполагается, что notifyDataSetChanged вызывается, когда данные только что изменились.

2. Насколько я понимаю, вы вызываете новый фрагмент, когда хотите добавить новый элемент? Где в этом случае хранятся старые данные?

3. в private fun addAddress вы пишете — val addressAdapter= AddressAdapter . Означает ли это, что вы создаете новый AddressAdapter? если да, то это работает не так

Ответ №1:

После проверки MainActivityViewModel я вижу, что вы не извлекаете данные из базы данных firebase. Вы должны добавить это в класс AddressFragment также добавьте переменную с именем addressAdapter в верхней части класса

 
fun getAddresses(){
   val ref = FirebaseDatabase.getInstance().reference.child("addresses")
   ref.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                if (dataSnapshot.exists()) {
                   val address = dataSnapshot.getValue<Address>()
                   mMainActivityViewModel.addresses_list.add(address)
                   addressAdapter.notifyDataChanged()
                } 
            }

            override fun onCancelled(databaseError: DatabaseError) {
              
            }
        })

}


 

Также реализация Address adapter здесь ничего не делает. просто удалите его.

 
private fun addAddress(addressName: String, addressStreet: String, addressNumber: String,addressZipCode: String, addressCityName: String) {
    
            val address = Address(mAuth.currentUser?.uid, addressName, addressStreet,addressZipCode, addressNumber, addressCityName)
            database.child("addresses").child(database.push().key.toString()).setValue(address).addOnCompleteListener {
                if(it.isSuccessful){
                    val addressAdapter= AddressAdapter(requireContext(),  mMainActivityViewModel.address_title, mMainActivityViewModel.address_street,mMainActivityViewModel.address_post_code, mMainActivityViewModel.address_house_number, mMainActivityViewModel.address_city_name)
                    addressAdapter.addAddress(addressName, addressStreet,addressZipCode, addressNumber, addressCityName)
                    activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addressFragment)?.commit()
                    Toast.makeText(requireContext(), "Added address", Toast.LENGTH_SHORT).show()
                }else{
                    Toast.makeText(requireContext(), "Fail at creating address", Toast.LENGTH_SHORT).show()
                }
            }

 

Комментарии:

1. Вы извлекаете адреса из базы данных Firebase в реальном времени?

2. Предполагается извлечь адреса из базы данных и добавить их в список массива адресов в MainViewModel

3. Вы уже сохраняете данные в базе данных firebase.

4. вы должны вызвать getAddresses() в onCreateView() , также нет необходимости в адаптере в addAddressFragment , он там не нужен. вы уже сохранили адрес в базе данных.

5. мой github с полным кодом: github.com/polonez-byte-112/Querto

Ответ №2:

Я попытаюсь объяснить это здесь. Когда вы создаете пользовательский адаптер RecyclerView, обычно предполагается передавать данные, которые вы хотите отобразить в списке. Для меня лучше передать массив класса, который вы создали для передачи данных для одного элемента просмотра ArrayList<ClassOfSingleItem> Recylcer, но, конечно, это необязательно. Поэтому, когда вы вызываете notifyDataSetCahnged, вы уведомляете свой адаптер о том, что данные в этом ArrayList были изменены. Но этот вызов не будет работать, если вы создаете новый адаптер каждый раз, когда добавляете или удаляете что-то из списка RecyclerView.

РЕДАКТИРОВАТЬ: позвольте мне объяснить, что я вижу: у вас есть кнопка с прослушивателем щелчков, при нажатии на нее вы заменяете текущий фрагмент на тот, в который вы добавляете данные.

  view.add_address_btn.setOnClickListener {
             activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addAddressFragment)?.commit()
            }
 

затем, когда вы завершите редактирование всех полей, что вы делаете? замените ваш addAdressFragment на addressFragment:

 val addressAdapter= AddressAdapter(requireContext(),  mMainActivityViewModel.list_of_addresses)
                    addressAdapter.addAddress(address)
                    activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addressFragment)?.commit()
 

внутри этого метода вы СОЗДАЕТЕ новый AdressAdapter для вызова метода addAdrees, внутри которого есть вызов notifyDataSetChanged. Но RecyclerView в вашем addressFragment понятия не имеет об этом новом AddressAdapter. Это главная проблема. Как я уже упоминал в комментарии под моим ответом, существует только два способа обновления RecyclerView, поэтому реализуйте один из них.
Ниже я показываю некоторый java-код (вы упомянули, что знаете java), который может дать вам представление:

 public class PointActivity extends BaseActivity  
       implements SearchView.OnQueryTextListener{
private RecyclerView mRecyclerView;  
@Override                                                            
protected void onCreate(@Nullable Bundle savedInstanceState) {       
    super.onCreate(savedInstanceState);                              
    setContentView(R.layout.activity_point);                                                                         
    mRecyclerView = findViewById(R.id.point_list);                   
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
    List<PointItem> pointItems = getFilteredAndSortedList();          
    mRecyclerView.setAdapter(new PointAdapter(this, pointItems));  
}   

//here we need to update recyclerView   
   @Override                                           
  public boolean onQueryTextSubmit(String query) {    
      searchResult(query);                            
      return false;                                   
 }                                                   
                                                      
public void searchResult(String query) {                                                                                               
    if (query.isEmpty()) {  
//just full list if query is empty                                                                                                           
        mRecyclerView.setAdapter(new PointAdapter(this, getFilteredAndSortedList()));                                                  
    } else {                                                                                                                           
        PointSearchManager pointSearchManager = new PointSearchManager();                                                              
        List<PointItem> list;                                                                                                          
        list = Arrays.asList(pointSearchManager.toFilters(getFilteredAndSortedList().toArray(new PointItem[0]), query)); 
//list filtered with query result              
        mRecyclerView.setAdapter(new PointAdapter(this, list));                                                                        
    }                                                                                                                                  
}                                                                                                                                         
       

                                     
 

Комментарии:

1. Означает ли этот код в kotlin — val addressAdapter= AddressAdapter(requireContext … — означает, что вы создаете новый экземпляр AddressAdapter или нет?

2. Неужели kotlin так отличается от других языков, где для объявления переменной используются такие ключевые слова, как val или var?

3. я уверен, что даже если я создал отдельный класс, который будет отображаться. это не сработает

4. Прочитайте мой ответ еще раз, пожалуйста. Есть два способа обновить RecyclerView. Сначала сохраните массив объектов, содержащих данные, которые вы хотите отобразить как локальную переменную, и вызывайте notifyDataSetChanged каждый раз, когда вы добавляете или удаляете объект из массива. Второй вызов RecyclerView.setAdapter(новый AddressAdapter(контекст, arrayListOfObjectsWithData))