Array list in kotlin not storing data after initialization - android

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()
}
}
...
}

Related

How can I display firebase data in a fragment with recyclerview? Kotlin Android Studio

hello I have an activity that has a navigation bar and fragments inside, in one of my fragments I save data in firebase, I want to get those data and display it in other fragment with recycler view. I watched a tutorial on youtube to do it cuz my teacher didn't teach us anything, the issue is the fragment that was supposed the display the data shows nothing, it's blank but I didn't understand why cuz I'm really new to this, if someone can take a look at my code and help me I would really appreciate it
This is my data class
data class Event(var eventName: String, var eventTime:String?=null)
This is ViewModel
class EventViewModel :ViewModel() {
private val repository : EventRepository
private val _allEvents = MutableLiveData<List<Event>>()
val allEvents : LiveData<List<Event>> = _allEvents
init {
repository= EventRepository().getInstance()
repository.loadEvents(_allEvents)
}
}
This is Repository
class EventRepository {
private val databaseReference: DatabaseReference= FirebaseDatabase.getInstance().getReference("PetEvents")
#Volatile private var INSTANCE : EventRepository ?=null
fun getInstance() : EventRepository{
return INSTANCE ?: synchronized(this) {
val instance = EventRepository()
INSTANCE=instance
instance
}
}
fun loadEvents(eventList : MutableLiveData<List<Event>>){
databaseReference.addValueEventListener(object:ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
try {
val _eventList : List<Event> = snapshot.children.map { dataSnapshot ->
dataSnapshot.getValue(Event::class.java)!!
}
eventList.postValue(_eventList)
}
catch (e: Exception){
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
This is Adapter
class EventAdapter : RecyclerView.Adapter<EventAdapter.MyViewHolder>() {
private var eventList =ArrayList<Event>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(
R.layout.event_item_cell,
parent, false
)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = eventList[position]
holder.eventName.text =currentItem.eventName
holder.eventTime.text =currentItem.eventTime
}
override fun getItemCount(): Int {
return eventList.size
}
fun updateEventList(eventList: List<Event>){
this.eventList.clear()
this.eventList.addAll(eventList)
notifyDataSetChanged()
}
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val eventName :TextView = itemView.findViewById(R.id.txEventName)
val eventTime :TextView = itemView.findViewById(R.id.txEventTime)
}
}
And this is the fragment I wish to display the data
private lateinit var viewModel : EventViewModel
private lateinit var eventRecyclerView: RecyclerView
lateinit var adapter: EventAdapter
class MainFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
eventRecyclerView=view.findViewById(R.id.recyclerView)
eventRecyclerView.layoutManager= LinearLayoutManager(context)
eventRecyclerView.setHasFixedSize(true)
adapter = EventAdapter()
eventRecyclerView.adapter= adapter
viewModel = ViewModelProvider(this).get(EventViewModel::class.java)
viewModel.allEvents.observe(viewLifecycleOwner, Observer {
adapter.updateEventList(it)
})
}
}
In my fragment xml I put a recycler view, there's no issue about that
And my item cell is a cardview idk if it has anything to with the problem I'm getting tho

I can't get the value userCount from the onDataChange method in ValueEventListener so I can use it in my getItemCount method inside my Adapter Kotlin

