firebase / quickstart-unity

Firebase Quickstart Samples for Unity
https://firebase.google.com/games
Apache License 2.0
835 stars 431 forks source link

RTDB Read Data Once returns inconsistent value after Update Firebase SDK - Unity Editor. #958

Closed MuhsinEge closed 3 years ago

MuhsinEge commented 3 years ago

[REQUIRED] Please fill in the following fields:

[REQUIRED] Please describe the issue here:

I have a node on my database that named as appVersion having a value of 1. Somehow i can not read it and parse it since i updated SDK from 6.24 to 7.03 and the snapshot.value is : System.Collections.Generic.Dictionary`2[System.String,System.Object]. Then i create another node named aVersion that have the same value as appVersion. I can read the aVersion but it is problematic too. Sometimes i got the value correct as 1 and sometimes i get the old value of aVersion which is really odd to me. I'm trying to use it on editor.

Edit: I found out that if my function execution starts before these Unity Console Logs the value that i read is false(2). If it is executed after these logs prompted the value is correct(1). But i don't know how to control this behavior. image

Edit2 I have tried getting a build to my Android device and everything works just fine. Tried the invoke this function 20+ times and see no inconsistent value. I think the problem is with Unity Editor, But i can not understand why it is returning the value as 2, it was the old value of this node and i don't know if it is about it. This error happening when the editor dealing with manifest files after the execution of this function, when the Parsing Manifest (I do not know what it does actually) happens after the execution of this function it returns the value 2, dataSnap.Exist is true everything seems successfull but the returned value is wrong. I have no idea how to control that everything starts after the Parsing Manifest logs are done. I hope someone can help me.

Edit3

I realize that i can reach the appVersion as i reach to the aVersion but instead of returning inconsistent value it returns System.Collections.Generic.Dictionary`2[System.String,System.Object] and i get a parsing error. As the same like aVersion if it executed after the manifest logs it returns the value truely.

Steps to reproduce:

The nodes that i have: image

Code is below in the Relevant Code Section.

Have you been able to reproduce this issue with just the Firebase Unity quickstarts (this GitHub project)? What's the issue repro rate? (eg 100%, 1/5 etc)

I just run into this problem and did not have time to reproduce it by myself.

What happened? How can we make the problem occur? This could be a description, log/console output, etc.

My code is same as the quick start and i gave examples on below.

If you have a downloadable sample project that reproduces the bug you're reporting, you will likely receive a faster response on your issue.

Relevant Code:

The function that i use for reading data once:

    public async Task<bool> CheckAppVersion()
    {
        DataSnapshot dataSnap;
        bool check = false;
        await FirebaseDatabase.DefaultInstance.GetReference("appVersion").GetValueAsync().ContinueWith(task =>
        {
            if (task.IsFaulted)
            {
                Application.Quit();
            }
            else if (task.IsCompleted)
            {  
                Debug.Log("Checking Version");
                dataSnap = task.Result;
                Debug.Log("Exist: " + dataSnap.Exists);
                Debug.Log(dataSnap.Value);
                int appVersionFromServer =  Convert.ToInt32(dataSnap.Value);
                Debug.Log(appVersionFromServer);
                if (appVersionFromServer == appVersion)
                {
                    check = true;
                    Debug.Log("App is up to date");
                }
                else
                {
                    check = false;
                    Debug.Log("App needs to updated");
                }
            }
        });
        return check;
    }

My start function:

        await FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task =>
        {
            dependencyStatus = task.Result;  
            if (dependencyStatus == DependencyStatus.Available)
            {         
                InitializeFirebase();
            }
            else
            {       
                Debug.LogError(
                  "Could not resolve all Firebase dependencies: " + dependencyStatus);
            }
        });

Firebase app initialization :

    protected async virtual void InitializeFirebase()
    {
        FirebaseApp app = FirebaseApp.DefaultInstance;// Set Firebase App
        auth = FirebaseAuth.DefaultInstance;
        dataBaseRef = FirebaseDatabase.DefaultInstance.RootReference; 
   }

Then my check appVersion function is called.

