FireBase multiple queries by document and collection - android

I am struggling with firebase to run one query to take the truckDocumentId and after that to run another query to take the routesByDateDocumentIdand at the end I am using both document ids to run the function "sendGpsPosition", my problem is that the first query finds truckDocumentId but sometimes the second query does not execute and that is why the applications stops. The code below is for Kotlin.
If I am on Debug then most of the time works.. if I switch off the debug it almost shows the error below =>
And because the query does not execute I got this error: java.lang.IllegalArgumentException: Invalid document reference. Document references must have an even number of segments, but trucks has 1
suspend fun getTruckId() {
val trucksReference = firestore.collection("trucks").whereEqualTo("dispatcher", "Miro")
.whereEqualTo("name", "PEUGEOT").get().await()
val document = trucksReference.documents[0]
if (document != null) {
truckDocumentId = document.id
}
}
suspend fun getRouteReferenceId() {
val routesByDate = firestore.collection("trucks")
.document(truckDocumentId)
.collection("routes_by_date").get().await()
val documentRoute = routesByDate.documents[0]
if (documentRoute != null) {
routesByDateDocumentId = documentRoute.id
}
}
fun sendGpsPosition(lat: Double, long: Double, imageRef: String? = null) {
runBlocking { getTruckId() } // if I get this DocumentID
runBlocking { getRouteReferenceId() } // this here maybe will be not found or maybe will be found.. the async is not done correct not sure how to do it.
firestore
.collection("trucks")
.document(truckDocumentId)
.collection("routes_by_date")
.document(routesByDateDocumentId)
.collection("live_route")
.add(LatLong(Timestamp.now(), lat, long))
}

**I solved it this way.**
private suspend fun getTruckId() {
val trucksReference = firestore.collection("trucks")
.whereEqualTo("dispatcher", "Miro")
.whereEqualTo("name", "VW")
.get()
.await()
val document = trucksReference.documents[0]
if (document != null) {
truckDocumentId = document.id
}
}
private suspend fun getRouteReferenceId() {
val currentTime = Timestamp.now()
val routesByDate = firestore.collection("trucks")
.document(truckDocumentId)
.collection("routes_by_date")
.get()
.await() // here will be better to look for data by delivery_day
val documentRoute = routesByDate.documents[0]
if (documentRoute != null) {
routesByDateDocumentId = documentRoute.documents[0].id
}
}
private fun addGpsDataInDatabase(lat: Double, long: Double, imageRef: String? = null) {
firestore
.collection("trucks")
.document(truckDocumentId)
.collection("routes_by_date")
.document(routesByDateDocumentId)
.collection("planned_route") //planned_route or live_route depends if we want to show current state of a truck of make a route plan
.add(LatLong(Timestamp.now(), lat, long))
}
fun sendGpsPosition(lat: Double, long: Double, imageRef: String? = null) {
GlobalScope.launch {
val truckDocId = async { getTruckId() }
truckDocId.await()
val routeDocId = async { getRouteReferenceId() }
routeDocId.await()
addGpsDataInDatabase(lat, long, imageRef)
}
}

Related

Extract Data from firebase