this is my Adapter class
class Adapter: RecyclerView.Adapter<Adapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Adapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.new_message_user, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: Adapter.ViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return 5
}
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
var userImage: ImageView
var userName: TextView
init {
userImage = itemView.findViewById(R.id.circle_image_view)
userName = itemView.findViewById(R.id.user_name_database)
itemView.setOnClickListener {
val position: Int = adapterPosition
}
}
}
}
and in the same file, I have this function fetchUsers() which will get me the number of my app users from firebase, it's getting the job done getting the right number of users in the Log
fun fetchUsers() {
val ref = FirebaseDatabase.getInstance().getReference("/users")
ref.addListenerForSingleValueEvent(object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val countUser = snapshot.childrenCount.toInt()
Log.d("countUsers", "FirebaseUsers: ${countUser.toString()}")
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
now I know that I can't use userCount outside onDataChange method,
so is there any way to get userCount so I can use it in my Adapter inside getItemCount,
I tried using callback but didn't get it right, I tried declaring countUser as a variable globally and set it to 0 as so
var countUsers: Int = 0
so when I run the app the first time I enter the activity it still shows 0, but if I go back to the previous activity and then enter it again it shows the right number of users.

Can't load firebase details to recycler view ? Kotlin

I need to get details from the Firebase Realtime Database to RecyclerView which is in a fragment. I refer to many tutorials and finally code this. I got this error:
Here's my fragment code.
class busFragment : Fragment() {
private lateinit var dbref: DatabaseReference
private lateinit var routeRecyclerView: RecyclerView
private lateinit var routesArrayList: ArrayList<BusRoutes>
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_bus, container, false)
}
#RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
routeRecyclerView = view.findViewById(R.id.bus_routes)
routeRecyclerView.layoutManager = LinearLayoutManager(activity)
routeRecyclerView.setHasFixedSize(true)
routesArrayList = arrayListOf<BusRoutes>()
getRouteDetails()
}
private fun getRouteDetails() {
dbref = FirebaseDatabase.getInstance().getReference("BusRoutes")
dbref.addValueEventListener(object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()){
for (routeSnapshot in snapshot.children){
val route = routeSnapshot.getValue(BusRoutes::class.java)
routesArrayList.add(route!!)
}
routeRecyclerView.adapter = Adapter(routesArrayList)
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
companion object {
}
}
here's my adapter.kt
class Adapter(private val routeslist: ArrayList<BusRoutes>) : RecyclerView.Adapter<Adapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.items_busroutes,
parent,false)
return MyViewHolder(itemView as ViewGroup)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = routeslist[position]
holder.route.text = currentItem.routeNo
holder.start.text = currentItem.start
holder.end.text = currentItem.end
}
override fun getItemCount(): Int {
return routeslist.size
}
class MyViewHolder(itemView:ViewGroup) : RecyclerView.ViewHolder(itemView){
val route : TextView = itemView.findViewById(R.id.routeNo)
val start : TextView = itemView.findViewById(R.id.startPlace)
val end : TextView = itemView.findViewById(R.id.endPlace)
}
}
Here's my data class.
Here's my firebase details.
Here's the line 52 val route = routeSnapshot.getValue(BusRoutes::class.java)
I try to fix this many times but still cannot find it. Help me. I'm still Learning Kotlin.
updated
BusRoutes class:
data class BusRoutes(val routeNo: String? = null, val start: String? = null, val end: String? = null)
You are getting the following error:
failed to convert value of type java.lang.long to string
Because the routeNo field in your database is of type number, while in your class is of type String and this is not correct. Both types must match because there is no way in Kotlin in which you can convert an object type Long to String, hence that error.
The simplest solution would to change the declaration fo your BusRoutes class like so:
data class BusRoutes(val routeNo: Long? = null, val start: String? = null, val end: String? = null)
// ^^

Delete item from recyclerview on button click - Kotlin MVVM Firestore

