#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))