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.
Related
On My unity app, inside login using email and password, I am using firebase login. My problem is when I try to call a function or execute a group code inside the task nothing happen. Not even an error. How to call a function inside this.
public void signin()
{
auth.SignInWithEmailAndPasswordAsync(email.text, password.text).ContinueWith(task => {
if (task.IsCanceled)
{
Debug.LogError("SignInWithEmailAndPasswordAsync was canceled.");
return;
}
if (task.IsFaulted)
{
Debug.LogError("SignInWithEmailAndPasswordAsync encountered an error: " + task.Exception);
return;
}
Firebase.Auth.FirebaseUser newUser = task.Result;
//========Checking Email Verified Or Not======
if(auth.CurrentUser.IsEmailVerified== true)
{
Debug.LogFormat("User signed in successfully: {0} ({1})",newUser.DisplayName, newUser.UserId);
//=================Here is the Problem this is not calling==========
MyFunction();
//==============================================
}
else
{
print("Email Not Verified");
auth.SignOut();
}
//===========
});
}
In general the most common issue with these kind of things is that most of the Unity API can only be used on the main thread.
The Task.ContinueWith is not guaranteed to be executed in the main thread but might happen in any async background thread/task
Therefore Firebase provides an extension method ContinueWithOnMainThread you should use instead, which makes sure the inner codeblock is executed on the Unity main thread.
So simply replace
auth.SignInWithEmailAndPasswordAsync(email.text, password.text).ContinueWith(task => {
with
auth.SignInWithEmailAndPasswordAsync(email.text, password.text).ContinueWithOnMainThread(task => {
make sure you have using Firebase.Extensions at the top of your file
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.
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.
Query does not contain a definition for 'once' and no accessable extension method 'once'. Maybe I am being really stupid but I am having tons of issues with this.
This is for database to check to see if a user already exists if not create a user or load their data.
DatabaseReference databaseReference = FirebaseDatabase.DefaultInstance.RootReference;
databaseReference.Child("users").OrderByChild("users").once("value", snapshot =>
{
if (snapshot.exists())
{
GetUserData(user);
}
});
The once() method only exists in the JavaScript SDK for the Firebase Realtime Database.
The equivalent in the Unity clients is GetValueAsync, as shown in the documentation on reading data once.
From there:
FirebaseDatabase.DefaultInstance
.GetReference("Leaders")
.GetValueAsync().ContinueWith(task => {
if (task.IsFaulted) {
// Handle the error...
}
else if (task.IsCompleted) {
DataSnapshot snapshot = task.Result;
// Do something with snapshot...
}
});
I am trying to receive the JSON value from the Realtime Database of Firebase using Unity.
I do the following:
FirebaseDatabase.DefaultInstance
.GetReference("Leaders").OrderByChild("score").GetValueAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
Debug.LogError("error in reading LeaderBoard");
return;
}
else if (task.IsCompleted)
{
Debug.Log("Received values for Leaders.");
string JsonLeaderBaord = task.Result.GetRawJsonValue();
callback(JsonLeaderBaord);
}
}
});
Trying to Read the CallBack :
private string GetStoredHighScores()
{
private string JsonLeaderBoardResult;
DataBaseModel.Instance.RetriveLeaderBoard(result =>
{
JsonLeaderBoardResult = result; //gets the data
});
return JsonLeaderBoardResult; //returns Null since it doesn't wait for the result to come.
}
Question is how do i wait for the callback to return the value and afterwards return the value of the JsonLeaderBoardResult.
return JsonLeaderBoardResult; //returns Null since it doesn't wait
for the result to come.
The RetriveLeaderBoard function doesn't return immediately. You can either use coroutine to wait for it or return the JsonLeaderBoardResult result via Action. Using Action make more sense in your case.
Change the string return type to void then return the result through Action:
private void GetStoredHighScores(Action<string> callback)
{
string JsonLeaderBoardResult;
DataBaseModel.Instance.RetriveLeaderBoard(result =>
{
JsonLeaderBoardResult = result; //gets the data
if (callback != null)
callback(JsonLeaderBoardResult);
});
}
Usage:
GetStoredHighScores((result) =>
{
Debug.Log(result);
});
EDIT:
That is great, but still need to do some stuff after getting the
result in `GetStoredHighScores' outside the Action, otherwise i can
get an error like: get_transform can only be called from the main
thread.
You get this error because RetriveLeaderBoard is running from on another Thread. Grab UnityThread from this post then do the callback on the main Thread with UnityThread.executeInUpdate.
Your new code:
void Awake()
{
UnityThread.initUnityThread();
}
private void GetStoredHighScores(Action<string> callback)
{
string JsonLeaderBoardResult;
DataBaseModel.Instance.RetriveLeaderBoard(result =>
{
JsonLeaderBoardResult = result; //gets the data
UnityThread.executeInUpdate(() =>
{
if (callback != null)
callback(JsonLeaderBoardResult);
});
});
}
You're seeing a classical confusion with asynchronous APIs. Since loading data from Firebase may take some time, it happens asynchronously (on a separate thread). This allows your main code to continue, while the Firebase client is waiting for a response from the server. When the data is available, the Firebase client calls your callback method with that data, so that you can process it.
It's easiest to see what this does in your code by placing a few logging statements:
Debug.Log("Before starting to load data");
FirebaseDatabase.DefaultInstance
.GetReference("Leaders").OrderByChild("score").GetValueAsync().ContinueWith(task => {
Debug.Log("Got data");
}
});
Debug.Log("After starting to load data");
When you run this code, it prints:
Before starting to load data
After starting to load data
Got data
This is probably not the order in which you expected the output. Due to the asynchronous nature of the call to Firebase, the second log line gets printed last. That explains precisely why you're seeing an empty array when you return it: by that time the data hasn't been loaded from Firebase yet, and your ContinueWith hasn't executed yet.
The solution to this is always the same: since you can't return data that hasn't loaded yet, you have to implement a callback function. The code you shared does that twice already: once for the ContinueWith of Firebase itself and one in RetriveLeaderBoard.
Any code that needs to have the up to date leaderboard, will essentially have to call RetriveLeaderBoard and do any work with the leaderboard data in its callback. For example, if you want to print the leaderboard:
DataBaseModel.Instance.RetriveLeaderBoard(result => {
Debug.Log(result);
});