I'm having trouble with deleting data from my Firestore collection when the user clicks a delete button in a recyclerview. I can delete it from the recyclerview without any problems, but I'm having trouble to make the connection between the adapter, the viewmodel and the repository that handles Firestore operations.
In my adapter, I remove the item the user clicked on from the recyclerview:
class ArticleAdapter : RecyclerView.Adapter<ArticleAdapter.ViewHolder>() {
var data = mutableListOf<Product>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
holder.bind(item)
holder.deleteButton.setOnClickListener {
data.removeAt(position)
notifyDataSetChanged()
}
} ...
The recyclerview is populated after a query to the Firestore collection in my viewmodel:
class ArticleViewModel(private val repository: ProductRepository) : ViewModel() {
var savedProducts: MutableLiveData<MutableList<Product>> = MutableLiveData<MutableList<Product>>()
init {
savedProducts = getProducts()
}
fun getProducts(): MutableLiveData<MutableList<Product>> {
repository.getProducts().addSnapshotListener(EventListener<QuerySnapshot> { value, e ->
if (e != null) {
savedProducts.value = null
return#EventListener
}
val savedProductsList: MutableList<Product> = mutableListOf()
for (doc in value!!) {
val item = doc.toObject(Product::class.java)
item.id = doc.id
savedProductsList.add(item)
}
savedProductsList.sortBy { i -> i.productName }
savedProducts.value = savedProductsList
})
return savedProducts
} }
In my Fragment, I'm then observing any changes that might happen to savedProducts:
class ArticleOverviewFragment : Fragment(), KodeinAware {
override val kodein: Kodein by kodein()
private val factory: ArticleViewModelFactory by instance()
private lateinit var viewModel: ArticleViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentArticleOverviewBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_article_overview, container, false)
viewModel = ViewModelProviders.of(this, factory).get(ArticleViewModel::class.java)
binding.viewModel = viewModel
val adapter = ArticleAdapter()
binding.recyclerViewGoods.adapter = adapter
viewModel.savedProducts.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
...
} }
Is there a way that I can observe/save the ID of the deleted item in my adapter and "transfer" that ID from the adapter to the UI where I call a function declared in the viewmodel whenever that field holding the ID is populated? Or should I directly access the viewmodel from the adapter? Somehow, that feels kinda wrong...
Declare one local variable
var removedPosition : Int ? = null
then update this variable into onClick event of deleteButton
holder.deleteButton.setOnClickListener {
data.removeAt(position)
removedPosition = position
notifyDataSetChanged()
}
Please make one method in Adapter (ArticleAdapter)
fun getRemoveItemPosition() : Int {
var position = removedPosition
return position;
}
which return the position of removed Item and call that method in UI(ArticleOverviewFragment) where you will require to get position of removed item from recyclerview
var removedItemPosition = adapter.getRemoveItemPosition()
Now you will get value of remove item Position using variable called removedItemPosition
So You can get Position of removed Item in UI where you can call a function declared in the viewmodel (ArticleViewModel) to delete particular item in firestore collection.

LiveData Observer for realmresults not getting triggered first time

I am going through Guide to app architecture and trying to implement MVVM and LiveData in one of my apps. I am using realm and I am using this to create a RealmLiveData as shown below
class RealmLiveData<T : RealmModel>(private val results: RealmResults<T>) : MutableLiveData<RealmResults<T>>() {
private val listener = RealmChangeListener<RealmResults<T>> { results -> value = results }
override fun onActive() {
results.addChangeListener(listener)
}
override fun onInactive() {
results.removeChangeListener(listener)
}
}
This how I am updating the list to recyclerview
var mList:ArrayList<Notes> = ArrayList()
lateinit var historyViewModel: HistoryViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_history, container, false)
mRCview = view.findViewById(R.id.list)
historyViewModel = ViewModelProviders.of(activity!!).get(HistoryViewModel::class.java)
// this is how I observe
historyViewModel.getList().observe(this, Observer{
(mRCview.adapter as MyHistoryRecyclerViewAdapter).setData(it)
})
with(mRCview) {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(mContext)
mList = ArrayList()
adapter = MyHistoryRecyclerViewAdapter(
mContext as OnListFragmentInteractionListener
)
}
return view
}
This is how I get the data in my repository class
class HistoryRepository {
fun getHistory(): RealmLiveData<Notes> {
val realmInstance = Realm.getDefaultInstance()
val realmResults = realmInstance
.where(Notes::class.java)
.findAll()
.sort("lastUpdatedTimeStamp", Sort.DESCENDING)
return realmResults.asLiveData()
}
fun <T:RealmModel> RealmResults<T>.asLiveData() = RealmLiveData(this)
}
EDIT
Here is the ViewModel
class HistoryViewModel: ViewModel() {
val repository = HistoryRepository()
fun getList(): RealmLiveData<Notes> {
return repository.getHistory()
}
}
The issue is that the observer is not getting triggered for the first time. If I update the realmresult, the live data update gets invoked and updates my list. Please let me know how I can fix the issue.
We need to notify the Observer of the existing data. When the first Observer registers to historyViewModel.getList() you are registering the realm callback. At this point we need to trigger a change just to notify this Observer of the existing data.
Something like
class RealmLiveData<T : RealmModel>(private val results: RealmResults<T>) : MutableLiveData<RealmResults<T>>() {
private val listener = RealmChangeListener<RealmResults<T>> { results -> value = results }
override fun onActive() {
results.addChangeListener(listener)
listener.onChange(results) // notify the added Observer of the existing data.
}
override fun onInactive() {
results.removeChangeListener(listener)
}
}

Categories

Resources