Unable to extract information from the datasnapshot received from firebase.
Currently, I am able to get the dataSnapshot from firebase, but I am having problems extracting the information from it.
In the example below I have a lobby with the code "81MUB" and inside I have a list of players (only using one player in the example). Data from FireBase
{
"81MUB": [
{
"name": "Alejandro",
"points": 0
}
]
}
Data Class
data class Player(
val name: String,
val points: Int
)
Listener
fun getCode(): String {
val index = ('A'..'Z') + ('1'..'9')
var code = ""
for (i in 0..4){
code += index[Random().nextInt(index.size)]
}
return code
}
class MviewModel : ViewModel() {
private val _Players: MutableLiveData<MutableList<Player>> =
MutableLiveData(mutableListOf<Player>(Player("Alejandro", 0)))
private var _LobbyCode: String = ""
private val dataBase = FirebaseDatabase.getInstance()
fun getPlayer(): MutableLiveData<MutableList<Player>> = _Players
fun createLobby() {
_LobbyCode = getCode()
}
fun listener() {
val postListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
}
}
dataBase.reference.child(_LobbyCode).addValueEventListener(postListener)
}
}
Any tips?
Each time you call getCode() you are generating a new random code. When reading data, you always use the exact same code that exists in the database. So in code, it should look like this:
val db = Firebase.database.reference
val codeRef = db.child("81MUB")
codeRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
val name = snapshot.child("name").getValue(String::class.java)
val points = snapshot.child("points").getValue(Long::class.java)
Log.d("TAG", "$name/$points")
} else {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
The result in the logcat will be:
Alejandro/0
If you however want to map the 81MUB node into an object of type Player, then your data class should look like this:
data class Player(
val name: String? = null,
val points: Int? = null
)
And in code:
val db = Firebase.database.reference
val codeRef = db.child("81MUB")
codeRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
val player = snapshot.getValue(Player::class.java)
Log.d("TAG", "${player.name}/${player.points}")
} else {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
Which will produce the exact same output as above.
You might also take into consideration, using the DatabaseReference#push() method which:
Create a reference to an auto-generated child location. The child key is generated client-side and incorporates an estimate of the server's time for sorting purposes.
Instead of using your codes.

How to test ViewModel + Flow

I'm doing a small project to learn flow and the latest Android features, and I'm currently facing the viewModel's testing, which I don't know if I'm performing correctly. can you help me with it?
Currently, I am using a use case to call the repository which calls a remote data source that gets from an API service a list of strings.
I have created a State to control the values in the view model:
data class StringItemsState(
val isLoading: Boolean = false,
val items: List<String> = emptyList(),
val error: String = ""
)
and the flow:
private val stringItemsState = StringtemsState()
private val _stateFlow = MutableStateFlow(stringItemsState)
val stateFlow = _stateFlow.asStateFlow()
and finally the method that performs all the logic in the viewModel:
fun fetchStringItems() {
try {
_stateFlow.value = stringItemsState.copy(isLoading = true)
viewModelScope.launch(Dispatchers.IO) {
val result = getStringItemsUseCase.execute()
if (result.isEmpty()) {
_stateFlow.value = stringItemsState
} else {
_stateFlow.value = stringItemsState.copy(items = result)
}
}
} catch (e: Exception) {
e.localizedMessage?.let {
_stateFlow.value = stringItemsState.copy(error = it)
}
}
}
I am trying to perform the test following the What / Where / Then pattern, but the result is always an empty list and the assert verification always fails:
private val stringItems = listOf<String>("A", "B", "C")
#Test
fun `get string items - not empty`() = runBlocking {
// What
coEvery {
useCase.execute()
} returns stringItems
// Where
viewModel.fetchStringItems()
// Then
assert(viewModel.stateFlow.value.items == stringItems)
coVerify(exactly = 1) { viewModel.fetchStringItems() }
}
Can someone help me and tell me if I am doing it correctly? Thanks.

Firestore live update using Kotlin Flow

I want to implement system with live updates (similar to onSnapshotListener). I heard that this can be done with Kotlin Flow.
Thats my function from repository.
suspend fun getList(groupId: String): Flow<List<Product>> = flow {
val myList = mutableListOf<Product>()
db.collection("group")
.document(groupId)
.collection("Objects")
.addSnapshotListener { querySnapshot: QuerySnapshot?,
e: FirebaseFirestoreException? ->
if (e != null) {}
querySnapshot?.forEach {
val singleProduct = it.toObject(Product::class.java)
singleProduct.productId = it.id
myList.add(singleProduct)
}
}
emit(myList)
}
And my ViewModel
class ListViewModel: ViewModel() {
private val repository = FirebaseRepository()
private var _products = MutableLiveData<List<Product>>()
val products: LiveData<List<Product>> get() = _produkty
init {
viewModelScope.launch(Dispatchers.Main){
repository.getList("xGRWy21hwQ7yuBGIJtnA")
.collect { items ->
_products.value = items
}
}
}
What do I need to change to make it work? I know data is loaded asynchronously and it doesn't currently work (the list I emit is empty).
You can use this extension function that I use in my projects:
fun Query.snapshotFlow(): Flow<QuerySnapshot> = callbackFlow {
val listenerRegistration = addSnapshotListener { value, error ->
if (error != null) {
close()
return#addSnapshotListener
}
if (value != null)
trySend(value)
}
awaitClose {
listenerRegistration.remove()
}
}
It uses the callbackFlow builder to create a new flow instance.
Usage:
fun getList(groupId: String): Flow<List<Product>> {
return db.collection("group")
.document(groupId)
.collection("Objects")
.snapshotFlow()
.map { querySnapshot ->
querySnapshot.documents.map { it.toObject<Product>() }
}
}
Note that you don't need to mark getList as suspend.
Starting in firestore-ktx:24.3.0, you can use the Query.snapshots() Kotlin flow to get realtime updates:
suspend fun getList(groupId: String): Flow<List<Product>> {
return db.collection("group")
.document(groupId)
.collection("Objects")
.snapshots().map { querySnapshot -> querySnapshot.toObjects()}
}
As of 2 days ago, firestore has this functionality provided out of the box: https://github.com/firebase/firebase-android-sdk/pull/1252/

How to retrieve data from nested array of maps in Firestore (Kotlin) for RecyclerView

I would like to ask for help on how to retrieve data from Firestore for nested Array of Maps called "cities" into MutableList , which I then want to insert into recycler view, where the data from the “regions” are for the header and data “cities” for the regular list items.
Data for regions: MutableList , when I follow the procedure https://medium.com/firebase-tips-tricks/how-to-map-an-array-of-objects-from-cloud-firestore-to-a-list -of-objects-122e579eae10 by Alex Mamo, got fine, but data for: cities: MutableList , according same approach, is null (unable to retrive).
Can you please advise how to get data for “cities”?
P.s. somewhere I read the recommendation to iterate over "cities", but I have no idea how, please go straight for an example (ideally in Kontlin).
Code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
…..
regionsRef.get().addOnCompleteListener { document ->
if (document.isSuccessful()) {
val documentSnapshot = document.result
// Retrieve array of maps for „regions“
val regions = documentSnapshot.toObject(RegionDocument::class.java)?.regions
// Retrieve array of maps for „cities“
val cities = documentSnapshot.toObject(CityDocument::class.java)?.cities
…
}
}
Data classes for object City:
data class City(
val cityNumber: Long? = null,
val cityName: String? = "" )
data class CityDocument(
var cities: MutableList<City>? = null)
Firestore structure:
To be able to get the data that corresponds to your document structure, you need three classes:
class Document {
var regions: MutableList<Region>? = null
}
class Region {
var cities: MutableList<City>? = null
var regionName: String? = null
var regionNumber: Long? = null
}
class City {
var cityName: String? = null
var cityNumber: Long? = null
}
And below you can find a solution for reading all cities:
val db = FirebaseFirestore.getInstance()
val docIdRef = db.collection("collName").document("docId")
docIdRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val document = task.result
if (document != null) {
val doc = document.toObject(Document::class.java)
if (doc != null) {
val regions = doc.regions
if (regions != null) {
for (region in regions) {
val cities = region.cities
//Do what you need to to do with your List<City>.
}
}
}
}
} else {
Log.d("TAG", task.exception!!.message!!) //Never ignore potential errors!
}
}
Now, simply replace collName and docId with the one you have in your database.

