I have a simple recycler view that reads a list from the database, I can see data when I log them to the console, but it doesn't show in the recycler view
The function below is in the kotlin fragment class for the xml layout containing the recycler view
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// Inflate the layout for this fragment
val view: View = inflater.inflate(R.layout.fragment_chat_list, container, false)
view.recyclerView?.setHasFixedSize(true)
view.recyclerView?.layoutManager = LinearLayoutManager(context)
ref?.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
val bal = snapshot.getValue<UserInfo.Uids>()
if (bal != null) {
UserInfo.Uids(bal.email,bal.uid,bal.displayName)
}
Log.d("RecyclerView", UserInfo.Uids().toString())
Log.d("RecyclerView", bal.toString())
adapter = BalAdapter(requireContext(), ArrayList<UserInfo.Uids>(), R.layout.users)
adapter!!.notifyDataSetChanged()
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
TODO("Not yet implemented")
}
override fun onChildRemoved(snapshot: DataSnapshot) {
TODO("Not yet implemented")
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
TODO("Not yet implemented")
}
override fun onCancelled(p0: DatabaseError) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})
adapter = BalAdapter(requireContext(), ArrayList<UserInfo.Uids>(), R.layout.users)
view.recyclerView.adapter = adapter
adapter?.notifyDataSetChanged()
return view
}
R.layout.fragment_chat_list is the xml containing the recycler view tag
R.layout.users is the data xml
view.recyclerView is the recycler view tag
BalAdapter is the adapter class, can be provided on request, but I feel the code there is correct
UserInfo.Uids is the data class, contains name and email
adapter variable was declared before the override fun onCreateView
Related
i have a fragment with a RecyclerView and TextInputEditText (for searching through Firebase Database)
When I start writing some text to filter the RecyclerView, I get the correct result, however if I remove text from TextInputEditText, the positions of the elements are not updated. For example: I have a RecyclerView of 3 elements, I filter by name and RecyclerView shows me only the third element, then I delete all the text with the TextInputEditText, all elements are returned, but my third element has the position of the first element. How can i fix this problem?
My code and video below:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
setupPlacesAdapter()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
FirebaseApp.initializeApp(requireContext())
getPlaces()
binding.searchBar.addTextChangedListener(object: TextWatcher {
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, size: Int) {
val query = binding.searchBar.text.toString().uppercase()
if (query.isEmpty()) {
getPlaces()
} else {
filterByName(query)
}
}
})
}
private fun setupPlacesAdapter() {
placeAdapter = PlaceAdapter()
binding.rvPlaces.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.HORIZONTAL,
false
)
binding.rvPlaces.adapter = placeAdapter
}
private fun getPlaces() {
val reference = FirebaseDatabase.getInstance().getReference("Places")
reference.addValueEventListener(object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val list = ArrayList<Place>()
for (placeSnapshot in snapshot.children) {
val place = placeSnapshot.getValue(Place::class.java)
list.add(place!!)
}
placeAdapter.submitList(list)
placeAdapter.notifyDataSetChanged()
}
}
})
}
private fun filterByName(query: String) {
Log.d("MyTag", "Filtered")
val reference = FirebaseDatabase.getInstance()
.getReference("Places")
.orderByChild("name")
.startAt(query)
.endAt(query + "\uf8ff")
reference.addValueEventListener(object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val list = ArrayList<Place>()
for (placeSnapshot in snapshot.children) {
val place = placeSnapshot.getValue(Place::class.java)
list.add(place!!)
}
placeAdapter.submitList(list)
placeAdapter.notifyDataSetChanged()
}
}
})
}
Demonstration of the problem itself on the video at the link: https://youtu.be/YlD9LTppC0s
I have a problem with the spinner inside a fragment. The spinner is filled with data, but when I select an item I don't see logs, and in the spinner, it does not select elements. When I used nearly the same code as an activity it worked (Just changed the context to this in the adapter)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_edit_stoper, container, false)
val tagSpinner = view.findViewById<Spinner>(R.id.editSpinner)
val items: MutableList<String> = ArrayList("a","b","c")
tagSpinner.adapter = ArrayAdapter(this.requireActivity(), android.R.layout.simple_spinner_item, items) as SpinnerAdapter
tagSpinner.onItemSelectedListener = this
return view
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
Log.d(TAG,"OnItemSelected: $type")
}
override fun onNothingSelected(parent: AdapterView<*>?) {
Log.d(TAG,"error")
}
}
Is your fragment really a listener for spinner?
Write a custom listener and use that instead of your fragment.
inner class SpinnerStateChangeListener : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
// do sth here
}
override fun onNothingSelected(parent: AdapterView<*>?) {
// do sth here
}
}
Now you can set your listener like this:
tagSpinner.onItemSelectedListener = SpinnerStateChangeListener()
Try moving your implementation from Fragment directly to spinner. remove override from class and instead of
tagSpinner.onItemSelectedListener = this
do
tagSpinner.onItemSelectedListener = object :AdapterView.OnItemSelectedListener{
override fun onNothingSelected(parent: AdapterView<*>?) {
Log.d(TAG,"error")
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
Log.d(TAG,"OnItemSelected: $type")
}
}
The solution is to add requireActivity().applicationContext in the adapter to change the activityFragment to a context:
...
val items: MutableList<String> = ArrayList("a","b","c")
tagSpinner.adapter = ArrayAdapter(requireActivity().applicationContext, android.R.layout.simple_spinner_item, items) as SpinnerAdapter
tagSpinner.onItemSelectedListener = this
...
I am building an app in which user can favourite some items.I am showing this in a recyclerview inside a fragment. User can also delete the item from favourite item list by clicking on delete button. The problem I am facing is that the if user delete and item at the end of the list the recylerview reloads and show from the start. I am using MVVM and using Livedata. Here is my code
FragmentFavorite.kt
class FavoriteFragment :Fragment() {
lateinit var favoriteViewModel: FavoriteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onAttach(context: Context) {
super.onAttach(context)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v=inflater.inflate(R.layout.fragment_favorite,container,false)
favoriteViewModel=ViewModelProvider(requireActivity()).get(FavoriteViewModel::class.java)
val recylerview=v.findViewById<RecyclerView>(R.id.recylerviewFav)
recylerview.layoutManager=LinearLayoutManager(requireContext(),RecyclerView.VERTICAL,false)
favoriteViewModel.getAll(requireContext()).observe(requireActivity(), Observer {
val adapter=FavoriteAdapter(it,requireContext(),object:FavoriteDeleteListener{
override fun OnFavDelete(id: Int) {
favoriteViewModel.deleteFav(requireContext(),id)
}
})
recylerview.adapter=adapter
})
return v
}
}
RecylerviewAdapter.kt
class FavoriteAdapter(val list:List<Favorite>,context: Context,val listener:FavoriteDeleteListener) :RecyclerView.Adapter<FavoriteAdapter.MyViewholder> (){
val listofFav=list.reversed()
class MyViewholder(itemview: View):RecyclerView.ViewHolder(itemview) {
val textview_src=itemview.findViewById<TextView>(R.id.textview_src)
val textView_tar=itemView.findViewById<TextView>(R.id.textview_tar)
val delte_fav=itemview.findViewById<ImageView>(R.id.delete_fav)
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FavoriteAdapter.MyViewholder {
val v=LayoutInflater.from(parent.context).inflate(R.layout.fav_list,parent,false)
return MyViewholder(v)
}
override fun onBindViewHolder(holder: FavoriteAdapter.MyViewholder, position: Int) {
holder.textView_tar.text=listofFav[position].translated_text
holder.textview_src.text=listofFav[position].text_to_translate
holder.delte_fav.setOnClickListener {
listofFav[position].fid?.let { it1 -> delete(it1,listener)
notifyItemRemoved(position)
}
}
}
override fun getItemCount(): Int {
return listofFav.size
}
fun delete(id:Int,listene: FavoriteDeleteListener) {
listene.OnFavDelete(id)
}
}
I suspect the problem is here:
favoriteViewModel.getAll(requireContext()).observe(requireActivity(), Observer {
val adapter=...
recylerview.adapter=adapter
})
If deleting an item from your dataset causes the observer to emit a new dataset, this will wind up re-assigning your RecyclerView's adapter, which will completely reset the view position.
You could use the ListAdapter component in order to compute a diff between the lists instead of completely overwriting the adapter everyt ime.
I am trying to fetch data from the firebase realtime database and I am storing it in a list of User data class to display it in the recycler view. But the list is not storing any data. I tried to add some dummy data during initialization and that is working perfectly. But after initialization, it is not storing anything.
I tried with the mutable list too, but that is also not working.
The main fragment where I am retrieving the data:
class UserChatFragment : Fragment() {
lateinit var mobileno: String
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_user_chat, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val session = sessionManager(context!!)
mobileno = session.getLoginContact()
val UserList = getusersdata()
Chat_recyclerview.adapter = UsersAdapter(UserList)
Chat_recyclerview.layoutManager = LinearLayoutManager(context)
contact.text = mobileno
}
private fun getusersdata(): List<User> {
val list: = ArrayList<User>()
val databaseReference =
FirebaseDatabase.getInstance().reference.child("Users").child("Connections")
.child(mobileno)
databaseReference.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
for (data: DataSnapshot in snapshot.children) {
val userReference = FirebaseDatabase.getInstance().reference.child("Users")
.child("Accounts").child(data.key!!)
userReference.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val item = User(
snapshot.child("name").value.toString(),
snapshot.child("profilePicture").value.toString(),
snapshot.child("phone").value.toString()
)
list.add(item)
}
}
override fun onCancelled(error: DatabaseError) {
Toast.makeText(context, error.message, Toast.LENGTH_SHORT).show()
}
})
}
}
}
override fun onCancelled(error: DatabaseError) {
Toast.makeText(context, error.message, Toast.LENGTH_SHORT).show()
}
})
return list
}
}
Adapter class:
class UsersAdapter(private val userList: List<User>) :
RecyclerView.Adapter<UsersAdapter.UserViewHolder>() {
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val mName: TextView = itemView.cardname
val mImage: CircleImageView = itemView.cardprofilepicture
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.cardview_chat, parent, false)
return UserViewHolder(itemView)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val currentItem = userList[position]
// Glide.with(UsersAdapter).load()
Picasso.get().load(currentItem.profileimageurl).into(holder.mImage)
holder.mName.text = currentItem.name
}
override fun getItemCount()=userList.size
}
No syntax error
Your method to obtain the list getUserList() is asynchronous; meaning it needs to wait for firebase to produce results.
You can see this because you do databaseReference.addListenerForSingleValueEvent... meaning you're adding a listener that will be called in the future, not synchronously.
Your method (getUserList) creates a new list, and returns it (empty).
Later, your firebase callback is called and you modify the local list; it's already "too late".
As a "quick hack" you can change the getUserList() method to something more like fun fetchResultsAndInitializeAdapter() (which is a little bit of a code smell because a function is doing "two things" but it should work). It doesn't return anything.
So inside the fetchResultsAndInitializeAdapter you can initialize the adapter after the list is populated:
override fun onDataChange(snapshot: DataSnapshot) {
val list: List<xxx>
if (snapshot.exists()) {
val item = User(
snapshot.child("name").value.toString(),
snapshot.child("profilePicture").value.toString(),
snapshot.child("phone").value.toString()
)
list.add(item)
}
adapter = YourAdapter(list)
recyclerView.adapter = adapter
}
Now, I wouldn't do this. I believe this database transaction and list mutation does not belong here, it belongs in a repo or a viewmodel that exposes a LiveData<List> that is observed by your Fragment/Activity, and when a value is emitted it is simply passed onto the Adapter.
But the whole firebase + list creation + etc. shouldn't be mixed with a Fragment's code in my opinion.
the onDataChange method is working asynchronously,, so instead of modifying local variable, you should modify class property.... move out the list into class property, and do changes it inside these methods, and don't forget to notify data changes to the adapter..
you need something like this:
class UserChatFragment : Fragment() {
lateinit var mobileno: String
val listUser: MutableList<User> = mutableListOf() // move it out here..
...
... // TLDR
// somewhere on your code
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val item = User(
snapshot.child("name").value.toString(),
snapshot.child("profilePicture").value.toString(),
snapshot.child("phone").value.toString()
)
listUser.add(item)
adapter.notifyDatasetChanges()
}
}
...
}
Good day
I have a customized spinner and it shows the string array without any issue. But when I add the onItemSelectedListener my application will crash when loading the fragment.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
auth = Firebase.auth
// show back button
val activity = activity as? MainActivity
activity?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
val result = inflater.inflate(R.layout.fragment_new_key, container, false)
val spinner: Spinner = result.findViewById(R.id.spinner_Category)
ArrayAdapter.createFromResource(
requireContext(), R.array.keyCategory, R.layout.spinner_item
).also { adapter ->
spinner.adapter = adapter
}
// without adding the below, the application will work smoothly
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
TODO("Not yet implemented")
}
}
return result
}
Could you please help, is there something conflicting with the spinner that pushed the application to crash?
Thank you.
Remove these lines
TODO("Not yet implemented")
TODO throws NotImplementedError