Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
My application will have multiple users and they will be feeding complaints and getting back complaint ID's.
I have created a separate document which stores the latest incremental values, every time a complaint is saved, it firsts gets the maximum number from that document , increments it with + 1 and saves that complaint ID in new complaint document.
Users will be around 8, is this possible that this autoincrement number can be duplicated?
Just in case, attaching screenshot of console and copying my code
get_ticket_id(){
//1. check if the doc exists
var docRef = firebase.firestore().collection('complaints').doc("autoincrement");
docRef.get().then(async doc=> {
if (doc.exists) {
this.ticketnumber = doc.data().number + 1;
await firebase.firestore().collection("complaints").doc("autoincrement").update({
number: this.ticketnumber,
})
.then(async doc=> {
await this.CreateNewComplaint();
})
} else {
Am I following the best practice? Please note: I am not using increment option
const increment = firebase.firestore.FieldValue.increment(1);
number: increment
Is it safer to use increment ?
Edit 1
Use transactions as guided, I am trying to fetch the value of newIncId in a variable but it says, code unreachable on this code of line this.ticketnumber = newIncId; // code uncreable.
var docRef = firebase.firestore().collection('complaints').doc("autoincrement");
firebase.firestore().runTransaction(transaction=> {
return transaction.get(docRef).then(incDoc=> {
//if no value exist, assume it as 0 and increase to 1
var newIncId = (incDoc.data().number || 0) + 1;
transaction.update(docRef, { number: incDoc });
return newIncId;
this.ticketnumber = newIncId; // code uncreable
});
}).then(function(newIncId) {
...//some code
Edit 2
Giving this error:
Function Transaction.update() called with invalid data. Unsupported field value: a custom t object (found in field number)
Attaching screenshot of console
For events like these you should consider Firebase transactions which prevent unwanted behaviours like multiple concurrent overwrites/updates.
final DocumentReference sfDocRef = db.collection("complaints").document("autoincrement");
db.runTransaction(new Transaction.Function<Void>() {
#Override
public Void apply(Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot snapshot = transaction.get(sfDocRef);
int newId = snapshot.getInt("number") + 1;
transaction.update(sfDocRef, "number", newPopulation);
// Success
return null;
}
}).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "Transaction success!");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Transaction failure.", e);
}
});
I've copied the code directly from the firebase documentation, so haven't properly tested this.
You could read more about transactions here: https://firebase.google.com/docs/firestore/manage-data/transactions#java
Updated to use transaction and get the new number:
var docRef = firebase.firestore().collection('complaints').doc("autoincrement");
db.runTransaction(function(transaction) {
return transaction.get(docRef).then(function(incDoc) {
//if no value exist, assume it as 0 and increase to 1
var newIncId = (incDoc.data().number || 0) + 1;
transaction.update(docRef, { number: newIncId });
return newIncId;
});
}).then(function(newIncId) {
//`newIncId` This is your new number incremented
//use newIncId here
this.ticketnumber = newIncId;
console.log("New autoincremented number ", newIncId);
}).catch(function(err) {
// Catch block to get any error
console.error(err);
});
See doc https://firebase.google.com/docs/firestore/manage-data/transactions#passing_information_out_of_transactions
Why you're not using number: firebase.firestore.FieldValue.increment(1);?
This use transactions internally. But be aware that firestore document have the write limit of 1/sec. You can use RTDB if you expect to write more than once.
Related
I have a problem to understand a chained "RXJava-Retrofit" API call. I got inspired by this and implement this class named ObservationLoader to load the data from the API bucket per bucket. When the end of data is reached the API sends a endOfRecords=true:
public Observable<PageObject<Observation>> getAllObservationDataByRegion(long taxonKey,
String regionId) {
final PublishSubject<PageObject<Observation>> subject = PublishSubject.create();
return subject.doOnSubscribe(disposable -> {
this.getData(taxonKey, regionId, 0).subscribe(subject);
})
.doOnNext(observationPageObject -> {
if (observationPageObject.isEndOfRecords()) {
// -> list is completely loaded
subject.onComplete();
} else {
int nextOffset = observationPageObject.getOffset() + 1;
this.getData(taxonKey, regionId, null, nextOffset).subscribe(subject);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private Observable<PageObject<Observation>> getData(long id,
String regionId,
int offset) {
// Get your API response value
return this.api.getObservations(id, regionId, ObservationLoader.PAGE_LIMIT, offset);
}
In my Android fragment HomeFragment I subscribe to the ObservationLoader:
ObservationLoader loader = new ObservationLoader(this.getApi());
Observable<PageObject<Observation>> observable = loader
.getAllObservationDataByRegion(this.getSelectedSpecies(), this.getSelectedRegion());
observable.subscribe(new Observer<PageObject<Observation>>() {
#Override
public void onSubscribe(Disposable d) {
Log.i(TAG, "ON_SUBSCRIBE");
}
#Override
public void onNext(PageObject<Observation> observationPageObject) {
Log.i(TAG, "ON_NEXT");
}
#Override
public void onError(Throwable e) {
Log.i(TAG, "ERROR = " + e.getMessage());
}
#Override
public void onComplete() {
Log.i(TAG, "COMPLETED");
}
});
I can see that the onSubscribe() and doOnSubscribe() are called and even the getData() is reached. I assume the API is responding correctly (a previous attempt attempt with recursion worked fine). But I never reached the doOnNext function. The observer goes straight to onComplete() and no data is received. What could be the reason?
When doOnSubscribe runs, the doesn't see any consumers yet so if getData is synchronous, there won't be any first results to trigger further results. Also if getData ends, it will complete the setup so the next getData call in doOnNext will push to an already terminated subject, ingoring all data.
You'll need a differently organized feedback loop:
// we loop back the nextOffset, in a thread-safe manner
Subject<Integer> subject = PublishSubject.<Integer>create()
.toSerialized();
// bootstrap with 0 and keep open for more offsets
subject.mergeWith(Observable.just(0))
// get the data for the current offset
.concatMap(nextOffset -> getData(taxonKey, regionId, nextOffset)
.subscribeOn(Schedulers.io())
)
// if the response is end of records, stop
.takeWhile(observationPageObject -> !observationPageObject.isEndOfRecords())
// otherwise not end of records, feedback the new offset
.doOnNext(observationPageObject ->
subject.onNext(observationPageObject.getOffset() + 1)
)
// get the data on the main thread
.observeOn(AndroidSchedulers.mainThread());
I want to perform a compound query in firestore where I would like to get all documents with field bloodgroup equal to A+ and with field createdBy not equal to email. This email is that of the logged in user. When I perform the query I get NullPointerException. How to perform the query correctly 021-07-24 19:50:24.746 17550-17550/com.example.bloodbankcompany E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.bloodbankcompany, PID: 17550 java.lang.NullPointerExceptionatcom.example.bloodbankcompany.UserlistActivity$EventChangeListener3$1.onEvent(UserlistActivity.kt:217) I am storing the document snapshot inside the userArrayList array. Without the whereNotEqualTo query I am getting output where my documents get listed in recyclerview.
private fun EventChangeListener2(){
val sharedPreferences1 = getSharedPreferences("email", Context.MODE_PRIVATE)
val email: String? = sharedPreferences1.getString("email","null")?.trim()
Toast.makeText(this, "ssrae$email", Toast.LENGTH_SHORT ).show()
mFireStore.collection("applicationForm").whereNotEqualTo("createdBy",email).whereEqualTo("bloodgroup","A+").addSnapshotListener(object : EventListener<QuerySnapshot>{
override fun onEvent(value: QuerySnapshot?, error: FirebaseFirestoreException?) {
if (error!= null){
Log.e("firestore error", error.message.toString())
}
for(dc: DocumentChange in value?.documentChanges!!){
if (dc.type== DocumentChange.Type.ADDED){
userArrayList.add(dc.document.toObject(User1::class.java))
var number=userArrayList
var number1 =userArrayList
}
// Toast.makeText(applicationContext,userArrayList.toString(), Toast.LENGTH_SHORT).show()
}
myAdapter.notifyDataSetChanged()
}
})
}
Well, I have edited a little bit of your code, if it still doesn't work add a comment.
Also, an explanation about changes is commented below.
private fun EventChangeListener2() {
val sharedPreferences1 = getSharedPreferences("email", Context.MODE_PRIVATE)
val email: String? = sharedPreferences1.getString("email", "null")?.trim()
Log.d("firestore email", email.toString())
Toast.makeText(this, "ssrae$email", Toast.LENGTH_SHORT).show()
// try and catch will avoid your app to crash.
try {
//ref
var ref = mFireStore.collection("applicationForm")
.whereEqualTo("bloodgroup", "A+")
/**
* I believe since your email is of type nullable and there may be
* maybe a chance that email is null and is given to whereNotEqualTo
* I am just making an assumption here since I don't know what you
* recieve from sharedPreferences and whether it is null or not
*
* So, what I have done here is,
* firstly, I have split the firestore call into 2 parts and
* Secondly, I have a null-check for email, if it is
* not-null ref will also include this query
*
*/
//null Check for email
if (email != null) ref = ref.whereNotEqualTo("createdBy", email)
// Snapshot Listener
ref.addSnapshotListener(object : EventListener<QuerySnapshot> {
override fun onEvent(value: QuerySnapshot?, error: FirebaseFirestoreException?) {
if (error != null) {
Log.e("firestore error", error.message.toString())
}
for (dc: DocumentChange in value?.documentChanges!!) {
if (dc.type == DocumentChange.Type.ADDED) {
userArrayList.add(dc.document.toObject(User1::class.java))
var number = userArrayList
var number1 = userArrayList
}
// Toast.makeText(applicationContext,userArrayList.toString(), Toast.LENGTH_SHORT).show()
}
myAdapter.notifyDataSetChanged()
}
})
} catch (e: Exception) {
Log.e("firestore error", "Error", e)
}
}
EDIT:
According to firebase docs,
https://firebase.google.com/docs/firestore/query-data/indexing#exemptions
If you attempt a compound query with a range clause that doesn't map to an existing index, you receive an error. The error message includes a direct link to create the missing index in the Firebase console.
I am currently attemping to build a custom Editor tool for Unity that utilizes the Firebase Realtime Database. This tool would allow someone to right-click on a scene asset in the inspector and select to 'lock' or 'unlock' the scene. Within our Firebase Database, this locking is represented by a dictionary, with each scene name as a key and each value being either "locked" or "unlocked". This funcitonality will be expanded later, but for now, I'm just trying to get things set up so that I can actually connect to and use the Firebase Realtime Database.
I had looked at the Firebase Quickstart Unity project for the Realtime Database (the one that functions like a leaderboard) and saw that it worked fine. I could replace the database URL in the project with the URL for my app's database, and when I entered in values, they appeared inside my realtime database.
So, I based the code for my custom editor script on the code from the quickstart. In fact, I copy-pasted most of it. I will post the script itself, and then describe the errors I receive, as well as the lines that give the errors:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Firebase;
using Firebase.Unity.Editor;
using Firebase.Database;
[CustomEditor(typeof(SceneAsset))]
[ExecuteInEditMode]
public class SceneLockingEditor : Editor
{
static string sceneName;
DependencyStatus dependencyStatus = DependencyStatus.UnavailableOther;
protected virtual void OnEnable()
{
Debug.Log("OnEnable Called");
sceneName = target.name;
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
dependencyStatus = task.Result;
if (dependencyStatus == DependencyStatus.Available)
{
InitializeFirebase();
}
else
{
Debug.LogError(
"Could not resolve all Firebase dependencies: " + dependencyStatus);
}
});
}
// Initialize the Firebase database:
protected virtual void InitializeFirebase()
{
Debug.Log("Initializing Firebase");
FirebaseApp app = FirebaseApp.DefaultInstance;
app.SetEditorDatabaseUrl(CENSORING MY DATABASE SORRY);
if (app.Options.DatabaseUrl != null) app.SetEditorDatabaseUrl(app.Options.DatabaseUrl);
}
static TransactionResult SceneLockTransaction(MutableData mutableData)
{
List<object> sceneLocks = mutableData.Value as List<object>;
if (sceneLocks == null)
{
sceneLocks = new List<object>();
}
if(mutableData.ChildrenCount > 0)
{
//Look at every child in the scene locks directory.
foreach (var child in sceneLocks)
{
Debug.Log("Checking next child.");
if (!(child is Dictionary<string, object>))
continue;
//If we find the scene we're looking for...
Debug.Log("Checking if the scene has the name we want");
foreach(string key in ((Dictionary<string, object>)child).Keys)
{
Debug.Log("Key: " + key);
}
if(((Dictionary<string, object>)child).ContainsKey(sceneName))
{
string childLockStatus = (string)((Dictionary<string, object>)child)["lockStatus"];
//If the scene is already locked, just abort.
if (childLockStatus == "locked")
{
Debug.Log("Scene is already locked. Abort.");
return TransactionResult.Abort();
}
else
{
Debug.Log("Scene existed in the database and was not locked. Locking it.");
// If the scene existed in the database but was not locked, we will lock it.
((Dictionary<string, object>)child)[sceneName] = "locked";
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
}
}
}
Debug.Log("Scene did not exist in the database. Adding it as locked.");
// If the scene didn't exist in the database before, we will add it as locked.
Dictionary<string, object> newSceneLock = new Dictionary<string, object>();
newSceneLock[sceneName] = "locked";
sceneLocks.Add(newSceneLock);
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
static TransactionResult SceneUnlockTransaction(MutableData mutableData)
{
List<object> sceneLocks = mutableData.Value as List<object>;
if (sceneLocks == null)
{
sceneLocks = new List<object>();
}
if (mutableData.ChildrenCount > 0)
{
//Look at every child in the scene locks directory.
foreach (var child in sceneLocks)
{
Debug.Log("Checking next child.");
if (!(child is Dictionary<string, object>))
continue;
//If we find the scene we're looking for...
Debug.Log("Checking if the scene has the name we want");
foreach (string key in ((Dictionary<string, object>)child).Keys)
{
Debug.Log("Key: " + key);
}
if (((Dictionary<string, object>)child).ContainsKey(sceneName))
{
string childLockStatus = (string)((Dictionary<string, object>)child)["lockStatus"];
//If the scene is already locked, just abort.
if (childLockStatus == "unlocked")
{
Debug.Log("Scene is already unlocked. Abort.");
return TransactionResult.Abort();
}
else
{
Debug.Log("Scene existed in the database and was locked. Unlocking it.");
// If the scene existed in the database but was not locked, we will lock it.
((Dictionary<string, object>)child)[sceneName] = "unlocked";
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
}
}
}
Debug.Log("Scene did not exist in the database. Adding it as unlocked.");
// If the scene didn't exist in the database before, we will add it as locked.
Dictionary<string, object> newSceneLock = new Dictionary<string, object>();
newSceneLock[sceneName] = "unlocked";
sceneLocks.Add(newSceneLock);
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
static public void AddSceneLock()
{
Debug.Log("Attempting to add scene lock to database.");
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("SceneLocks");
Debug.Log("Running Transaction...");
// Use a transaction to ensure that we do not encounter issues with
// simultaneous updates that otherwise might create more than MaxScores top scores.
reference.RunTransaction(SceneLockTransaction)
.ContinueWith(task => {
if (task.Exception != null)
{
Debug.Log(task.Exception.ToString());
}
else if (task.IsCompleted)
{
Debug.Log("Transaction complete.");
}
});
}
static public void RemoveSceneLock()
{
Debug.Log("Attempting to add scene lock to database.");
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("SceneLocks");
Debug.Log("Running Transaction...");
// Use a transaction to ensure that we do not encounter issues with
// simultaneous updates that otherwise might create more than MaxScores top scores.
reference.RunTransaction(SceneUnlockTransaction)
.ContinueWith(task => {
if (task.Exception != null)
{
Debug.Log(task.Exception.ToString());
}
else if (task.IsCompleted)
{
Debug.Log("Transaction complete.");
}
});
}
[MenuItem("CONTEXT/SceneAsset/Lock Scene", false, 0)]
public static void LockScene()
{
Debug.Log("LockScene Called for scene " + sceneName + ".");
AddSceneLock();
}
[MenuItem("CONTEXT/SceneAsset/Unlock Scene", false, 0)]
public static void UnlockScene()
{
Debug.Log("UnlockScene Called for scene " + sceneName + ".");
RemoveSceneLock();
}
}
The errors always come from this line:
FirebaseDatabase.DefaultInstance.GetReference("SceneLocks");
Any line that has to do with "FirebaseDatabase.DefaultInstance" will throw one of the following two errors
Error 1:
InvalidOperationException: SyncContext not initialized.
Firebase.Unity.UnitySynchronizationContext.get_Instance ()
Firebase.Platform.PlatformInformation.get_SynchronizationContext ()
Firebase.FirebaseApp.get_ThreadSynchronizationContext ()
Firebase.Database.DotNet.DotNetPlatform+SynchronizationContextTarget..ctor ()
Firebase.Database.DotNet.DotNetPlatform.NewEventTarget (Firebase.Database.Internal.Core.Context c)
Firebase.Database.Internal.Core.Context.EnsureEventTarget ()
Firebase.Database.Internal.Core.Context.InitServices ()
Firebase.Database.Internal.Core.Context.Freeze ()
Firebase.Database.Internal.Core.RepoManager.CreateLocalRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.Internal.Core.RepoManager.CreateRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.FirebaseDatabase.EnsureRepo ()
Firebase.Database.FirebaseDatabase.get_RootReference ()
SceneLockingEditor.OnInspectorGUI () (at Assets/Bitloft/SCRIPTS/Editor/SceneLockingEditor.cs:37)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor[] editors, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1242)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
Error 2:
Exception: Custom Run loops are not supported!
Firebase.Database.Internal.Core.Context.GetExecutorService ()
Firebase.Database.Internal.Core.Context.GetConnectionContext ()
Firebase.Database.Internal.Core.Context.NewPersistentConnection (Firebase.Database.Internal.Connection.HostInfo info, IDelegate delegate_)
Firebase.Database.Internal.Core.Repo..ctor (Firebase.Database.Internal.Core.RepoInfo repoInfo, Firebase.Database.Internal.Core.Context ctx, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.Internal.Core.RepoManager.CreateLocalRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.Internal.Core.RepoManager.CreateRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.FirebaseDatabase.EnsureRepo ()
Firebase.Database.FirebaseDatabase.get_RootReference ()
SceneLockingEditor.OnInspectorGUI () (at Assets/Bitloft/SCRIPTS/Editor/SceneLockingEditor.cs:37)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor[] editors, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1242)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
It's always one error or the other, and I can't determine what causes one error to appear rather than the other. Both errors stop whatever operation I'm trying to do on the database, which means I can't interact with my database at all.
I've taken a look at the quickstart projects and watched some videos of people setting up Firebase to work with their projects, and I can't seem to determine what I have messed up during the process. I have imported my google-services.json into the unity project. The quickstart projects worked just fine interacting with my database. It's just this particular script that won't work. I cannot find any mention of these two errors anywhere on Google. I even contacted the official Firebase support and they couldn't give me any advice on what the errors mean or what could be causing them.
I considered one problem might be in my initialization function. Instead of doing:
FirebaseApp app = FirebaseApp.DefaultInstance;
I figured that maybe I am supposed to use FirebaseApp.Create() with a custom name passed in, but that resulted in the same errors being thrown on the same line. I am at a loss for how to proceed with this problem. I don't know of anyone else who has had these particular errors, and I've done very much experimentation with different ways to access the database over the past several days. If anybody has an idea of what I am doing wrong here, or what causes these errors (and, how to fix them), I would really appreciate it.
At first you should initialize firebase with new instance of FirebaseApp with unique name. I do it like this:
FirebaseApp firebaseApp = FirebaseApp.Create(
FirebaseApp.DefaultInstance.Options,
"FIREBASE_EDITOR");
The second is setup references (DatabaseReference, StorageReference etc.) with this firebaseApp instance and use it only after FirebaseApp.CheckAndFixDependenciesAsync()
Overall code will look like this:
public static void Initialize(bool isEditor = false)
{
if (isEditor)
{
FirebaseApp firebaseApp = FirebaseApp.Create(
FirebaseApp.DefaultInstance.Options,
"FIREBASE_EDITOR");
firebaseApp.SetEditorDatabaseUrl("https://project.firebaseio.com/");
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
if (task.Result == DependencyStatus.Available)
{
database = FirebaseDatabase.GetInstance(firebaseApp).RootReference;
storage = FirebaseStorage.GetInstance(firebaseApp).RootReference;
auth = FirebaseAuth.GetAuth(firebaseApp);
}
else
{
Debug.LogError(
"Could not resolve all Firebase dependencies: " + task.Result);
}
});
}
else
{
FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("https://project.firebaseio.com/");
database = FirebaseDatabase.DefaultInstance.RootReference;
storage = FirebaseStorage.DefaultInstance.RootReference;
auth = FirebaseAuth.DefaultInstance;
}
IsInitialized = true;
}
I had the same errors. I spent several hours solving this and it worked for me
I have an int named "length" in my code and I am trying to change it's value from a field in my Cloud Firestore:
int length;
_handlePressed(context) {
DocumentReference postReference = Firestore.instance.collection(ISBN).document(post);
postReference.get().then((datasnapshot){
if(datasnapshot.exists) {
length = datasnapshot.data["length"];
print(length.toString());
}
});
}
The field "length" is stored as a Number Type in my Firestore.
The problem is that the print operation does not execute and printing length elsewhere shows null in the console. What am i missing?
First, make sure that _handlePressed is really called then update the state with setState when using a StatefulWidget:
int length;
_handlePressed(context) {
DocumentReference postReference = Firestore.instance.collection(ISBN).document(post);
postReference.get().then((datasnapshot){
if(datasnapshot.exists) {
setState(()
length = datasnapshot.data["length"];
);
print(length.toString());
} }); }
Does Firebase supports grouped counting?
I would like to get counting for specific key grouped by the value.
Example of my data structure:
"playbackPosition" : {
"-JZ2c7v-imQXaEx2c4Cs" : {
"data" : "0"
},
"-JZ2cDta73UGAgwIOejL" : {
"data" : "25"
},
"-JZ2cJutDZu-7quLpLI2" : {
"data" : "50"
},
"-JZ2cO-Me0nGK5XLTwd-" : {
"data" : "75"
},
"-JZ2cSh-XnstYxbE_Zad" : {
"data" : "100"
},
"-JZ2cWxya0-kmTPCze4u" : {
"data" : "0"
},
"-JZ2c_wX-ODv43TKXkNG" : {
"data" : "25"
}
}
Required results based on the data key :
0 => 2
25 => 2
50 => 1
75 => 1
100 => 1
And of course I must consider that it will have thousands of children's, not only 7...
Thanks ahead!
EDIT
Deeper explanation of the app and the problem we want to solve.
We have video scripts which runs on different websites, each video session (a user session) sends events and data, you can see an example here, check the Network tab - https://secure.bwebi.co/videostir/regular.html
Our goal is to collect this data and create an analytics real time dashboard with few charts and graphs.
You can see our current data structure here
Example for graphs we need:
Completion rate
General - Bar graph showing overall number of views per clip duration pre defined periods.
Filters - date (start/end), unique/all, All urls / specific urls (embed on), All configurations / specific configurations/ ignore silent.
X axis - groups 0-25, 25-50,50-75,75-99, 100
Y axis - number of views
Views per day (with completion rate)
General - Multi lines graph showing number of views per day per duration periods.
Filters - date (start/end), unique/all, All urls / specific urls (embed on), All configurations / specific configurations / ignore silent.
X axis - Time in days
Y axis - Number of views
Lines for:
Total daily views
Daily views with 100% duration
Daily views with 75-99% duration
Daily views with 50-75% duration
Daily views with 25-50% duration
Daily views with 0-25% duration
Hope it's more clear now!
Group by is a SQL function. The reason SQL can't do real-time data is because this sort of method does not scale. Mongo provides similar functionality, but once again, it doesn't scale. You may notice a pattern here of why Firebase does not provide this sort of query function.
It would be extremely helpful if you provided some context of what you're actually attempting to accomplish here, what the rules of the app are, and what approaches you've ruled out, rather than just your presupposed solution of group by. There are probably other, possibly better, alternatives. See the XY problem.
Here are a couple generic alternatives derived by making sweeping assumptions about your use case.
Store the totals
This is the most scalable solution. Store your data as follows:
/playbacks/$id/<playback data>
/group_totals/$group/<count>
When writing to playbacks, also update the count for the appropriate group:
var fb = new Firebase(URL);
function addPlayback(rec) {
var ref = fb.child('playbacks').push(rec, function(err) {
if( err ) throw err;
incrementCount(rec.data);
});
}
function incrementCount(count) {
fb.child('group_totals/' + count).transaction(function(currentVal) {
return (currentVal||0)+1;
});
}
Now when you want to get the total for a group, you can simply look up the value at group_totals/$group. Similarly, you can store ids for records that belong to each group and utilize that index to grab only the records for a given group.
Use priorities to fetch and group
A simpler approach would be to give each record a priority based on the group/data value.
var fb = new Firebase(URL);
function addPlayback(rec) {
rec['.priority'] = rec.data;
var ref = fb.child('playbacks').push(rec, function(err) {
if( err ) throw err;
});
}
Now to grab a set of records for a given group:
var fb = new Firebase(URL);
function getGroup(groupValue, callback) {
fb.child('playbackPosition').startAt(groupValue).endAt(groupValue).once('value', callback);
}
function logGroupCount(groupValue, callback) {
getGroup(groupValue, function(snap) {
console.log('there are ' + snap.numChildren() + ' items in group ' +groupValue);
});
}
A am not professional programmer, I am just learning.
Here is the piece of code I came up with when I wanted to group my query results:
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
progressBar.setVisibility(View.VISIBLE);
tv_info.setText("Please wait ... ");
Query query = collectionRef.orderBy("timestamp", Query.Direction.DESCENDING);
query.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
String dataInfo = "";
int arrayLength = 1;
List <String> UsedNames = new ArrayList<String>();
String someName = "...";
String oneRow = "";
int orderNumber = -1;
int count = 0;
for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
OneRecord oneRecord = documentSnapshot.toObject(OneRecord.class);
someName = oneRecord.getSomeName();
if (UsedNames.contains(someName)) {
// Log.i("test" , "Array Contains ");
} else {
orderNumber += 1;
UsedNames.add(someName);
}
}
List list = queryDocumentSnapshots.toObjects(OneRecord.class);
for (String someString : UsedNames) {
int counter = 0;
for (int i = 0; i < list.size(); i++) {
OneRecord oneRecord = (OneRecord) list.get(i);
String name = oneRecord.getName();
if (someString.equals(name)) {
counter += 1;
}
}
Log.i("test" , "Array: " + someString + " : " + counter);
count = count +1;
dataInfo = dataInfo + someString + " : " + counter + "\n";
}
Log.i("test" , "Used length: " + UsedNames.size());
progressBar.setVisibility(View.GONE);
tv_info.setText(dataInfo);
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
progressBar.setVisibility(View.GONE);
tv_info.setText("Could not query last records: " + e.getMessage());
}
});
}
});
Unfortunately I did not figure out how to sort them in DESCENDING or ASCENDING order