Kotlin: Make the function wait to return until for loop is complete [duplicate]

This question already has answers here:
How to return a DocumentSnapShot as a result of a method?
(2 answers)
Closed 1 year ago.
I have a simple function that retrieves a value from Firebase Firestore. I want to return the value. How do I make the function wait until the value is retrieved, rather than immediately returning an empty value?
Please Kotlin only.
fun fireStoreGetter(keyVal: String): String {
Log.d("FIRESTORE_OP", "fGet running with keyVal: " + keyVal)
//logs: FIRESTORE_OP: fGet running with keyVal: exampleString
var userId :String = ""
var mFirebaseDatabaseInstances = FirebaseFirestore.getInstance()
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
userId = user.uid
//Log.e(TAG, "User data is null")
} else {
Log.d("FIRESTORE_OP", "MUST AUTHENTICATE TO ACCESS FIRESTORE")
}
Log.d("FIRESTORE_OP", "USER IN WITH UID: " + userId)
//logs FIRESTORE_OP: USER IN WITH UID: DGGiDibnldhP5z6iUyf_GQ
val docRef2 = mFirebaseDatabaseInstances.collection("users").document(userId).collection("dBase").document("exampleDoc")
docRef2.get()
.addOnSuccessListener { document ->
//for (document in result) {
if (document != null) {
//docVal is instantiated in main thread
docVal = document.getString(keyVal).toString()//String(keyVal)
Log.d("FIRESTORE_OP", "DOCVAL SET: " + docVal)
//logs: FIRESTORE_OP: DOCVAL SET: (correct value from firestore)
}
}
Log.d("FIRESTORE_OP", "RETURNING fGet: " + "VALUE: " + docVal)
//logs: FIRESTORE_OP: RETURNING fGet: VALUE:
return docVal
//returns empty
}
So, this is how you try to do it now:
fun myFunction() {
val value = fireStoreGetter(keyVal)
...
}
fun fireStoreGetter(keyVal: String): String {
val docRef2 = ...
docRef2.get().addOnSuccessListener { document ->
for (document in result) {
if (document != null) {
docVal = ...
}
}
}
return docVal
}
And this is how you should do it:
fun myFunction(value: String) {
...
}
fun fireStoreGetter(keyVal: String) {
val docRef2 = ...
docRef2.get().addOnSuccessListener { document ->
for (document in result) {
if (document != null) {
docVal = ...
}
}
myFunction(docVal)
}
}
Your docRef2.get() returns a Task instance. Which hasn't functions to wait for its result right in place, like in a suspend coroutine function or something like that. So the only way is not to return the result from fireStoreGetter function, but rather trigger myFunction when the result is ready.
UPD: Turns out there IS a way to wait for Task's result right in place. See Kamal's answer if you really need to get rid of callbacks. But keep in mind that this will require a little extra tweaking. Coroutines, suspend functions, etc.
You need to add below dependency to use await()
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1'
and use suspend keyword in the function name and add .await() after success or failure listener of firebase. This will make the code wait for the function to retrieve data from firebase.
You can read this blog for reference
Your code should be like this:
suspend fun fireStoreGetter(keyVal: String): String {
Log.d("FIRESTORE_OP", "fGet running with keyVal: " + keyVal)
//logs: FIRESTORE_OP: fGet running with keyVal: exampleString
var userId :String = ""
var mFirebaseDatabaseInstances = FirebaseFirestore.getInstance()
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
userId = user.uid
//Log.e(TAG, "User data is null")
} else {
Log.d("FIRESTORE_OP", "MUST AUTHENTICATE TO ACCESS FIRESTORE")
}
Log.d("FIRESTORE_OP", "USER IN WITH UID: " + userId)
//logs FIRESTORE_OP: USER IN WITH UID: DGGiDibnldhP5z6iUyf_GQ
val docRef2 = mFirebaseDatabaseInstances.collection("users").document(userId).collection("dBase").document("exampleDoc")
docRef2.get()
.addOnSuccessListener { document ->
//for (document in result) {
if (document != null) {
//docVal is instantiated in main thread
docVal = document.getString(keyVal).toString()
}
}
return docVal
}.await()

Categories

Resources