Unity firestore - How to run GetSnapshotAsync() right after CheckAndFixDependenciesAsync()? - c#

using Unity, I am trying to intialize my the firestore using CheckAndFixDependenciesAsync() and getting a document from firestore using GetSnapshotAsync(). From a firebase article, it is possible to add a continuation for the task with ContinueWithOnMainThread as shown in the example here:
Debug.Log("Checking Dependencies");
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(fixTask =>
{
Assert.IsNull(fixTask.Exception);
Debug.Log("Authenticating");
var auth = FirebaseAuth.DefaultInstance;
auth.SignInAnonymouslyAsync().ContinueWithOnMainThread(authTask =>
{
Assert.IsNull(authTask.Exception);
Debug.Log("Signed in!");
var successes = PlayerPrefs.GetInt("Successes", 0);
PlayerPrefs.SetInt("Successes", ++successes);
Debug.Log($"Successes: {successes}");
auth.SignOut();
Debug.Log("Signed Out");
});
});
However, this is not the case for my project, where I want to use GetSnapshotAsync() instead to retrieve some 'level' data - everything inside the block is not running.
private void Start()
{
Debug.Log("Database awake ");
//check if all dependency is in project
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
string lvCode = "oHFxRAIpIHfT8moiYSl9";
Debug.Log("Get level :" + lvCode);
DocumentReference levelref = db.Collection("level").Document(lvCode);
levelref.GetSnapshotAsync().ContinueWithOnMainThread(querySnapshotTask =>
{
//Debug.Log here is not ran
Debug.Log("Completed? " + querySnapshotTask.Result);
Debug.Log("Faulted? " + querySnapshotTask.IsFaulted);
});
});
}
Can someone explain why is this happening to my code and possibly provide some alternatives that use the ContinueWith methods? (as opposed to await and coroutine solutions)

Author of that article here 😃
I can't quite see if you're doing anything wrong, but my guess is that maybe you're doing a db = FirebaseFirestore.DefaultInstance outside of that block (ignore this if you're doing a lazy iniitalization). Calling that before CheckAndFixDependenciesAsync could be causing your issue (CheckAndFixDependenciesAsync verifies that DefaultInstance can be called without erroring out).
Subbing out your db reference might fix it:
private void Start()
{
Debug.Log("Database awake ");
//check if all dependency is in project
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
string lvCode = "oHFxRAIpIHfT8moiYSl9";
Debug.Log("Get level :" + lvCode);
DocumentReference levelref = FirebaseFirestore.DefaultInstance.Collection("level").Document(lvCode);
levelref.GetSnapshotAsync().ContinueWithOnMainThread(querySnapshotTask =>
{
//Debug.Log here is not ran
Debug.Log("Completed? " + querySnapshotTask.Result);
Debug.Log("Faulted? " + querySnapshotTask.IsFaulted);
});
});
}
Also, pay close attention to any error logging, changing the first ContinueWith to ContinueWithOnMainThread might reveal more errors (some versions of Unity do fail to report errors in background tasks).
Finally it's worth checking that you actually do have Firestore setup with your current game. Assuming you have a google-services.json or GoogleService-Info.plist in your Assets/ directory, you can go to "Window>Firebase>Documentation":
And click "Open in Console" to jump right to your project and double check that Firestore is setup:
If you do find that changing the first ContinueWith to ContinueWithOnMainThread fixes your issue completely, it could be worth filing a bug. Generally the Firebase libraries should be thread safe, and since Firestore is in beta maybe it's a use case that was missed.

Related

Unity C# Firebase: What do I do if "task.Exception.Message" returns "one or more errors have occurred" and is not returning data?

