Does whereEqualTo support HashMap equality? - android

The whereEqualTo API can take Any as its argument. Does this mean that I can pass it a HashMap<K, V> and it will compare its matching fields with that of a field of map type in the Firestore database or do I have to manually compare each property of the map?
I've build a query like this one
collection.whereEqualTo("meta", hashMapOf( // <-- using full hash-map for matching
"user" to "JD",
"name" to "John"
))
where meta is a map-type field with two key/value pairs.
The document I'm trying to match has this structure:
{
"createdOn": as timestamp
...other properties
"meta": as map
{
"user": "JD", as string
"name": "John" as string
}
}
The criteria I use should yield some results but it doesn't (without crashing) so I was wondering whether I made a mistake somewhere else or is this type of a query simply not supported? The result is empty.
I might also be searching only for the name property with a simpler query like this one to find all Johns
collection.whereEqualTo("meta", hashMapOf( // <-- using parcial hash-map for matching
"name" to "John"
))
Will it match only name fields or does it require a full-map?
I mean, should the previous query work or do I need to rewrite as this?
collection
.whereEqualTo("meta.user", "JD")
.whereEqualto("meta.name", "John")

Firestore can compare items of a map in your client code with a map in a document. But the maps must be completely equivalent, so you must specify all properties in your code. So something like this should work:
collection.whereEqualTo("meta", hashMapOf( // <-- using parcial hash-map for matching
"user" to "JD",
"name" to "John"
))
If you can't get this to work, edit your question to include a screenshot of the document you think this should match and I'll have another look.
There is no way specify partial map matches for a field, so if that's what you need, you'll indeed need to add separate conditions for each nested field.

Related

Firebase RTDB: How to filter child events based on a nested field?

Given that every entry in my Firebase Realtime Database has a version field that represents the timestamp it was last edited, how can I filter them by their version such that only child events that are newer than X are delivered?
val listener = object : ChildEventListener {
// Not relevant
}
val ref = database.getReference("exercises").child("userId")
ref.addChildEventListener(listener)
// TODO: ref.filter(version>localVersion)
For context as to why Im looking to do this. There are very many entries, and while Id like to know whenever any of them change, observing all of them at all times results in too big of a memory footprint (>150 mb).
Json representation of a data model (exercise):
{
"details" : {
"category" : "LIFT",
"equipment" : "CABLE_MACHINE",
"muscles" : [ "BACK" ],
"name" : "Row in Cable Machine",
"status" : "AVAILABLE"
},
"id" : "1",
"source" : "CUSTOM",
"version" : 1648446264560 // This is the field Id like to filter on.
}
How can I filter them by their version such that only child events that are newer than X are delivered?
Since your version filed is a number, to get only the objects newer than a particular value you can use the Query#startAt() function, which:
Creates a query constrained to only return child nodes with a value greater than or equal to the given value, using the given orderBy directive or priority as default.
Or Query#startAfter() function that:
Creates a query constrained to only return child nodes with a value greater than the given value, using the given orderBy directive or priority as default.

How do you change a field in array of maps in firestore? [duplicate]

