I wanted to make button visibe only for users with uid from Admins node, but somehow it doesn't work. That's the function for this(uid value is setting earlier and it is the uid of current user):
private fun checkAdmin() {
val ref = FirebaseDatabase.getInstance().getReference("/admins/")
ref.addListenerForSingleValueEvent(object: ValueEventListener {
override fun onCancelled(p0: DatabaseError) { }
override fun onDataChange(p0: DataSnapshot) {
if (p0.exists()) {
if (uid == p0.value.toString()) {
createNewButton.visibility = View.VISIBLE
} else {
createNewButton.isEnabled = false
createNewButton.visibility = View.INVISIBLE
}
}
}
})
}
There is the part from JSON file:
"admins" : [ "rTXdtJsE7qPZRpWnwTGBAX7dIxx1","4kwOjCjkKvazfoMcZygfsn1byB72" ]
A quick solution for your problem might be the following code:
val uid = FirebaseAuth.getInstance().currentUser!!.uid
val rootRef = FirebaseDatabase.getInstance().reference
val adminsRef = rootRef.child("admins")
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (ds in dataSnapshot.children) {
val value = ds.getValue(String::class.java)
if(value.equals(uid)) {
createNewButton.visibility = View.VISIBLE
} else {
createNewButton.isEnabled = false
createNewButton.visibility = View.INVISIBLE
}
}
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("TAG", databaseError.getMessage()) //Don't ignore errors!
}
}
adminsRef.addListenerForSingleValueEvent(valueEventListener)
To get those values, you need to loop through the DataSnapshot object.
Related
(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())
}
}
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()
}
I'm retrieving a node from Database that looks like this:
DataSnapshot { key = appointment, value = {timeAppointment=12:00pm, dateAppointment=12/12/1221, clientUID=Bu4sw8ouUUhDv0Ut1IKqeh8kESg2, caseManagerName=Karla Moreno, userName=Sakura Nakamura, formatAppointment=Zoom} }
This DataSnapshot is being stored in a custom callback.
However, I'm only able to retrieve it as a DataSnapshot. I'd like to retrieve the values, and display them using a TextView.
Is there a way to convert it into a HashMap, other than hardcoding the values into a HashMap?
Here is my code:
class ViewAppointments : AppCompatActivity() {
var readAppointment: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view_appointments)
readData(object : AppointmentCallback {
override fun onCallback(value: DataSnapshot) {
//Here readAppointment is of type DataSnapshot
readAppointment = value.toString()
Log.d("ZXC", "$readAppointment")
}
})
displayData()
}
private fun displayData() {
// returns null
Log.d("ZXC", "Display appointment from Database: $readAppointment")
}
private fun readData(appointmentCallback: AppointmentCallback) {
val uid = FirebaseAuth.getInstance().currentUser!!.uid
val rootRef = FirebaseDatabase.getInstance().reference
val uidRef = rootRef.child("users").child(uid)
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (item in dataSnapshot.children) {
if (item.key.toString() == "appointment") {
//item = DataSnapshot { key = appointment, value = {timeAppointment=12:00pm, dateAppointment=12/12/1221,
// clientUID=Bu4sw8ouUUhDv0Ut1IKqeh8kESg2, caseManagerName=Karla Moreno, userName=Sakura Nakamura,
// formatAppointment=Zoom} }
Log.d("ZXC", "$item")
appointmentCallback.onCallback(item)
}
}
}
override fun onCancelled(databaseError: DatabaseError) {
}
}
uidRef.addListenerForSingleValueEvent(valueEventListener)
}
interface AppointmentCallback {
fun onCallback(value: DataSnapshot)
}
}
To get the map of values in a snapshot, call its getValue() method. This might actually be mapped to a value property in Kotlin.
How i can optimize my code?
In every function i created valueEventListener.
Here is all code:
class TargetsPresenter(private val contract: SelectTargetViewContract) {
var firebaseUser: FirebaseUser? = null
var targetList: ArrayList<Goal> = ArrayList()
private var databaseReference: DatabaseReference? = null
private var targetsRef: DatabaseReference? = null
private var uid: String? = null
fun setInitialData() {
firebaseUser = FirebaseAuth.getInstance().currentUser
databaseReference = FirebaseDatabase.getInstance().reference
uid = firebaseUser?.uid
targetsRef = databaseReference?.child("targets")
?.child("users")?.child(uid.toString())
?.child("targets")
}
fun getTargetsFromDb() {
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
targetList.clear()
dataSnapshot.children
.mapNotNull { it.getValue(Goal::class.java) }
.toCollection(targetList)
contract.updateViewContent()
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("some", "Error trying to get targets for ${databaseError.message}")
}
}
targetsRef?.addListenerForSingleValueEvent(valueEventListener)
}
fun getTargetsByPriority() {
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
targetList.clear()
dataSnapshot.children
.mapNotNull { it.getValue(Goal::class.java) }
.sortedBy { it.priority }
.toCollection(targetList)
contract.updateViewContent()
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("some", "Error trying to get targets for ${databaseError.message}")
}
}
targetsRef?.addListenerForSingleValueEvent(valueEventListener)
}
fun getTargetsByDeadline() {
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
targetList.clear()
dataSnapshot.children
.mapNotNull { it.getValue(Goal::class.java) }
.sortedBy { it.deadline }
.toCollection(targetList)
contract.updateViewContent()
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("some", "Error trying to get targets for ${databaseError.message}")
}
}
targetsRef?.addListenerForSingleValueEvent(valueEventListener)
}
}
Optimization is the wrong word to describe the issue. The issue is repeating identical code (violating the DRY principle), which can be a problem because it invites error if you need to change something, and it's less readable.
In this case, it's not extreme, but I guess it could be improved somewhat. You can declare a class implementation of the listener that takes a parameter for how to sort the list.
class TargetsPresenter(private val contract: SelectTargetViewContract) {
//...
fun getTargetsFromDb() {
targetsRef?.addListenerForSingleValueEvent(MyValueEventListener<String>())
}
fun getTargetsByPriority() {
targetsRef?.addListenerForSingleValueEvent(MyValueEventListener(Goal::priority))
}
fun getTargetsByDeadline() {
targetsRef?.addListenerForSingleValueEvent(MyValueEventListener(Goal::deadline))
}
private inner class MyValueEventListener<R: Comparable<R>>(
private val sortCriteria: (Goal) -> R? = { null }
) : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
targetList.clear()
dataSnapshot.children
.mapNotNull { it.getValue(Goal::class.java) }
.sortedBy(sortCriteria)
.toCollection(targetList)
contract.updateViewContent()
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("some", "Error trying to get targets for ${databaseError.message}")
}
}
}
I add targets to the database this way:
private fun addTarget(name: String, description: String) {
if (!TextUtils.isEmpty(name)) {
val target = Target(guid = "some", name = name, description = description)
databaseReference?.child("users")
?.child(mUserId.toString())?.child("targets")?.push()?.setValue(target)
} else Log.d("some", "Enter a name")
}
And get the following structure in my firebase database:
Next, I try to display my list of targets in TargetsFragment
In onViewCreated i call next functions:
private fun updateListData() {
databaseReference = FirebaseDatabase.getInstance().getReference()
getTargetsFromDb()
}
private fun getTargetsFromDb() {
databaseReference?.child("users")?.child(mUserId.toString())?.
child("targets")?.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (targetSnapshot in dataSnapshot.children) {
val target = targetSnapshot.getValue(Target::class.java)
target?.let { targetList.add(it) }
}
recyclerView?.adapter = adapter
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("some", "Error trying to get targets for ${databaseError.message}")
}
})
}
As I said, because I cannot see what changes do you make, I wrote the code that can help you get the data from the database:
val uid = FirebaseAuth.getInstance().currentUser!!.uid
val rootRef = FirebaseDatabase.getInstance().reference
val targetsRef = rootRef!!.child("targets").child("users").child(uid).child("targets")
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (ds in dataSnapshot.children) {
val target = ds.getValue(Target::class.java)
targetList.add(target)
}
adapter.notifyDataSetChanged()
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d(TAG, databaseError.getMessage()) //Don't ignore errors!
}
}
targetsRef.addListenerForSingleValueEvent(valueEventListener)
The output in the logcat will be:
uuuuu
yyyyy
Even if you are using two nodes with the same name targets, both should be mentioned in the reference.