alexames commented 3 years ago

It's possible that there is a difference in behavior between the Editor and the Android app. The implementations are separate, but they're supposed to be identical in behavior. It sounds like there might be a bug in the Desktop implementation, but it's a little bit hard to follow your reproduction steps that you gave here.

You mention a few times that the value is System.Collections.Generic.Dictionary`2[System.String,System.Object] However, that's just an opaque identifier, that doesn't tell me what's in that snapshot's value is. Can you print out the value at snapshot.GetRawJsonValue() instead of just printing the value directly?

If I had to guess what is happening, it sounds like you're attempting to retrieve the value before the database is online, so it returns a cached value from disk. If that's the case, ensure that you allow the FirebaseInitialize function to finish before running any other database queries.

chkuang-g commented 3 years ago

@MuhsinEge

To reiterate on what @alexames said, could you try to print out the DataSnapshot in Json format using code like

Debug.Log(dataSnap.GetRawJsonValue());

Also, just out of curiosity, I wonder if your issue is resolved by turning off Persistence in editor. Try to change your InitializeFirebase() to:

    protected async virtual void InitializeFirebase()
    {
        FirebaseApp app = FirebaseApp.DefaultInstance;// Set Firebase App
        auth = FirebaseAuth.DefaultInstance;
        dataBaseRef = FirebaseDatabase.DefaultInstance.RootReference; 
#if UNITY_EDITOR
        FirebaseDatabase.DefaultInstance.SetPersistenceEnabled(false);
#endif
   }

Please let us know. Shawn

google-oss-bot commented 3 years ago

Hey @MuhsinEge. We need more information to resolve this issue but there hasn't been an update in 5 weekdays. I'm marking the issue as stale and if there are no new updates in the next 5 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

MuhsinEge commented 3 years ago

Hi Everyone, I was so busy to answer your questions and really sorry for my late response.

@alexames You are correct. I realized the issue is what you mentioned. My code tries to reach database before it is online.

        await FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task =>
        {
            dependencyStatus = task.Result;

            if (dependencyStatus == DependencyStatus.Available)
            {
                InitializeFirebase();

            }
            else
            {
                Debug.LogError(
                  "Could not resolve all Firebase dependencies: " + dependencyStatus);
            }
        });

This is the relevant code before the firebase initialized. What do you suggest about ensuring firebase initialized before the queries start?

@chkuang-g Before doing it, what changing the persistence does?

Thank you guys.

patm1987 commented 3 years ago

Hi @MuhsinEge,

First, I'm curious about the logs you print. Is the dependency manager window still open when you click play (it may not be the topmost window) or does it run every time you hit play (it shouldn't)?: image

Next, I'll note that you can simplify your initialization code a bit by using async/await throughout:

public async Task<bool> CheckAppVersion()
{
    Debug.Log("Checking Version");
    DataSnapshot dataSnap = await FirebaseDatabase.DefaultInstance.GetReference("appVersion").GetValueAsync()    

    Debug.Log("Exist: " + dataSnap.Exists);
    Debug.Log(dataSnap.Value);
    int appVersionFromServer =  Convert.ToInt32(dataSnap.Value);

    Debug.Log(appVersionFromServer);
    if (appVersionFromServer == appVersion)
    {
        check = true;
        Debug.Log("App is up to date");
    }
    else
    {
        check = false;
        Debug.Log("App needs to updated");
        return false;
    }
}

Similarly for your start function:

var dependencyStatus = await FirebaseApp.CheckAndFixDependenciesAsync();
if (dependencyStatus == DependencyStatus.Available)
{         
    InitializeFirebase();
}
else
{       
    Debug.LogError(
        "Could not resolve all Firebase dependencies: " + dependencyStatus);
}

With await, you should continue on the calling thread (so it will usually be identical to ContinueWithOnMainThread, at least in this usage) and keeps everything reading linearly which may help work out interdependencies. I mention it because I reason with async/await much better than with continuations, and mixing the two could make it harder to follow more complicated asynchronous logic.

To answer your question to @alexames: There is not a great way to ensure that firebase is initialized before you make a query. I have some techniques though:

  1. You can await on CheckAndFixDependenciesAsync in every script that touches a Firebase product. I really don't like this, but it's quick and easy to throw in.
  2. You can CheckAndFixDependenciesAsync in one scene and load another scene when this completes (alternatively, load a prefab when this completes). If you have a good loading scene to put this into, it's pretty easy to work with (I do this a lot).
  3. You can use a dependency injector line ZenJect to inject a FirebaseDatabase instance into your scripts. I usually don't like this method, but I've used it.
  4. You can use a reactive framework (such as UniRx) that serves a FirebaseDatabase instance (this is very similar to the ZenJect solution, and can be used together).
  5. In larger projects, I typically mimic a MVVM type of architecture for things like Firebase. I'll have a global singleton manager for my FirebaseDatabase instance that's a combination of a C# property (for accessing once, on registration) and a UnityEvent to notify when something's available. UniRx basically achieves the same thing automatically.
  6. You can create a Task<FirebaseDatabase> (or a Coroutine using CustomYieldInstruction) that waits for your CheckAndFixDependenciesAsync to call. Cache this so you only run the hard logic once. This works best when you have your own "Firebase Manager" class that persists between scene loads.

To answer your question to @chkuang-g: The short answer is that persistence persists your database to local storage. Any database operations will hit your local cache first before hitting the backend.

The long answer, which I'm curious if you've tested: GetValueAsync doesn't really work the way many folks assume. The TLDR is that it registers a ValueChanged listener, waits for a value to come in, returns that value, and then unregisters that listener. This means that if you have a value in your local cache, you will retrieve that value with GetValueAsync instead of whatever is available on the server (although the act of registering a listener will cause future calls to GetValueAsync to eventually retrieve the correct value). Disabling persistence forces you to always hit the server (with a pretty nasty performance hit if your data doesn't change very often).

Given this behaviour, you can call KeepSynced(true) before you call GetValueAsync may make reading this value a bit more reliable (as will disabling persistence) -- although you really should consider adding a ValueChanged listener and disabling features of your game while this isn't the expected version (remember that your first call might be the wrong version if it hits the cache, and you may get a new version later if the version changes from what's in the cache).

Let me know if any of that helped! Even if there's a workaround in there, I don't think you should be seeing a value of 2 unless you ever synced that down to the cache (it doesn't seem like you have?) so there is likely still a bug to track down.

chkuang-g commented 3 years ago

@MuhsinEge

Try something like the following to turn off persistence:

#if UNITY_EDITOR
  FirebaseDatabase.DefaultInstance.SetPersistenceEnabled(false);
#endif

But seems like you resolved your issue before your turn the persistence off, so that probably is not the issue.

MuhsinEge commented 3 years ago

@patm1987 Hi Patrick

I do not see the Resolving Android Dependencies tab but these are the logs everytime i start playing.

image

You can await on CheckAndFixDependenciesAsync in every script that touches a Firebase product. I really don't like this, but it's quick and easy to throw in.

I already do this suggestion. I'll give the other ones a try then let you know if issue is gone.

fmoyano commented 3 years ago

I had a similar issue, reading old values some times from the database, and it seems to be fixed by adding: `#if UNITY_EDITOR FirebaseDatabase.DefaultInstance.SetPersistenceEnabled(false);

endif`

,as mentioned above.

One curiosity though: where is the local cache info for the real-time database? I've looked for it inside the Unity project without success.

alexames commented 3 years ago

@fmoyano

Depends on your platform. This is the function that's called to get the platform specific data directory, and we place a folder based on your app name in that folder, which is where the cache is stored.

google-oss-bot commented 3 years ago

Hey @MuhsinEge. We need more information to resolve this issue but there hasn't been an update in 5 weekdays. I'm marking the issue as stale and if there are no new updates in the next 5 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

google-oss-bot commented 3 years ago

Since there haven't been any recent updates here, I am going to close this issue.

@MuhsinEge if you're still experiencing this problem and want to continue the discussion just leave a comment here and we are happy to re-open this.