Reputation: 13
I am using the Real Time Database from Firebase on java for Android and I am trying to write a set of helper function to facilitate read and write functions from/to the RTDB.
My function looks like the following and is supposed to return a HashMap of my Parking objects on the database; I get a reference to my database and add an onSuccessListener
where I iterate through the snapshot and add each Parking
object to my HashMap and return the HashMap parkings
.
The problem is the function returns parkings
with no values in it before the onSuccessListener runs.
public static ArrayList<Parking> getParkingLots() {
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
Task<DataSnapshot> dataSnapshotTask = mDatabase.get();
ArrayList<Parking> parkings = new ArrayList<Parking>();
dataSnapshotTask.addOnSuccessListener(new OnSuccessListener<DataSnapshot>() {
@Override
public void onSuccess(DataSnapshot dataSnapshot) {
Iterable<DataSnapshot> parkingsData = dataSnapshot.getChildren();
for (DataSnapshot parking :
parkingsData) {
parkings.add(parking.getValue(Parking.class));
}
}
});
return parkings;
}
I tried this implementation as well where I directly try and get the results from the Task datSnapshotTask
but I get an exeption thrown
java.lang.IllegalStateException: Task is not yet complete
.
public static HashMap<String, Parking> getParkingLots() {
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
Task<DataSnapshot> dataSnapshotTask = mDatabase.get();
Iterable<DataSnapshot> parkingsData = dataSnapshotTask.getResult().getChildren();
HashMap<String, Parking> parkings = new HashMap<String, Parking>();
for (DataSnapshot parking :
parkingsData) {
parkings.put(parking.getKey(), parking.getValue(Parking.class));
}
return parkings;
}
Is there a way to get the results from the Task in an await fashion ?
Upvotes: 1
Views: 4491
Reputation: 30625
I don't think it's possible to do in "an await fashion" in Java.
But in Kotlin we can use coroutines for that.
suspend fun getParkingLots(): HashMap<String, Parking> {
val database: DatabaseReference = FirebaseDatabase.getInstance().reference
val task: Task<DataSnapshot> = database.get()
val deferredDataSnapshot: Deferred<DataSnapshot> = task.asDeferred()
val parkingsData: Iterable<DataSnapshot> = deferredDataSnapshot.await().children // or can use just task.await()
// ... use parkingsData
}
Using Task.asDeferred
extension function we can convert a Task
into a Deferred
object.
Or can use Task.await()
without converting the task
to the Deferred
object.
To use Task.asDeferred()
extension function use dependency:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.2'
To call suspend
function we need to launch a coroutine
someCoroutineScope.launch {
val parkings = getParkingLots()
}
someCoroutineScope
is a CoroutineScope
instance. In android it can be viewModelScope
in ViewModel
class and lifecycleScope
in Activity
or Fragment
, or some custom CoroutineScope
instance. Dependencies:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
Upvotes: 2
Reputation: 598728
Data is loaded from Firebase asynchronously, since it may have to come from the server. While the data is being loaded, your main code continues to run. Then when the data is available, the task completes and your onSuccess
gets called.
It's easiest to see what this means in practice by running in a debugger, or by adding some logging:
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
Log.i("Firebase", "1. Starting to load data");
Task<DataSnapshot> dataSnapshotTask = mDatabase.get();
for (DataSnapshot parking: parkingsData) {
Log.i("Firebase", "2. Got data");
}
Log.i("Firebase", "3. After starting to load data");
When you run this code, it prints:
Starting to load data
After starting to load data
Got data
This is probably not the order that you expected, but it actually working as intended. It also explains why you're not getting a result from your getParkingLots
function: by the time your return parkings
runs, the parkings.add(parking.getValue(Parking.class))
hasn't been called yet!
The solution for this is always the same: any code that needs the data from the database, needs to be inside your onSuccess
method or be called from there. This means you can't return the parking lots from the function, but you can pass in a callback that takes them as a parameter. Or you could return a Task<HashMap<String, Parking>>
pretty similar to what Firebase does got get()
.
For an example of the callback, see my longer explanation on: getContactsFromFirebase() method return an empty list
Upvotes: 2