This is my first time using Firebase with Unity. My code seems to be connected to my Firestore database but is not retrieving the data.
I also get this error message: System.Threading.Tasks.ContinuationResultTaskFromResultTask2[Firebase.Firestore.DocumentSnapshotProxy,Firebase.Firestore.DocumentSnapshot] UnityEngine.Debug:Log(Object) <>c:<savedata>b__5_0(Task1) (at Assets/Scripts/CloudFirebase.cs:60)
The corresponding line for CloudFirebase.cs:60 is Debug.Log(task); and the code normally gets stuck on is line DocumentSnapshot snapshot = task.Result;
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback()
Note: I have tried to look around for answers relating to my issue but can't seem to find any. If you have any links that relate specifically to Unity please do share.
Here's my code below:
FirebaseFirestore db;
Dictionary<string, object> user;
CollectionReference stuRef;
private bool istrue = true;
// Start is called before the first frame update
void Start()
{
Debug.Log("Start called");
Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
var dependencyStatus = task.Result;
if (dependencyStatus == Firebase.DependencyStatus.Available)
{
Debug.Log("available for use");
// Create and hold a reference to your FirebaseApp,
// where app is a Firebase.FirebaseApp property of your application class.
//app = Firebase.FirebaseApp.DefaultInstance;
db = FirebaseFirestore.DefaultInstance;
Debug.Log(db.Collection("Users").Document("hello"));
stuRef = db.Collection("Users");
savedata();
// Set a flag here to indicate whether Firebase is ready to use by your app.
}
else
{
UnityEngine.Debug.LogError(System.String.Format(
"Could not resolve all Firebase dependencies: {0}", dependencyStatus));
// Firebase Unity SDK is not safe to use here.
}
});
//db = FirebaseFirestore.DefaultInstance;
//if (istrue)
//{
// savedata();
// istrue = false;
//}
}
public void savedata()
{
stuRef.Document("hello").GetSnapshotAsync().ContinueWith(task =>
{
if (task.IsCompleted)
{
task.GetAwaiter();
if (task.IsFaulted)
{
Debug.Log(task.Exception.Message);
}
Debug.Log("succesfully added to database");
Debug.Log(task);
DocumentSnapshot snapshot = task.Result;
if (snapshot.Exists)
{
Debug.Log("snapshot exists");
//user = snapshot.ToDictionary();
// foreach (KeyValuePair<string, object> pair in user)
// {
// Debug.Log(("{0}:{1}", pair.Key, pair.Value));
// }
}
else
{
Debug.Log("snapshot does not exist");
}
}
else
{
Debug.Log("failed db");
}
});
}
A few notes, which should help you debug:
Exceptions in Task are of type AggregateException. This means that rather than printing out the exception itself with Debug.Log(task.Exception.Message);, you should iterate over the exceptions it contains (usually only 1 in the case of Firebase) and print out each internal exception.
I tend to log these using the Flatten method. Paraphrasing Microsoft's documentation:
foreach (var e in task.Exception.Flatten().InnerExceptions) {
Debug.LogWarning($"Received Exception: {e.Message}");
}
You could also use Handle. Again paraphrasing the official docs:
task.Exception.Handle(e => {
Debug.LogWarning($"Received Exception: {e.Message}");
});
I suspect that the error will become apparent once you change your code accordingly. But feel free to update the bug with the appropriate exception text if you remain stuck.
Additionally:
You shouldn't have to call Task.IsCompleted, this will always be true inside a continuation (see the docs).
You shouldn't need to call Task.GetAwaiter, this is intended for the compiler's use (although don't let me stop you if you're doing something creative).
You should replace ContinueWith with ContinueWithOnMainThread, this is a Firebase-provided extension method that ensures that continuations run on the main (Unity) thread. You'll immediately run into new and exciting exceptions if you touch almost anything in the UnityEngine namespace in your continuation.
My suspicion would be that you have an issue with security rules, but that will become more apparent once you fix your error logging.
I would make sure that the snapshot exist as in the docs:
DocumentReference docRef = db.Collection("Users").Document("hello");
DocumentSnapshot snapshot = await docRef.GetSnapshotAsync();
if (snapshot.Exists)
{
//whatever
} else
{
Console.WriteLine("Document {0} does not exist!", snapshot.Id);
}
So that the existance of the doc can be discarded as a problem.
I would also tell in the question what is the corresponding line CloudFirebase.cs:60 in your code where you obtain the error and if its not from your code, try to provide the last line called from your code where the error is produced.

Cloud Firestore GetSnapshotAsync to return a value. Unity and C#

