I am a beginner and currently working on an android app in Kotlin that gets data from TMDb
API and uses Firebase Realtime Database for storing and retrieving data.
I use Kotlin Coroutines to get data from TMDb API but I am not sure if I should use coroutines for storing or retrieving data from Firebase Realtime Database.
I am hoping that Firebase automatically does the work done by Kotlin coroutines.
Here is one of the Firebase operations that I want to perform : (retrieve object from database)
firebaseDatabase = Firebase.database
dbReference = firebaseDatabase.getReference("users/$uid")
val dbListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// Get Post object and use the values to update the UI
val fobject = dataSnapshot.getValue<TvFirebase>()
Log.v("utk", "tv show name is " + fobject!!.name)
// ...
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w("utk", "onCancelled", databaseError.toException())
// ...
}
}
val tvReference = firebaseDatabase.getReference("users/$uid/tv/236")
tvReference.addValueEventListener(dbListener)
So do I need to use Kotlin Coroutines with Firebase Realtime Database?
I'm adding some new features in my old project using both "Realtime database" and "Firestore".
Since firestore supports "await" suspend function on Coroutines I searched the same for Realtime database. Coroutines are great if you don't mix suspend and callback function..
End up to do it myself. I didn't try yet the code below but it should work.
Note: Some features are still marked as ExperimentalCoroutinesApi since 1.2.0, so be careful if it changes in futur releases.
sealed class RealtimeDatabaseValueResult {
class Success(val dataSnapshot: DataSnapshot): RealtimeDatabaseValueResult()
class Error(val error: DatabaseError): RealtimeDatabaseValueResult()
}
/**
* Perform a addListenerForSingleValueEvent call on a databaseReference in a suspend function way
* #param onCancellation action to perform if there is a cancellation
*/
#ExperimentalCoroutinesApi
suspend fun DatabaseReference.awaitSingleValue(onCancellation: ((cause: Throwable) -> Unit)? = null) = suspendCancellableCoroutine<RealtimeDatabaseValueResult> { continuation ->
val valueEventListener = object: ValueEventListener{
override fun onCancelled(error: DatabaseError) {
continuation.resume(RealtimeDatabaseValueResult.Error(error = error), onCancellation)
}
override fun onDataChange(snapshot: DataSnapshot) {
continuation.resume(RealtimeDatabaseValueResult.Success(snapshot), onCancellation)
}
}
// add listener like you normally do
addListenerForSingleValueEvent(valueEventListener)
// in case the job, coroutine, etc. is cancelled, we remove the current event listener
continuation.invokeOnCancellation { removeEventListener(valueEventListener) }
}
Usage :
fun fetchUser(firebaseDatabase: FirebaseDatabase, userId: String){
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate).launch{
when(val result = firebaseDatabase.getReference("users").child(userId).awaitSingleValue()){
is RealtimeDatabaseValueResult.Success -> {
val dataSnapshot: DataSnapshot = result.dataSnapshot
// proceed action with dataSnapshot
}
is RealtimeDatabaseValueResult.Error -> {
val error: DatabaseError = result.error
// proceed action with error
}
}
}
}
It's not required to use coroutines with any Firebase API, but that can certainly make your development easier, if you do it correctly. However, coroutines are not compatible with Firebase listeners that report changed values over time. For those, you might want to use a LiveData or Flow.
Just add a minor change to your code.
From this...
firebaseDatabase = Firebase.database
dbReference = firebaseDatabase.getReference("users/$uid")
val dbListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// Get Post object and use the values to update the UI
val fobject = dataSnapshot.getValue<TvFirebase>()
Log.v("utk", "tv show name is " + fobject!!.name)
// ...
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w("utk", "onCancelled", databaseError.toException())
// ...
}
}
val tvReference = firebaseDatabase.getReference("users/$uid/tv/236")
tvReference.addValueEventListener(dbListener)
To this...
val firebaseDatabase = FirebaseDatabase.getInstance().getReference("users/$uid")
firebaseDatabse.addChildEventListener(object: ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
Log.d("MainFragment", "Children $snapshot")
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {}
override fun onChildRemoved(snapshot: DataSnapshot) {}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {}
override fun onCancelled(error: DatabaseError) {}
})
Dont forget to check this if its correct man ;)
You can use callBackFlow API to remove all of firebase callbacks.
Simple ex:
override suspend fun getSomething(): Flow<Long> = callbackFlow {
val valueListener = object: ValueEventListener{
override fun onCancelled(error: DatabaseError) {
close()
}
override fun onDataChange(snapshot: DataSnapshot) {
offer(snapshot.childrenCount)
}
}
currentUserId?.let { userId ->
firebaseRef?.child(userId)?.addValueEventListener(valueListener)
?: offer(0) }
awaitClose {
currentUserId?.let { userId ->
firebaseRef?.child(userId)?.removeEventListener(valueListener)
}
}
}
Related
in my case when login to the application
need to get user profile from realTime Database by UID
in docs must be using addValueEventListener to read from RealTime DB
//how can get value direct from real-time firebase if I have id for obj
fun loginByEmail(email: String, password: String) {
firebaseAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener { task ->
if (task.isSuccessful) {
getUserValueFromDB(firebaseAuth.currentUser!!.uid, email)
}
}
}
//My problem is here is not access to this method
private fun getUserValueFromDB(uid: String, email: String) { //todo
databaseReference.child("Users").addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (snap in snapshot.children) {
var data = snap.getValue(User::class.java)
if (data?.id == uid) {
prefs.userImage = data.image!!
GlobalScope.launch {
_loginStatus.emit(true)
}
}
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
If you want to read the data from one specific user from your database, you can do so by using a query.
private fun getUserValueFromDB(uid: String, email: String) {
var query = databaseReference.child("Users").orderByChild("id").equalTo(uid)
query.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
...
The rest of your code can stay the same, you'll just be loading a lot less data.
I'm just trying to find an answer how to pass the data from Repository to ViewModel without extra dependencies like RxJava. The LiveData seems as a not good solution here because I don't need to proceed it in my Presentation, only in ViewModel and it's not a good practice to use observeForever.
The code is simple: I use Firebase example trying to pass data with Flow but can't use it within a listener (Suspension functions can be called only within coroutine body error):
Repository
fun fetchFirebaseFlow(): Flow<List<MyData>?> = flow {
var ret: List<MyData>? = null
firebaseDb.child("data").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val data = dataSnapshot.getValue<List<MyData>>()
emit(data) // Error. How to return the data here?
}
override fun onCancelled(databaseError: DatabaseError) {
emit(databaseError) // Error. How to return the data here?
}
})
// emit(ret) // Useless here
}
ViewModel
private suspend fun fetchFirebase() {
repo.fetchFirebaseFlow().collect { data ->
if (!data.isNullOrEmpty()) {
// Add data to something
} else {
// Something else
}
}
You can use callbackFlow
#ExperimentalCoroutinesApi
fun fetchFirebaseFlow(): Flow<List<String>?> = callbackFlow {
val listener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val data = dataSnapshot.getValue<List<MyData>>()
offer(data)
}
override fun onCancelled(databaseError: DatabaseError) {
}
}
val ref =firebaseDb.child("data")
reef.addListenerForSingleValueEvent(listener)
awaitClose{
//remove listener here
ref.removeEventListener(listener)
}
}
ObservableField is like LiveData but not lifecycle-aware and may be used instead of creating an Observable object.
{
val data = repo.getObservable()
val cb = object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(observable: Observable, i: Int) {
observable.removeOnPropertyChangedCallback(this)
val neededData = (observable as ObservableField<*>).get()
}
}
data.addOnPropertyChangedCallback(cb)
}
fun getObservable(): ObservableField<List<MyData>> {
val ret = ObservableField<List<MyData>>()
firebaseDb.child("events").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
ret.set(dataSnapshot.getValue<List<MyData>>())
}
override fun onCancelled(databaseError: DatabaseError) {
ret.set(null)
}
})
return ret
}
It is also possible to use suspendCancellableCoroutine for a single result. Thanks to Kotlin forum.
I want to implement firebase realtime database with coroutines, so I need to use flow because firebase just accept callbacks. the problem is the .collect{} block never gets executed
here is my code
#ExperimentalCoroutinesApi
override suspend fun getProduct(barcode: String): ProductItem? {
return withContext(Dispatchers.Default) {
println("Hi")
var item: ProductItem? = null
productFlow(barcode).collect {
//this never gets called
print("Getting product")
item = it
}
println("Ending product request ${item?.name}")
Log.i("GetProduct",item?.name)
item
}
}
#ExperimentalCoroutinesApi
private fun productFlow(barcode: String): Flow<ProductItem?> = callbackFlow {
val database = FirebaseDatabase.getInstance()
val productRef = database.getReference("products/$barcode")
val callback = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for(snapshot in dataSnapshot.children){
Log.i("Source", snapshot.value.toString())
}
val product = dataSnapshot.getValue(ProductItem::class.java)
Log.i("Source",product?.name) //everything is good until here
sendBlocking(dataSnapshot.getValue(ProductItem::class.java)) //after this i dont get anything on the collect{} block
}
override fun onCancelled(databaseError: DatabaseError) {
println("cancelling")
sendBlocking(null)
}
}
try {
productRef.addListenerForSingleValueEvent(callback)
} catch (e: FirebaseException) {
println("Firebase exception")
sendBlocking(null)
}
awaitClose{
println("Closing")
productRef.removeEventListener(callback)
}
}
First I would suggest to use the catch method to check if there is an error or not. Second, for callbackflow I remember using offer() instead of sendBlocking
Can someone help me fix it?
Following code works without any error, however, it does not retrieve data from Firebase and show in the TextView.
private fun viewData() {
val postReference = FirebaseDatabase.getInstance().getReference("dataID")
val dbView= findViewById<TextView>(R.id.txtFdbData)
val postListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val post = dataSnapshot.getValue(Post::class.java)
dbView.text=post?.postName
}
override fun onCancelled(databaseError: DatabaseError) {
}
}
postReference.addValueEventListener(postListener)
Toast.makeText(this,"Retrieved",Toast.LENGTH_LONG).show()
}
Above code is called when I tap the button 'btnView'
viewButton = findViewById(R.id.btnView)
viewButton.setOnClickListener {
viewData()
}
When I hit the button it shows the toast message 'Retrieved' and the default value given in the TextView (txtFdbData) is deleted (or may be replaced with an empty string?, I do not know).
Following is the post Class
data class Post (
val postName: String="",
val postDescription:String="")
I am working on Android Studio, using Kotlin and Firebase Realtime Database.
You query to database return list of items. So loop through it and try to get Post. Check below:
override fun onDataChange(dataSnapshot: DataSnapshot) {
dataSnapshot.children.forEach {childSnapshot ->
val post = childSnapshot.getValue(Post::class.java)
dbView.text=post?.postName
}
}
getReference("dataID") is not your data node it is your parent node.
Then you have to access their children using getChildren() method.
Change you on data change method with this.
override fun onDataChange(dataSnapshot: DataSnapshot) {
for (postSnapshot : dataSnapshot.getChildren()) {
val post = postSnapshot .getValue(Post::class.java)
dbView.text=post?.postName
}
}
I'm building an activity in an app where users can upload a post, and other users in the app can view the post.
I built a function called loadPost()
This function will take what's there in the database and load it in my View.
Unfortunately, I'm facing an error with the
.addValueEventListener(ValueEventListener{
It's telling me that the interface needs a constructor.
here's the function:
//*******************************************************
//Load posts to the screen from the database
//****************************************************
fun loadPost(){
myRef.child("posts")
.addValueEventListener(ValueEventListener{
override fun onDataChange(#NonNull dataSnapshot: DataSnapshot?) {
try {
postElements.clear()
postElements.add(Post("0","him","url","add"))
postElements.add(Post("0","him","url","ads"))
//Hashmap : Key and value (represents the post and node)
var td= dataSnapshot!!.value as HashMap<String,Any>
for(key in td.keys){
var post= td[key] as HashMap<String,Any>
postElements.add(Post(key,
post["text"] as String,
post["postImage"] as String
,post["userUID"] as String))
}
adpater!!.notifyDataSetChanged() //notify when there's an update in the adapter
}catch (ex:Exception){}
}
override fun onCancelled(p0: DatabaseError?) {
}
})
}
this is how the constructor of a ValueEventListener should look alike:
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
}
}
which means, that you have to replace this one line
.addValueEventListener(ValueEventListener {
with:
.addValueEventListener(object : ValueEventListener {
see https://youtrack.jetbrains.com/issue/KT-7770 (concerning the code-converter)