I am stuck in one case of firebase operation in android.
My Requirement :
I have table Named : "Values"
This value table contains following type of data.
{
"menu": [
{
"category": "cocktails",
"product": [
{
"id": "123",
"name": "test1"
},
{
"id": "456",
"name": "test2"
}
]
},
{
"category": "vodka",
"product": [
{
"id": "789",
"name": "test3"
},
{
"id": "901",
"name": "test4"
}
]
}
]
}
Now, I want to update id : 123 with name = "test5" , How to update this nested array object
in firebase database in android?
I tried following but it update/add Entire menu array not perticular product array object.
I just want to update in product array object.
Following are the code that i tried, but it update all menu array.
val listPorduct = ArrayList<HashMap<String, Any>>()
for ((i, product) in productList.withIndex()) {
var productMap = HashMap<String, Any>()
productMap = hashMapOf(
"id" to "123",
"name" to "test5")
listPorduct.add(productMap)
}
val map = hashMapOf<String, Any>()
map.put("category", list[position].category)
map.put("product", listPorduct)
repository.getTabs().update("menu", FieldValue.arrayUnion(map))
if i am try with
repository.getTabs().update("menu/product", FieldValue.arrayUnion(map))
or
repository.getTabs().update("menu.product", FieldValue.arrayUnion(map))
then getting issue / or dot not allowed. i have latest firebase gradle file.
How update particular position of product object?
Please anyone can help me to solve out this?
Image of firebase database.
The solution to this problem can be found in my answer from the following post:
firestore -- Add element in a field in hashmap
There is a slightly smaller difference. However, for your use-case, please note that the document that you gave us as an example contains a property named menu which is of type array. In this array, you have added some objects that contain two properties, a String property named category and an array named product. This array contains in term two other properties, one is the id and the other one is the name, both being of type String.
None of those arrays can be updated using FieldValue.arrayUnion(map). The only option that you have is to get the menu array as a list of HashMap<String, Any>. Once you have this map, you can simply iterate through it and get the desired data.
So to update the value of a property inside a Map, you should first get the map, do the changes and put it back. In the end, after you've done all the necessary changes, write the document back to Firestore.
Edit:
According to your comment:
In my case I have more than 5000 records in a list with a nested array.
It's hard to believe that you can nest 5000 elements into a single document because it might not fit since the maximum size for a document is 1 MiB (1,048,576 bytes). Please see usage and limits. So nesting 5000 objects into an array isn't a solution at all. In this case, the single solution that you have is to use a sub-collection and add each item from the array as a document. In this case, there are no limitations.
As a conclusion, the only highly scalable way to store an unknown large list of items is using documents in a collection. Array type fields do not scale for lists the are constantly growing because the items will eventually exceed that (1 MiB), which will obviously cause problems in the future.

Android Firebase : Update/Add record in Nested Array data

I am stuck in one case of firebase operation in android.
My Requirement :
I have table Named : "Values"
This value table contains following type of data.
{
"menu": [
{
"category": "cocktails",
"product": [
{
"id": "123",
"name": "test1"
},
{
"id": "456",
"name": "test2"
}
]
},
{
"category": "vodka",
"product": [
{
"id": "789",
"name": "test3"
},
{
"id": "901",
"name": "test4"
}
]
}
]
}
Now, I want to update id : 123 with name = "test5" , How to update this nested array object
in firebase database in android?
I tried following but it update/add Entire menu array not perticular product array object.
I just want to update in product array object.
Following are the code that i tried, but it update all menu array.
val listPorduct = ArrayList<HashMap<String, Any>>()
for ((i, product) in productList.withIndex()) {
var productMap = HashMap<String, Any>()
productMap = hashMapOf(
"id" to "123",
"name" to "test5")
listPorduct.add(productMap)
}
val map = hashMapOf<String, Any>()
map.put("category", list[position].category)
map.put("product", listPorduct)
repository.getTabs().update("menu", FieldValue.arrayUnion(map))
if i am try with
repository.getTabs().update("menu/product", FieldValue.arrayUnion(map))
or
repository.getTabs().update("menu.product", FieldValue.arrayUnion(map))
then getting issue / or dot not allowed. i have latest firebase gradle file.
How update particular position of product object?
Please anyone can help me to solve out this?
Image of firebase database.
The solution to this problem can be found in my answer from the following post:
firestore -- Add element in a field in hashmap
There is a slightly smaller difference. However, for your use-case, please note that the document that you gave us as an example contains a property named menu which is of type array. In this array, you have added some objects that contain two properties, a String property named category and an array named product. This array contains in term two other properties, one is the id and the other one is the name, both being of type String.
None of those arrays can be updated using FieldValue.arrayUnion(map). The only option that you have is to get the menu array as a list of HashMap<String, Any>. Once you have this map, you can simply iterate through it and get the desired data.
So to update the value of a property inside a Map, you should first get the map, do the changes and put it back. In the end, after you've done all the necessary changes, write the document back to Firestore.
Edit:
According to your comment:
In my case I have more than 5000 records in a list with a nested array.
It's hard to believe that you can nest 5000 elements into a single document because it might not fit since the maximum size for a document is 1 MiB (1,048,576 bytes). Please see usage and limits. So nesting 5000 objects into an array isn't a solution at all. In this case, the single solution that you have is to use a sub-collection and add each item from the array as a document. In this case, there are no limitations.
As a conclusion, the only highly scalable way to store an unknown large list of items is using documents in a collection. Array type fields do not scale for lists the are constantly growing because the items will eventually exceed that (1 MiB), which will obviously cause problems in the future.