I'm trying to create a Class to manage my Cloud Firestore requests (like any SQLiteHelper Class). However, firebase uses async calls and I'm not able to return a value to other scripts.
Here an example (bool return):
public bool CheckIfIsFullyRegistered(string idUtente)
{
DocumentReference docRef = db.Collection("Utenti").Document(idUtente);
docRef.GetSnapshotAsync().ContinueWithOnMainThread(task =>
{
DocumentSnapshot snapshot = task.Result;
if (snapshot.Exists)
{
Debug.Log(String.Format("Document {0} exist!", snapshot.Id));
return true; //Error here
}
else
{
Debug.Log(String.Format("Document {0} does not exist!", snapshot.Id));
}
});
}
Unfortunately, since Firestore is acting as a frontend for some slow running I/O (disk access or a web request), any interactions you have with it will need to be asynchronous. You'll also want to avoid blocking your game loop if at all possible while performing this access. That is to say, there won't be a synchronous call to GetSnapshotAsync.
Now there are two options you have for writing code that feels synchronous (if you're like me, it's easier to think like this than with callbacks or reactive structures).
First is that GetSnapshotAsync returns a task. You can opt to await on that task in an async function:
public async bool CheckIfIsFullyRegistered(string idUtente)
{
DocumentReference docRef = db.Collection("Utenti").Document(idUtente);
// this is equivalent to `task.Result` in the continuation code
DocumentSnapshot snapshot = await docRef.GetSnapshotAsync()
return snapshot.Exists;
}
The catch with this is that async/await makes some assumptions about C# object lifecycle that aren't guaranteed in the Unity context (more information in my related blog post and video). If you're a long-time Unity developer, or just want to avoid this == null ever being true, you may opt to wrap your async call in a WaitUntil block:
private IEnumerator CheckIfIsFullyRegisteredInCoroutine() {
string idUtente;
// set idUtente somewhere here
var isFullyRegisteredTask = CheckIfIsFullyRegistered(idUtente);
yield return new WaitUntil(()=>isFullyRegisteredTask.IsComplete);
if (isFullyRegisteredTask.Exception != null) {
// do something with the exception here
yield break;
}
bool isFullyRegistered = isFullyRegisteredTask.Result;
}
One other pattern I like to employ is to use listeners instead of just retrieving a snapshot. I would populate some Unity-side class with whatever the latest data is from Firestore (or RTDB) and have all my Unity objects ping that MonoBehaviour. This fits especially well with Unity's new ECS architecture or any time you're querying your data on a per-frame basis.
I hope that all helps!
This is how i got it to work, but excuse my ignorance as i've only been using FBDB for like a week.
Here is a snippet, I hope it helps someone.
Create a thread task extension to our login event
static Task DI = new System.Threading.Tasks.Task(LoginAnon);
Logging in anon
DI = FirebaseAuth.DefaultInstance.SignInAnonymouslyAsync().ContinueWith(result =>
{
Debug.Log("LOGIN [ID: " + result.Result.UserId + "]");
userID = result.Result.UserId;
FirebaseDatabase.DefaultInstance.GetReference("GlobalMsgs/").ChildAdded += HandleNewsAdded;
FirebaseDatabase.DefaultInstance.GetReference("Users/" + userID + "/infodata/nickname/").ValueChanged += HandleNameChanged;
FirebaseDatabase.DefaultInstance.GetReference("Users/" + userID + "/staticdata/status/").ValueChanged += HandleStatusChanged;
FirebaseDatabase.DefaultInstance.GetReference("Lobbies/").ChildAdded += HandleLobbyAdded;
FirebaseDatabase.DefaultInstance.GetReference("Lobbies/").ChildRemoved += HandleLobbyRemoved;
loggedIn = true;
});
And then get values.
DI.ContinueWith(Task =>
{
FirebaseDatabase.DefaultInstance.GetReference("Lobbies/" + selectedLobbyID + "/players/").GetValueAsync().ContinueWith((result) =>
{
DataSnapshot snap2 = result.Result;
Debug.Log("Their nickname is! -> " + snap2.Child("nickname").Value.ToString());
Debug.Log("Their uID is! -> " + snap2.Key.ToString());
//Add the user ID to the lobby list we have
foreach (List<string> lobbyData in onlineLobbies)
{
Debug.Log("Searching for lobby:" + lobbyData[0]);
if (selectedLobbyID == lobbyData[0].ToString()) //This is the id of the user hosting the lobby
{
Debug.Log("FOUND HOSTS LOBBY ->" + lobbyData[0]);
foreach (DataSnapshot snap3 in snap2.Children)
{
//add the user key to the lobby
lobbyData.Add(snap3.Key.ToString());
Debug.Log("Added " + snap3.Child("nickname").Value.ToString() + " with ID: " + snap3.Key.ToString() + " to local lobby.");
currentUsers++;
}
return;
}
}
});
});
Obviously you can alter it how you like, It does not really need loops but i'm using them to test before i compact the code into something less readable and more intuitive.

