LiveData Obserever catch only first value - android

I get list of issues from Firebase Database and return LiveData.
fun getAllIssues(): MutableLiveData<IssueEntity> {
val issues: MutableLiveData<IssueEntity> = MutableLiveData()
val issuesReference = FirebaseDatabase.getInstance().reference.child("issues")
issuesReference.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError?) {
}
override fun onDataChange(snapshot: DataSnapshot?) {
if (snapshot == null) {
return
}
snapshot.children.forEach({
val issueMessage = it.child("message").value.toString()
val issueTitle = it.child("title").value.toString()
val issueOwner = it.child("owner").value.toString()
issues.postValue(IssueEntity(issueTitle, issueMessage, issueOwner))
})
}
})
return issues
}
Code of my Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_all_issues)
val issues: MutableList<IssueEntity> = mutableListOf()
issuesList.adapter = IssuesAdapter(issues)
IssueRepository().getAllIssues().observe(this, Observer {
if (it != null) {
issues.add(it)
val issuesAdapter = issuesList.adapter as IssuesAdapter
issuesAdapter.notifyDataSetChanged()
}
})
}
But Observer gets only first value. Could you please tell me why?

Sorry for the confusion. I just need to return not just MutableLiveData and MutableLiveData>

Related

Not able to recover data from firebase before function returns the value

(Android, Kotlin)
I'm trying to recover data from firebase through a repository and It is happening correctly but in the wrong time
override suspend fun getAllOnline(): MutableStateFlow<ResourceState<List<DocModel>>> {
val docList: MutableList<DocModel> = mutableListOf()
auth = FirebaseAuth.getInstance()
database
.child(auth.currentUser!!.uid)
.addValueEventListener(object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for(docs in snapshot.children) {
val doc = docs.getValue(DocModel::class.java)
docList.add(doc!!)
}
}
override fun onCancelled(error: DatabaseError) {
return
}
})
return if(docList.isNullOrEmpty()) {
MutableStateFlow(ResourceState.Empty())
} else {
MutableStateFlow(ResourceState.Success(docList))
}
}
The problem is: my doc list is populated after the return finishes. I've debugged and logged it and the result always come after the function is ended, so it return no data.
It is necessary to somehow only allow the return when the data retrieve is completed.
Any suggestions?
Thanks in advance
You can either use await or if you want the code remain this way, you can also use suspendCoroutine like below:
private suspend fun getFirebaseToken(): String? {
return try {
val suspendCoroutine = suspendCoroutine<Task<String>> { continuation ->
FirebaseMessaging.getInstance().token.addOnCompleteListener {
continuation.resume(it)
}
}
if (suspendCoroutine.isSuccessful && suspendCoroutine.result != null)
suspendCoroutine.result
else null
} catch (e: Exception) {
e logAll TAG
null
}
}
suspendCoroutine<Task<String>> can be replaced by suspendCoroutine<MutableList<DocModel>>
And you will pass docList in "continuation.resume(docList)" instead of "it":
Your final code will look like this:
override suspend fun getAllOnline(): MutableStateFlow<ResourceState<List<DocModel>>> {
auth = FirebaseAuth.getInstance()
val docList = suspendCoroutine<MutableList<DocModel>>{ continuation->
database
.child(auth.currentUser!!.uid)
.addValueEventListener(object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val docList: MutableList<DocModel> = mutableListOf()
for(docs in snapshot.children) {
val doc = docs.getValue(DocModel::class.java)
docList.add(doc!!)
}
continuation.resume(docList)
}
override fun onCancelled(error: DatabaseError) {
continuation.resume(emptyList<DocModel>())
}
})
}
return if(docList.isSuccessful && docList.result != null &&
docList.result.isNullOrEmpty()) {
MutableStateFlow(ResourceState.Success(docList.result))
} else {
MutableStateFlow(ResourceState.Empty())
}
}

Want to return list from repository to viewmodel MVVM

