0

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.

Dror
  • 1,262
  • 3
  • 21
  • 34
  • The code you shared looks fine. Can you run it in a debugger, and set a breakpoint on `JsonLeaderBoardResult = result` to see what value it gets there? – Frank van Puffelen Nov 16 '18 at 14:30
  • @FrankvanPuffelen I modified my method in the question. it will get the data in `JsonLeaderBoardResult = result` but the `return JsonLeaderBoardResult` value will be null. – Dror Nov 16 '18 at 14:54
  • That is expected, since the `JsonLeaderBoardResult = result` runs **after** `return JsonLeaderBoardResult;`. I'll write up a quick answer. – Frank van Puffelen Nov 16 '18 at 15:01

2 Answers2

0

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);
});
Community
  • 1
  • 1
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Good explanation! but i want to do some stuff with the `result` outside when callback finished. (your last code sample). Need some way to 'wait' for it or different approche. – Dror Nov 16 '18 at 15:15
0

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);
                });
            });
}
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • 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. Constructors and field initializers will be executed from the loading thread when loading a scene. – Dror Nov 16 '18 at 17:26
  • That's no problem. See my edit for how to handle that. The Action will now be called on the main Thread. – Programmer Nov 16 '18 at 17:33
  • 1
    At last, so much pain for something that seems simple :). Works, Appreciated! – Dror Nov 16 '18 at 17:42