Problem with installing chocolatey files with task.run on windows 8.1

I'm tasked with creating a tool to help set up customers systems easily. I've created a function that calls a chocolatey script through powershell in c# and I use Task.run to create a new thread so it doesn't affect the UI thread, The system works fine, but I'm having problems with some computers. It's not helped that I have no access to these computers and do not know much about their system, and due to time constraints do not have access to these computers. I do know they have windows 8.1. I was given a windows 10 virtual machine to test on (which I still don't understand as it was known that this was a windows 8 problem)
Here is the code.
I know for a fact(due to the one time I was given access to these computers) that it stops on Task.Run(() => task)
Does anyone know if there are any problems with either chocolatey or Tasks on windows 8.1?
Task callTask = Task.Run(() => ExecuteAsynchronouslyAsync("chocolatey string", CheckBox box, string logName));
public async Task<PowerShellAction> ExecuteAsynchronouslyAsync(String commandStr, CheckBox box, string logName)
{
powerShellAction = new PowerShellAction();
powerShellAction.isFinished = false;
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript(commandStr); // adding the script to the powershell script.
outputCollection = new PSDataCollection<PSObject>();
outputCollection.DataAdded += OutputData;
IAsyncResult result = ps.BeginInvoke<PSObject, PSObject>(null, outputCollection);
PSDataCollection<PSObject> execRes = await Task.Factory.FromAsync(result, ps.EndInvoke);
}
return powerShellAction;
}
Working right now on trying to get a virtual machine of 8.1 to continue trying to debug myself. Any other suggestions would be welcome.
Unfortunately I cannot ensure that my suggestions are correct. The main reason is, that i can't figure out what PowerShellAction is supposed to be. I'm assuming here that PowerShell is System.Management.Automation.PowerShell.
I'm suggesting several things:
Your code does not compile for several reasons: you have no var or type-declaration on the first line of your method and the method-call would not work because of the addition string keyword. Try to avoid pasting in code like yours in the future please because it's pretty hard to rebuild your sample.
Don't bypass a UI control to an async method but use the needed value (e.g. box.IsChecked as a bool) instead.
Add ConfigureAwait(false) to your await to prevent .NET from trying to sync back to the context.
Take more care about exception handling insude of your method.
Dont' return anything if you don't need it in your method.
The code (untestet) could be something like this:
var task = Task.Run(() => ExecutePowerShellAsync("chocolatey string", box.IsChecked, "NameOfTheLog"));
public async Task<PowerShellAction> ExecutePowerShellAsync(String commandStr, bool checkBoxValue, string logName)
{
var powerShellAction = new PowerShellAction();
powerShellAction.isFinished = false;
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript(commandStr); // adding the script to the powershell script.
var outputCollection = new PSDataCollection<PSObject>();
outputCollection.DataAdded += OutputData;
IAsyncResult result = ps.BeginInvoke<PSObject, PSObject>(null, outputCollection);
PSDataCollection<PSObject> execRes = await Task.Factory.FromAsync(result, ps.EndInvoke).ContinueWith(t => {
if (t.IsFaulted)
{
System.Diagnostics.Trace.TraceError("Task faulted with exception: " + t.Exception?.Message);
}
return t.Result;
}).ConfigureAwait(false);
}
return powerShellAction;
}
I use ContinueWith in order to be able to react to any exception that might occur inside the original task.
I'm suggesting this because your description smells like you have a typical thread-lock which means the code simple does not come back due to an exception or context-syncing-problems.

Issue retrieving data from Firebase Database and saving data on unity