Firestore query where map contains string

Data structure:
houses (collection)
name (string)
users (map)
90c234jc23 (map)
percentage: 100% (string/number)
Rules:
allow read: request.auth.uid in resource.data.users;
The problem is when I try to query houses which user owns:
FirebaseFirestore.getInstance().collection(House.COLLECTION)
// .whereArrayContains(House.USERS_FIELD, currentUser.getUid()) // does not work
.whereEqualTo("users." + currentUser.getUid(), currentUser.getUid()) // does not work either
.get()
No result are returned.
You cannot perform this type of query in firestore as there is no 'map-contains-key' operator. However, there are very simple workarounds for implementing this by making slight adjustments to your datastructure.
Specific Solution
Requirement: For this solution to work, each map value has to be uniquely identifyable in a firestore query, meaning it cannot be a map or an array.
If your case meets the listed requirements, you can go with #Dennis Alund's solution which suggests the following data structure:
{
name: "The Residence",
users: {
uid1: 80,
uid2: 20
}
}
General Solution
If your map values are maps or arrays, you need to add a property to each value which will be constant across all created values of this type. Here is an example:
{
name: "The Residence",
users: {
uid1: {
exists: true,
percentage: 80,
...
},
uid2: {
exists: true,
percentage: 20,
...
},
}
}
Now you can simply use the query:
_firestore.collection('houses').whereEqualTo('users.<uid>.exists', true)
Edit:
As #Johnny Oshika correctly pointed out, you can also use orderBy() to filter by field-name.
You can use orderBy to find documents where map contains a certain key. Using this example document:
{
"users": {
"bob": {},
"sam": {},
}
}
.orderBy('users.bob') will only find documents that contain users.bob.
This query is not working because your users field is a map and not an array.
.whereArrayContains(House.USERS_FIELD, currentUser.getUid())
This query
.whereEqualTo("users." + currentUser.getUid(), currentUser.getUid())
is not working because your map value for users.<uid> is a string that says percentage: xx% and that statement is testing if percentage: xx% === <uid>, which is false.
And that strategy will be problematic since you can not do queries to find items that "are not null" or "strings not empty", etc.
I'm assuming that the percentage is the user's ownership in the house (?). If so, you might have better luck in trying to structure your house document data like this if you want to maintain the same structure of document as in your question
{
name: "The Residence",
users: {
uid1: 80,
uid2: 20
}
}
That will allow you to do a query such as
.whereGreaterThan("users." + currentUser.getUid(), 0)
to find users that has some shares of ownership in that house.
But a fair bit of warning, as soon as you need composite indexes you will start having problems to maintain that structure. You might instead want to consider storing an array of users that owns that house for ease of querying.

FlashLight/Elastic Search Get the all fields of the resultant record

I am using flashlight which provides a solution to make content search over firebase database and i am facing a problem that I have a database consists of posts where any post consists of:-
{
title : "",
Body :""
TimeStamp :""
Author :""
}
and in the config.js, I defined my paths as follow
exports.paths = [
{
path : "posts",
index: "firebase",
type : "thing",
fields: ['title']
}}
the result of the search contains:-
_source {
title : ""
}
How do i make the ES to return all the post document fields not only the "title" field?
fields:['title'] means that title is the only field indexed. The fields: parameter says "index these properties and these properties only" Basically, you are indexing only the title property, so there's nothing else to be returned when querying type thing.
To return your whole post object, simply remove the fields: property when defining your path. This will default to index everything in your https://<instance>.firebaseio.com/posts path.

Categories

Resources