I have a map containing the keys of my Firebase Realtime Database and want to retrieve the corresponding key data and put it in the result data list. How can I execute the loop sequentially? Basically, block the Firebase listener until it gets the result and only then iterate to the next key in the loop.
fun functionA() {
val resultFileDataList = List<DataSnapshot>()
for ((key, value) in filesMap) {
val dbRef = database.child("files").child(key)
dbRef.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {}
override fun onDataChange(dataSnapshot: DataSnapshot) {
resultFileDataList.add(dataSnapshot)
}
})
}
callFunctionB() // call this function only after all the data in the loop above is retrieved
}
I tried runBlocking {} but no luck.
You can achieve it using this way by utilizing the Task. Tasks.whenall() will wait until all task are done.
fun functionA() {
val taskList = mutableListOf<Task<DataSnapshot>>()
val resultFileDataList = List<DataSnapshot>()
for ((key, value) in filesMap) {
val databaseReferenceTask: Task<DataSnapshot> = database.child("files").child(key).get()
taskList.add(databaseReferenceTask)
val resultTask = Tasks.whenAll(taskList)
resultTask.addOnCompleteListener {
for (task in taskList) {
val snapshotKey: String? = task.result.key
val snapShotValue = task.result
}
callFunctionB()
}
}
}
Since you are using Kotlin, then the simplest solution would be to use Kotlin Coroutines. In this way, you can use suspend functions and call await for each read operation. To achieve that, please check the following article:
How to read data from Firebase Realtime Database using get()?
If you need however to pipeline the requests over its existing connection, then you should consider using kotlinx-coroutines-play-services, case in which you can use awaitAll() function.
This is one way to do it:
suspend fun functionA() = suspendCoroutine<List<DataSnapshot>>{ continuation ->
val resultFileDataList = mutableListOf<DataSnapshot>()
for ((key, value) in filesMap) {
val dbRef = database.child("files").child(key)
dbRef.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {}
override fun onDataChange(dataSnapshot: DataSnapshot) {
resultFileDataList.add(dataSnapshot)
if(resultFileDataList.size == fileMaps.size){
continuation.resume(resultFileDataList)
}
}
})
}
}
And then you can call the functions wherever you want like so:
CoroutineScope(Dispatchers.IO).launch {
val dataSnapshotList = functionA()
functionB(dataSnapshotList)
}
Bear in mind that it is better to use the following to bind the coroutine to the lifecycle of the activity:
lifecycleScope.launch(Dispatchers.IO) {
val dataSnapshotList = functionA()
functionB(dataSnapshotList)
}
Note:
This will basically wait for all the data to change so that the onDataChanged() is triggered and when the last file is added, continues with the coroutine and returns the value. Depending on your user's behaviour, this could take a long time to complete since even if one of the files is not changed, the coroutine will not resume.
Also, if onCancelled() is triggered for one file, this will never complete. So if you are absolutely sure that onDataChanged() will be triggered for all files, use this. Otherwise, implement some sort of timeout functionality to resume with the incomplete data.
Related
My question is simple "how much time does it take to remove a listener?"
I am asking this because when I assign a ValueEventListener to a specific place in a real-time database and remove it after the work is done. Now, if still the changes are made to the same database location where the listener was working before being removed, I still get the values and that should not happen.
So, removing the ValueEventListener removes the listener instantaneously or after a while. If not then I might be doing something wrong with my code.
Along with ValueEventListener, I am also using callback flows.
// Repository Class
class TesterRepository {
//Some Dao I have
private val dao: GoodsDao = TODO()
private val dataSource = TestDataSource()
// Some State Flow
private val _someStateFlow = MutableStateFlow<String?>(null)
private val someStateFlow = _someStateFlow.asStateFlow()
private suspend fun someSyncFunction() {
coroutineScope {
launch { dao.getActiveTxnReferences().collect { _someStateFlow.value = it[0] } }
launch {
someStateFlow.collect {
if (it != null)
dataSource.remoteFirebaseDatabaseData(it).collectLatest {
//data fed into some other room database dao.
}
if (it == "some logic"){
// Say I have a logic here which tells me the reference of where previous code was being run, i.e., I get the same exact value of "ref" which was used in datasource
dataSource.removeFirebaseSync(it)
}
}
}
}
}
}
// DataSource Class
class TestDataSource() {
// Listener is saved here
private var listener: ValueEventListener? = null
suspend fun remoteFirebaseDatabaseData(ref: String) = callbackFlow {
//Listener
listener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
trySend(1)
TODO("Not yet implemented")
}
override fun onCancelled(error: DatabaseError) {
trySend(2)
TODO("Not yet implemented")
}
}
//Firebase Realtime Database call
//the code keeps sync until a condition is med
if (listener != null)
FirebaseDatabase.getInstance().getReference(ref).addValueEventListener(listener!!)
awaitClose {
if (listener != null)
FirebaseDatabase.getInstance().getReference(ref).removeEventListener(listener!!)
}
}
//This code should remove the listener
suspend fun removeFirebaseSync(ref: String) {
//The code is run.
if (listener != null)
FirebaseDatabase.getInstance().getReference(ref).removeEventListener(listener!!)
}
}
Now the problem is, after the remove event listener is run and I go on to realtime database console and change the values, the updates show up.
Explanation of code::
So There are 2 different Databases:
Represented by GoodsDao (Can change in Realtime)
Which we need to update using references contained in 1.
The first step gets the references from GoodsDao:
The References which are new should start listening to Realtime Database.
The References which are removed/absent, will take the path defined by if (it == "some logic") and remove the listeners.
I am a little skeptical about whether to use suspend or not because awaitClose {} is a suspend function call
Suspends the current coroutine until the channel is either closed or canceled and invokes the given block before resuming the coroutine.
trySend is the function being used to send data.
about trySend
Immediately adds the specified element to this channel, if this doesn't violate its capacity restrictions, and returns the successful result. Otherwise, returns failed or closed result. This is synchronous variant of send, which backs off in situations when send suspends or throws.
I have created an app with Kotlin and Firebase Firestore. Now I need to implement coroutines as there is so much work on the main thread. But I'm also a beginner so it's something new to me. I've watched some tutorials on this but I didn't find complete tutorials on Firestore with coroutines. So I need some help to implement coroutines in my app In such parts like these (I tried by myself but didn't get it).
Retrieving posts from Firestore.
private fun retrievePosts() {
FirebaseFirestore.getInstance().collection("Posts")
.orderBy("timeStamp", Query.Direction.DESCENDING)
.get()
.addOnSuccessListener { queryDocumentSnapshots ->
postList?.clear()
for (documentSnapshot in queryDocumentSnapshots) {
val post = documentSnapshot.toObject(Post::class.java)
postList?.add(post)
}
postAdapter?.notifyDataSetChanged()
postAdapter?.setOnPostClickListener(this)
if (isRefreshed) {
swipe_refresh_home?.setRefreshing(false)
isRefreshed = false
}
swipe_refresh_home?.visibility = VISIBLE
progress_bar_home?.visibility = GONE
}.addOnFailureListener { e ->
Log.d(TAG, "UserAdapter-retrieveUsers: ", e)
swipe_refresh_home?.visibility = VISIBLE
progress_bar_home?.visibility = GONE
}
}
Getting user data into an adapter
private fun userInfo( fullName: TextView, profileImage: CircleImageView,
about: TextView, uid: String,
userLocation: TextView, itemRoot: LinearLayout ) {
val userRef = FirebaseFirestore.getInstance().collection("Users").document(uid)
userRef.get()
.addOnSuccessListener {
if (it != null && it.exists()) {
val user = it.toObject(User::class.java)
Glide.with(mContext).load(user?.getImage()).placeholder(R.drawable.default_pro_pic).into(profileImage)
fullName.text = user?.getFullName().toString()
about.text = user?.getAbout()
if (user?.getLocation() != ""){
userLocation.visibility = VISIBLE
userLocation.text = user?.getLocation()
}
if (profileImage.drawable == null){
itemRoot.visibility = GONE
}
else{
itemRoot.visibility = VISIBLE
}
}
}
}
And this Save post button in an adapter.
private fun savedPost(postId: String, saveButton: ImageView?) {
FirebaseFirestore.getInstance().collection("Users").document(currentUserID)
.collection("Saved Posts").document(postId)
.get()
.addOnSuccessListener {
if (it.exists()) {
saveButton?.setImageResource(drawable.ic_bookmark)
} else {
saveButton?.setImageResource(drawable.bookmark_post_ic)
}
}
}
As I see your code, you are using the following query:
val queryPostsByTimestamp = FirebaseFirestore.getInstance().collection("Posts")
.orderBy("timeStamp", Query.Direction.DESCENDING)
Most probably to get a list of Post objects from your "Posts" collection.
In order to use Kotlin Coroutines, don't forget to add the following dependencies in the Gradle (app) file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.9"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
I'll provide you a solution using the MVVM architecture pattern. So we'll use a repository class and a ViewModel class. For the asynchronous calls to Firestore, we'll use Flow.
For the response that we get from the database call, we need a sealed class that looks like this:
sealed class Response<out T> {
class Loading<out T>: Response<T>()
data class Success<out T>(
val data: T
): Response<T>()
data class Failure<out T>(
val errorMessage: String
): Response<T>()
}
Assuming that you have a "Post" class, let's create in the repository class the following function:
fun getPostsFromFirestore() = flow {
emit(Loading())
emit(Success(queryPostsByTimestamp.get().await().documents.mapNotNull { doc ->
doc.toObject(Post::class.java)
}))
}. catch { error ->
error.message?.let { errorMessage ->
emit(Failure(errorMessage))
}
}
So we'll emit an object according to the state. When first-time calling the function, we emit a loading state using emit(Loading(), when we get the data we emit the List<Post> and if we get an error, we emit the error message using Failure(errorMessage).
Now we need to call this function, from the ViewModel class:
fun getPosts() = liveData(Dispatchers.IO) {
repository.getPostsFromFirestore().collect { response ->
emit(response)
}
}
With the above function, we collect the data that we get from the getPostsFromFirestore() function call, and we emit the result further as a LiveData object so it can be observed in the activity/fragment like this:
private fun getPosts() {
viewModel.getPosts().observe(this, { response ->
when(response) {
is Loading -> //Load a ProgessBar
is Success -> {
val postList = response.data
//Do what you need to do with your list
//Hide the ProgessBar
}
is Failure -> {
print(response.errorMessage)
//Hide the ProgessBar
}
}
})
}
That's pretty much of it!
I don't know Firebase, so I may miss something, but generally speaking you don't need a special support in the library to use it with coroutines. If you start a background coroutine and then execute your above code in it, then Firebase will probably run within your coroutine without any problems.
The only problematic part could be listeners. Some libs invoke callbacks in the thread that was used to execute them, but some dispatch callbacks to a specific thread. In the case of Firebase it seems by default it runs listeners in the main thread. If this is not what you want, you can pass an executor to run callbacks within coroutines as well, e.g.:
.addOnSuccessListener(Dispatchers.Default.asExecutor()) { ... }
I have a DAO class where I have fetchHubList method which fetches a collection of documents from cloud Firestore asynchronously using await(). This implementation used the "get()" method which I got to know later on does not fetch real-time updates. On trying to implement the code similarly using onSnapshotListener gives an error (which was quite expected to be honest, because get() and this methods return quite different things). Does anyone have any idea how to implement this?
How the code is currently:
suspend fun fetchHubList(): ArrayList<HubModel>? = try {
val hubList = ArrayList<HubModel>()
hubsListCollection.get().await().map { document ->
if (document != null) {
Log.d(TAG, "Data fetch successful!")
Log.d(TAG, "the document id is ${document.id}")
val temp = HubModel(document.get("hubName").toString(),
document.id.toString(),
document.get("isAdmin") as Boolean)
hubList.add(temp)
// hubList.add(document.toObject(HubModel::class.java))
} else {
Log.d(TAG, "No such document")
}
}
And what I want to implement here (and which is totally erroneous):
suspend fun fetchHubList(): ArrayList<HubModel>? = try {
val hubList = ArrayList<HubModel>()
hubsListCollection.addSnapshotListener().await().map { document ->
if (document != null) {
Log.d(TAG, "Data fetch successful!")
Log.d(TAG, "the document id is ${document.id}")
val temp = HubModel(document.get("hubName").toString(),
document.id.toString(),
document.get("isAdmin") as Boolean)
hubList.add(temp)
// hubList.add(document.toObject(HubModel::class.java))
} else {
Log.d(TAG, "No such document")
}
}
I use this function in my ViewModel class to create a LiveData wrapped ArrayList:
val hubList = MutableLiveData<ArrayList<HubModel>>()
private val hubListDao = HubListDao()
init {
viewModelScope.launch {
hubList.value = hubListDao.fetchHubList()
}
}
Thanks in advance!
You don't need addSnapshotListener, just use get:
hubsListCollection.get().await()
In order to observe changes in your collection you can extend LiveData:
class CafeLiveData(
private val documentReference: DocumentReference
) : LiveData<Cafe>(), EventListener<DocumentSnapshot> {
private var snapshotListener: ListenerRegistration? = null
override fun onActive() {
super.onActive()
snapshotListener = documentReference.addSnapshotListener(this)
}
override fun onInactive() {
super.onInactive()
snapshotListener?.remove()
}
override fun onEvent(result: DocumentSnapshot?, error: FirebaseFirestoreException?) {
val item = result?.let { document ->
document.toObject(Cafe::class.java)
}
value = item!!
}
}
And expose it from your view model:
fun getCafe(id: String): LiveData<Cafe> {
val query = Firebase.firestore.document("cafe/$id")
return CafeLiveData(query)
}
As #FrankvanPuffelen already mentioned in his comment, there is no way you can use ".await()" along with "addSnapshotListener()", as both are two totally different concepts. One is used to get data only once, while the second one is used to listen to real-time updates. This means that you can receive a continuous flow of data from the reference you are listening to.
Please notice that ".await()" is used in Kotlin with suspend functions. This means that when you call ".await()", you start a separate coroutine, which is a different thread that can work in parallel with other coroutines if needed. This is called async programming because ".await()" starts the coroutine execution and waits for its finish. In other words, you can use ".await()" on a deferred value to get its eventual result, if no Exception is thrown. Unfortunately, this mechanism doesn't work with real-time updates.
When it comes to Firestore, you can call ".await()" on a DocumentReference object, on a Query object, or on a CollectionReference object, which is actually a Query without filters. This means that you are waiting for the result/results to be available. So you can get a document or multiple documents from such calls. However, the following call:
hubsListCollection.addSnapshotListener().await()
Won't work, as "addSnapshotListener()" method returns a ListenerRegistration object.
I want to use a snapshot listener to listen to changes that might occur in my database to update my RecyclerView
In this case, you should consider using a library called Firebase-UI for Android. In this case, all the heavy work will be done behind the scenes. So there is no need for any coroutine or ".await()" calls, everything is synched in real-time.
If you don't want to use either Kotlin Coroutines, nor Firebase-UI Library, you can use LiveData. A concrete example can be seen in my following repo:
https://github.com/alexmamo/FirestoreRealtimePagination/blob/master/app/src/main/java/ro/alexmamo/firestorerealtimepagination/ProductListLiveData.java
Where you can subclass LiveData class and implement EventListener the interface.
I'm building an app for a friend and I use Firestore. What I want is to display a list of favorite places but for some reason, the list is always empty.
I cannot get the data from Firestore. This is my code:
fun getListOfPlaces() : List<String> {
val places = ArrayList<String>()
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result) {
val name = document.data["name"].toString()
places.add(name)
}
}
}
return list;
}
If I try to print, let's say the size of the list in onCreate function, the size is always 0.
Log.d("TAG", getListOfPlaces().size().toString()); // Is 0 !!!
I can confirm Firebase is successfully installed.
What am I missing?
This is a classic issue with asynchronous web APIs. You cannot return something now, that hasn't been loaded yet. With other words, you cannot simply return the places list as a result of a method because it will always be empty due the asynchronous behavior of the onComplete function. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available.
But not only Cloud Firestore loads data asynchronously, almost all of modern other web APIs do, since it may take some time to get the data. But let's take an quick example, by placing a few log statements in the code, to see more clearly what I'm talking about.
fun getListOfPlaces() : List<String> {
Log.d("TAG", "Before attaching the listener!");
val places = ArrayList<String>()
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("TAG", "Inside onComplete function!");
for (document in task.result) {
val name = document.data["name"].toString()
places.add(name)
}
}
}
Log.d("TAG", "After attaching the listener!");
return list;
}
If we run this code will, the output in your logcat will be:
Before attaching the listener!
After attaching the listener!
Inside onComplete function!
This is probably not what you expected, but it explains precisely why your places list is empty when returning it.
The initial response for most developers is to try and "fix" this asynchronous behavior, which I personally recommend against it. Here is an excelent article written by Doug Stevenson that I'll highly recommend you to read.
A quick solve for this problem would be to use the places list only inside the onComplete function:
fun readData() {
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val list = ArrayList<String>()
for (document in task.result) {
val name = document.data["name"].toString()
list.add(name)
}
//Do what you need to do with your list
}
}
}
If you want to use the list outside, there is another approach. You need to create your own callback to wait for Firestore to return you the data. To achieve this, first you need to create an interface like this:
interface MyCallback {
fun onCallback(value: List<String>)
}
Then you need to create a function that is actually getting the data from the database. This method should look like this:
fun readData(myCallback : MyCallback) {
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val list = ArrayList<String>()
for (document in task.result) {
val name = document.data["name"].toString()
list.add(name)
}
myCallback.onCallback(list)
}
}
}
See, we don't have any return type anymore. In the end just simply call readData() function in your onCreate function and pass an instance of the MyCallback interface as an argument like this:
readData(object: MyCallback {
override fun onCallback(value: List<String>) {
Log.d("TAG", list.size.toString())
}
})
If you are using Kotlin, please check the other answer.
Nowadays, Kotlin provides a simpler way to achieve the same result as in the case of using a callback. This answer is going to explain how to use Kotlin Coroutines. In order to make it work, we need to add the following dependency in our build.gradle file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.2.1"
This library that we use is called Module kotlinx-coroutines-play-services and is used for the exact same purpose. As we already know, there is no way we can return a list of objects as a result of a method because get() returns immediately, while the callback from the Task it returns will be called sometime later. That's the reason why we should wait until the data is available.
When calling "get()" on the Task object that is returned, we can attach a listener so we can get the result of our query. What we need to do now is to convert this into something that is working with Kotlin Coroutines. For that, we need to create a suspend function that looks like this:
private suspend fun getListOfPlaces(): List<DocumentSnapshot> {
val snapshot = placesRef.get().await()
return snapshot.documents
}
As you can see, we have now an extension function called await() that will interrupt the Coroutine until the data from the database is available and then return it. Now we can simply call it from another suspend method like in the following lines of code:
private suspend fun getDataFromFirestore() {
try {
val listOfPlaces = getListOfPlaces()
} catch (e: Exception) {
Log.d(TAG, e.getMessage()) //Don't ignore potential errors!
}
}
The reason for having a empty list got perfectly answered by Alex Mamo above.
I just like to present the same thing without needing to add an extra interface.
In Kotlin you could just implement it like so:
fun readData(myCallback: (List<String>) -> Unit) {
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val list = ArrayList<String>()
for (document in task.result) {
val name = document.data["name"].toString()
list.add(name)
}
myCallback(list)
}
}
}
and then use it like so:
readData() {
Log.d("TAG", it.size.toString())
})
I'm building an app for a friend and I use Firestore. What I want is to display a list of favorite places but for some reason, the list is always empty.
I cannot get the data from Firestore. This is my code:
fun getListOfPlaces() : List<String> {
val places = ArrayList<String>()
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result) {
val name = document.data["name"].toString()
places.add(name)
}
}
}
return list;
}
If I try to print, let's say the size of the list in onCreate function, the size is always 0.
Log.d("TAG", getListOfPlaces().size().toString()); // Is 0 !!!
I can confirm Firebase is successfully installed.
What am I missing?
This is a classic issue with asynchronous web APIs. You cannot return something now, that hasn't been loaded yet. With other words, you cannot simply return the places list as a result of a method because it will always be empty due the asynchronous behavior of the onComplete function. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available.
But not only Cloud Firestore loads data asynchronously, almost all of modern other web APIs do, since it may take some time to get the data. But let's take an quick example, by placing a few log statements in the code, to see more clearly what I'm talking about.
fun getListOfPlaces() : List<String> {
Log.d("TAG", "Before attaching the listener!");
val places = ArrayList<String>()
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("TAG", "Inside onComplete function!");
for (document in task.result) {
val name = document.data["name"].toString()
places.add(name)
}
}
}
Log.d("TAG", "After attaching the listener!");
return list;
}
If we run this code will, the output in your logcat will be:
Before attaching the listener!
After attaching the listener!
Inside onComplete function!
This is probably not what you expected, but it explains precisely why your places list is empty when returning it.
The initial response for most developers is to try and "fix" this asynchronous behavior, which I personally recommend against it. Here is an excelent article written by Doug Stevenson that I'll highly recommend you to read.
A quick solve for this problem would be to use the places list only inside the onComplete function:
fun readData() {
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val list = ArrayList<String>()
for (document in task.result) {
val name = document.data["name"].toString()
list.add(name)
}
//Do what you need to do with your list
}
}
}
If you want to use the list outside, there is another approach. You need to create your own callback to wait for Firestore to return you the data. To achieve this, first you need to create an interface like this:
interface MyCallback {
fun onCallback(value: List<String>)
}
Then you need to create a function that is actually getting the data from the database. This method should look like this:
fun readData(myCallback : MyCallback) {
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val list = ArrayList<String>()
for (document in task.result) {
val name = document.data["name"].toString()
list.add(name)
}
myCallback.onCallback(list)
}
}
}
See, we don't have any return type anymore. In the end just simply call readData() function in your onCreate function and pass an instance of the MyCallback interface as an argument like this:
readData(object: MyCallback {
override fun onCallback(value: List<String>) {
Log.d("TAG", list.size.toString())
}
})
If you are using Kotlin, please check the other answer.
Nowadays, Kotlin provides a simpler way to achieve the same result as in the case of using a callback. This answer is going to explain how to use Kotlin Coroutines. In order to make it work, we need to add the following dependency in our build.gradle file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.2.1"
This library that we use is called Module kotlinx-coroutines-play-services and is used for the exact same purpose. As we already know, there is no way we can return a list of objects as a result of a method because get() returns immediately, while the callback from the Task it returns will be called sometime later. That's the reason why we should wait until the data is available.
When calling "get()" on the Task object that is returned, we can attach a listener so we can get the result of our query. What we need to do now is to convert this into something that is working with Kotlin Coroutines. For that, we need to create a suspend function that looks like this:
private suspend fun getListOfPlaces(): List<DocumentSnapshot> {
val snapshot = placesRef.get().await()
return snapshot.documents
}
As you can see, we have now an extension function called await() that will interrupt the Coroutine until the data from the database is available and then return it. Now we can simply call it from another suspend method like in the following lines of code:
private suspend fun getDataFromFirestore() {
try {
val listOfPlaces = getListOfPlaces()
} catch (e: Exception) {
Log.d(TAG, e.getMessage()) //Don't ignore potential errors!
}
}
The reason for having a empty list got perfectly answered by Alex Mamo above.
I just like to present the same thing without needing to add an extra interface.
In Kotlin you could just implement it like so:
fun readData(myCallback: (List<String>) -> Unit) {
placesRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val list = ArrayList<String>()
for (document in task.result) {
val name = document.data["name"].toString()
list.add(name)
}
myCallback(list)
}
}
}
and then use it like so:
readData() {
Log.d("TAG", it.size.toString())
})