I am using the following code to retrieve data from Firebase database on a user with Unity3D, in our case i am getting User Level:
FirebaseDatabase.DefaultInstance
.GetReference("users").Child(userID)
.GetValueAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
Debug.LogError("Error retriving user data: " + userID);
// Handle the error...
}
else if (task.IsCompleted)
{
DataSnapshot snapshot = task.Result;
int TempUserLevel = (int)snapshot.Child("Level").Value;
//this get's an error
PlayerPrefs.SetInt(_UserLevel, TempUserLevel);
}
}
Error:
TrySetInt can only be called from the main thread. Constructors and
field initializers will be executed from the loading thread when
loading a scene.
As I understand the TASK is a new thread and not Unity Main thread. Still
I can't seem save values locally on unity, or get the value out of the TASK.
It cannot be called because continue with is a delegate and it waits for response. What I did is just made a waituntil coroutine before calling this delegate using and set a bool for instance some bool check = false.
else if(task.IsCompleted)
{
// your operation
check=true;
}
////////
IEnumerator myRoutine()
{
yield return new WaitUntil ( () => check );
// set your playerprefs.
}
Actually you could just change "ContinueWith" to "ContinueWithOnMainThread".
You can change ContinueWith to ContinueWithOnMainThread, but you will need add "using Firebase.Extensions;"
using Firebase.Extensions;
FirebaseDatabase.DefaultInstance.GetReference("users").Child(userID)
.GetValueAsync().ContinueWithOnMainThread(task =>
{
if (task.IsFaulted)
{
// Handle the error...
}
else if (task.IsCompleted)
{
DataSnapshot snapshot = task.Result;
// Do something with snapshot...
}
});
See documentation for more examples: https://firebase.google.com/docs/database/unity/retrieve-data
Potential Note: If you are following documentation for Google Sign in with Firebase and downloaded the Firebase SDK for Unity 2020, you may get an error regarding a conflict with System.Threading.Tasks (this happened to me). If anyone else gets this error, it can be dealt with by deleting or renaming the Unity.Compat and Unity.Tasks files under Unity > Assets > Parse > Plugins, but do not change or delete the files in the dotNet45 folder.

Backgroundworker Stops Working

I have a WPF project that uses background workers to interfaces with some external hardware (Test & Measure equipment, etc), write to local files, and insert data into a database as it runs. Program flow is basically sequential, the background workers in use are to keep the GUI accessible for the user, and were chosen because I haven't had issues with using them before. We take measurements, do some stuff, log, then repeat. There is a status log on the GUI that displays messages as we go.
All of this works beautifully for hours on end, however, eventually, without fail, it appears that the background worker used to write to the database never calls DoWork.
BackgroundWorker DbLogWorker = new BackgroundWorker();
...
DbLogWorker.DoWork +=
new DoWorkEventHandler(DbLogWorker_DoWork);
DbLogWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
DbLogWorker_RunWorkerCompleted);
ProcessScanData is called when data has been retrieved from one of the pieces of hardware, setting a property and firing a generic property changed event
(disclaimer: some extra code everywhere as I was investigating to see whats going on):
private void Data_DataSetChanged(object pSender, EventArgs pArgs)
{
this.Test.ProcessScanData(OsaVm.Osa.Data.DataSet);
}
...
public void ProcessScanData(SortedList<double,double> pData)
{
...
RaiseLogEvent(MessageType.SystemGeneral, "Logging to database...");
DbLogWorker.RunWorkerAsync(new AsyncDbLogArgs(CurrentChannel, tempdate,
loss1, loss2, loss3,
CurrentTemperature, CurrentPressure, CurrentRoomTemp));
}
private void DbLogWorker_DoWork(object pSender, DoWorkEventArgs pArgs)
{
AsyncDbLogArgs args = (AsyncDbLogArgs)pArgs.Argument;
string filename = string.Empty;
try
{
long datakey = Db.LogScan(CurrentChannel, args.Time,
args.Temperature, args.Pressure, args.RoomTemperature,
args.Loss1, args.Loss2, args.Loss3);
filename = args.Time.ToString(FOLDER_DATETIME_FORMAT) + "_[" + datakey.ToString() + "]";
}
catch (Exception ex)
{
filename = args.Time.ToString(FOLDER_DATETIME_FORMAT) + "_{" + (fileindex++) + "}";
}
pArgs.Result = new Tuple<AsyncDbLogArgs, string>(args, filename);
}
Symptoms:
Everything works fine for anywhere between 1 hour and ~16 hours until eventually we get to a point where we see "Logging to database..." and nothing else ever happens. No more messages, no exceptions (release build on target machine), no database entry...etc. This happens consistently.
I've been scratching my head on this for a while. Any leads will help, I have some workarounds in mind but I'd really like to know whats going on so I can avoid this in the future.
Thanks
Edited back to original code...thought the most recent would help avoid some "how do you know its not firing" questions

Categories

Resources