I'm working on a project where i hava to fetch the data from firestore in a repository and then return it to viewmodel and from viewmodel to activity/fragment but the loop return the list before the data getting fetched
my activity code
binding!!.mainspinnercatalog.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
p0: AdapterView<*>?,
p1: View?,
position: Int,
p3: Long
) {
val mainCatalog = p0?.getItemAtPosition(position).toString()
addNamesViewModel.getSubCatalogs(mainCatalog)
.observe(this#ActivityAddCatalogNames, { list ->
val adapter = ArrayAdapter(
this#ActivityAddCatalogNames,
android.R.layout.simple_dropdown_item_1line,
list
)
binding?.subspinnercatalog?.adapter = adapter
})
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
ViewModel code
fun getSubCatalogs(mainCatalog:String): MutableLiveData<ArrayList<String>> {
var subCatalogs = MutableLiveData<ArrayList<String>>()
viewModelScope.launch {
subCatalogs = productDao.getSubCatalogs(mainCatalog)
}
return subCatalogs
}
repository code
fun getSubCatalogs(mainCatalog: String): MutableLiveData<ArrayList<String>> {
subCatalogCollection.whereEqualTo("mainCatalogtName", mainCatalog)
.addSnapshotListener { snapshot, e ->
if (e != null) {
return#addSnapshotListener
}
if (snapshot != null) {
val catalogs = ArrayList<String>()
val documents = snapshot.documents
documents.forEach {
val catalog = it.getString("subCatalogName")
if (catalog != null) {
catalogs.add(catalog)
}
}
_subCatalog.value = catalogs
}
}
return _subCatalog
}
Your getSubCatalogs method works asynchronously and takes times to fetch data make it suspend and add this dependency to work firebase with coroutines:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1'
repository implementation would be :
suspend fun getSubCatalogs(mainCatalog: String): MutableList<String> = withContext(Dispatchers.IO) {
return#withContext try{
val dataSnapshot = subCatalogCollection.whereEqualTo("mainCatalogtName", mainCatalog).await()
if(dataSnapshot.exists()){
val catalogs = mutableListOf<String>()
val documents = dataSnapshot.documents
documents.forEach {
val catalog = it.getString("subCatalogName")
if (catalog != null) {
catalogs.add(catalog)
}
}
catalogs
}else emptyList<String>().toMutableList()
}catch (e: Exception){
emptyList<String>().toMutableList()
}
}
otherwise if you are going with kotlin flow then use Callback Flow

Kotlin. Get two firebase queries in one viewmodel

There are multiple RecyclerView in my application. Each one consists of the same records, but with different filters.
For example, the first RecyclerView contains new records, the second RecyclerView contains the most popular, etc.
I am trying to get "voices" with different filters. But in the end I get 2 identical lists.
My ViewModel:
private var recentlyAddedVoices = MutableLiveData<List<VoicesModel>>()
private val topFreeVoices = MutableLiveData<List<VoicesModel>>()
private val favExists = MutableLiveData<Boolean>()
private val addToFavoriteResult = MutableLiveData<Boolean>()
val homeVoicesData: MutableLiveData<Pair<List<VoicesModel>?, List<VoicesModel>?>> =
object: MediatorLiveData<Pair<List<VoicesModel>?, List<VoicesModel>?>>() {
var voices: List<VoicesModel>? = null
var freeVoices: List<VoicesModel>? = null
init {
addSource(recentlyAddedVoices) { voices ->
this.voices = voices
voices?.let { value = voices to it }
}
addSource(topFreeVoices) { free ->
this.freeVoices = free
freeVoices?.let { value = freeVoices to it }
}
}
}
fun loadRecentlyAddedVoices(){
REF_DATABASE_ROOT.child(NODE_STICKERS).addValueEventListener(object :
ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val tmpList: MutableList<VoicesModel> = mutableListOf()
for (ds in snapshot.children) {
val voices: VoicesModel? = ds.getValue(VoicesModel::class.java)
voices!!.pushKey = ds.key.toString()
tmpList.add(voices)
}
recentlyAddedVoices.postValue(tmpList)
}
override fun onCancelled(error: DatabaseError) {
}
})
}
fun loadTopFree(){
REF_DATABASE_ROOT.child(NODE_STICKERS).
orderByChild(CHILD_IS_FREE).
equalTo(true).
addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val tmpList: MutableList<VoicesModel> = mutableListOf()
for (ds in snapshot.children) {
val voices: VoicesModel? = ds.getValue(VoicesModel::class.java)
voices!!.pushKey = ds.key.toString()
tmpList.add(voices)
}
topFreeVoices.postValue(tmpList)
}
override fun onCancelled(error: DatabaseError) {
}
})
}
Observe in Fragment:
firebaseViewModel.homeVoicesData.observe(this){ (recentlyAdded, topFree) ->
// recentlyAdded and topFree equals identical value
UpdateUI()
}

Key expected String[] but value was a java.lang.String

I am getting issues of getting my USER KEY and it returned as null even if there's a username.
Thing is I am just trying to get my username.
I am currently using firebase database
class NewMessageActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_message)
supportActionBar?.title="Select User"
/*
val adapter = GroupAdapter<ViewHolder>()
adapter.add(UserItem())
adapter.add(UserItem())
adapter.add(UserItem())
new_message_list.adapter = adapter
*/
fetchusers()
}
companion object {
val USER_KEY = "USER_KEY"
}
private fun fetchusers(){
val ref = FirebaseDatabase.getInstance().getReference("/users")
ref.addListenerForSingleValueEvent(object: ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<ViewHolder>()
p0.children.forEach {
Log.d("NewMessage", it.toString())
val user = it.getValue(User::class.java)
if (user != null){
adapter.add(UserItem(user))
}
}
adapter.setOnItemClickListener { item, view ->
val userItem = item as UserItem
val intent = Intent(view.context, ChatLogActivity::class.java)
intent.putExtra(USER_KEY, userItem.user.username)
startActivity(intent)
finish()
}
new_message_list.adapter = adapter
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
}
class UserItem(val user: User): Item<ViewHolder>() {
override fun bind(viewHolder: ViewHolder, position: Int){
//list stuff
viewHolder.itemView.username_textview_new.text = user.username
Picasso.get().load(user.profileImageUrl).into(viewHolder.itemView.imageview_new_msg)
}
override fun getLayout(): Int {
return R.layout.user_row_new_message
}
}
This one really frustrated me for hours. I needed this for my chat log title for each person
Maybe I should skip this?
I am just new to android development
Can anyone help?
error in debug

Room returning Null value while using Live Data but returns Proper value when its not wrapped with Livedata

I am using the following DAO
#Dao
interface GroceryListDao {
#Insert
fun insert(list: GroceryList)
#Update
fun update(list: GroceryList)
#Query("Delete from grocery_list_table")
fun clear()
#Query ("Select * from grocery_list_table")
fun getAllItems(): LiveData<List<GroceryList>>
#Query ("Select * from grocery_list_table where itemId = :item")
fun getItem(item: Long): GroceryList?
#Query ("Select * from grocery_list_table where item_status = :status")
fun getItemsBasedOnStatus(status: Int): List<GroceryList>
}
And my database has 3 columns groceryId(Long - autogenerated), groceryName(String) and groceryStatus(Int - 1/0).
When I am using getItemsBasedOnStatus(status: Int) without using LiveData I am able to retrieve the data. But when it is wrapped with LiveData I am getting null.
The other issue is when I get a list of items from a database without wrapping with LiveData and assigning to MutableLiveData in ViewModel, then the assigned MutableLiveData is displaying null values. I see lot of questions on this but no answer.
Adding code for my viewModel and Fragment
ViewModel
class GroceryListViewModel(
val database: GroceryListDao,
application: Application
) : AndroidViewModel(application) {
private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
var grocerylistItems = database.getAllItems()
var groceryItem = MutableLiveData<GroceryList>()
var groceryitems = MutableLiveData<List<GroceryList>>()
init {
getAllItemsFromDatabase()
}
fun insertIntoDatabase(item: GroceryList) {
uiScope.launch {
insert(item)
}
}
private suspend fun insert(item: GroceryList) {
withContext(Dispatchers.IO) {
database.insert(item)
}
}
fun updateGrocerylist(itemId: Long, status: Int) {
uiScope.launch {
groceryItem.value = getItem(itemId)
groceryItem.value?.itemStatus = status
groceryItem.value?.let { update(it) }
}
}
private suspend fun update(item: GroceryList) {
withContext(Dispatchers.IO) {
database.update(item)
}
}
private suspend fun getItem(itemId: Long): GroceryList? {
return withContext(Dispatchers.IO) {
var item = database.getItem(itemId)
item
}
}
fun getAllItemsFromDatabase() {
uiScope.launch {
getAllItems()
}
}
private suspend fun getAllItems() {
withContext(Dispatchers.IO) {
grocerylistItems = database.getAllItems()
}
}
fun getPendingItemsFromDatabase(status: Int) {
uiScope.launch {
getPendingItems(status)
}
}
private suspend fun getPendingItems(status: Int) {
withContext(Dispatchers.IO) {
val items = database.getItemsBasedOnStatus(status)
groceryitems.postValue(items)
Log.i("Grocery List", "Pending Items:" + items.size)
}
}
fun getDoneItemsFromDatabase(status: Int) {
uiScope.launch {
getDoneItems(status)
}
}
private suspend fun getDoneItems(status: Int) {
withContext(Dispatchers.IO) {
val items = database.getItemsBasedOnStatus(status)
groceryitems.postValue(items)
Log.i("Grocery List", "Done Items:" + items.size)
}
}
fun clearAllItemsFromDatabase() {
uiScope.launch {
clearItems()
}
}
private suspend fun clearItems() {
withContext(Dispatchers.IO) {
database.clear()
getAllItemsFromDatabase()
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
Fragment
class GroceryLIstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val binding = FragmentGroceryLIstBinding.inflate(inflater,container,false)
val application = requireNotNull(this.activity).application
val dataSource = GroceryDatabase.getInstance(application)?.groceryListDatabaseDao
val viewModelFactory = dataSource?.let { GroceryListViewModelFactory(it, application) }
val viewModel = viewModelFactory?.let {
ViewModelProvider(
this,
it
).get(GroceryListViewModel::class.java)
}
viewModel?.grocerylistItems?.observe(this , Observer {
binding.grocerylistView.removeAllViews() // is it correct ?
for (item in it){
Log.i("Grocery List","Grocery Id=" + item.itemId+" ,Grocery Name=" + item.itemName +", Grocery status="+item.itemStatus)
addItemToScrollbar(item, binding, viewModel)
}
})
binding.addGrocery.setOnClickListener {
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view?.windowToken, 0)
val item = binding.groceryitemField.text.toString()
if (!item.isNullOrBlank()) {
val newItem = GroceryList(itemName = item)
viewModel?.insertIntoDatabase(newItem)
if (viewModel != null) {
addItemToScrollbar(newItem, binding,viewModel)
}
binding.groceryitemField.text.clear()
}
}
binding.doneCheckbox.setOnClickListener {
if (binding.doneCheckbox.isChecked)
viewModel?.getDoneItemsFromDatabase(1)
else
viewModel?.getAllItemsFromDatabase()
}
binding.pendingCheckbox.setOnClickListener {
if (binding.pendingCheckbox.isChecked) {
viewModel?.getPendingItemsFromDatabase(0)
}
else
viewModel?.getAllItemsFromDatabase()
}
binding.clearGrocery.setOnClickListener {
viewModel?.clearAllItemsFromDatabase()
binding.grocerylistView.removeAllViews()
}
return binding.root
}
private fun addItemToScrollbar(
item: GroceryList,
binding: FragmentGroceryLIstBinding,
viewModel: GroceryListViewModel
) {
val itemBox = AppCompatCheckBox(context)
itemBox.text = item.itemName
itemBox.isChecked = item.itemStatus == 1
binding.grocerylistView.addView(itemBox)
itemBox.setOnClickListener {
val itemstatus: Int = if (itemBox.isChecked)
1
else {
0
}
viewModel?.updateGrocerylist(item.itemId,itemstatus)
}
}
}
Any help would be appreciated.
This most likely the same issue as here (read the linked answer). Due to way asynchronous way LiveData is working, it will return null when you call it. LiveData is meant to be used in conjunction with Observers, that will be triggered once changes to observed subject occur.
An Observer can look like this
database.getItemsBasedOnStatus(status).observe(viewLifecycleOwner, Observer { groceryList->
// Do cool grocery stuff here
})
If you want to retrieve your data inside your ViewModel you do not have a viewLifecycleOwner, you can then use "observeForever()", but then you have to remove the Observer explicitly, see this answer.
Same issue and answer also in this post

Categories

Resources