Home > Enterprise >  Unity Firebase Realtime Database - Get index of child in database
Unity Firebase Realtime Database - Get index of child in database

Time:01-05

I am attempting to retrieve the index of an entry in my database. I can do this by ordering the data then grabbing all of the data in my database and counting the elements up to the entry that I want to find the index of. However, this is not a viable solution when the database goes online.

I have tried to use StartAt but it returns Season1 and null

databaseReference.Child("Leaderboards").Child("Season1").StartAt("player0").GetValueAsync().ContinueWith(task =>
{
    print(task.Result.Key);
    print(task.Result.GetRawJsonValue());
});

I have also tried to use EndAt but it returns all of the data in Season1

databaseReference.Child("Leaderboards").Child("Season1").EndAt("player0").GetValueAsync().ContinueWith(task =>
{
    print(task.Result.Key);
    print(task.Result.GetRawJsonValue());
});

I have also added .OrderByValue() after .Child("Season1") which works fine in foreach(DataSnapshot dataSnapshot in task.Result.Children) but without limiting the data received, there is no point.

Perhaps, I need to restructure my database (Which I am fully willing to do as it does not hold actual data yet) but I do not know what format would fit my needs.

The Database being worked on:

Database Structure

CodePudding user response:

From what I have found out through extensive testing, the functions StartAt() and EndAt() for Firebase Realtime Database only search by value and not key. In my case, all of my values are integers so my StartAt() and EndAt() functions must include an integer value to filter data. Perhaps you can send a JSON string to filter objects but that requires further testing as the documentation is nonexistent

The best solution to find an index while retrieving as little data as possible would be to loop through the database searching for the key.

A few relevant things that I discovered during my testing are:

  • .OrderByValue() returns items in a low to high (0-100 and maybe a-z) format and cannot be changed
  • .StartAt(int value) will begin by showing all values starting at the given value passed into the function Inclusive
  • .EndAt(int value) will stop showing values from the given value passed into the function oddly enough, this is also Inclusive
  • .LimitToLast(int count) will only retrieve the last count number of elements from the previous query which in my case, is safer to use than combining .StartAt() with .EndAt() as they can return an unknown amount of data
  • .LimitToLast(100).LimitToLast(5) and .LimitToLast(100).LimitToFirst(5) both return the same 5 last values
  • There may be some cases when using .EndAt(int value).LimitToLast(int count) does not return all of the elements at value which can prevent a recursive function from progressing. As stacking .LimitToLast().LimitToFirst/Last() only returns the last values you cannot use it to iterate through the elements in the database. In this case, you can use .StartAt(int value).EndAt(int value) to obtain all elements with that value which would unfortunately return up to the entire database. This issue would happen at the start of a season when all scores are 0. A possible workaround for this issue would be to check if the current element is 0 (lowest possible value) and then return the count of higher elements

My example will be ran inside of an IEnumerator instead of .GetValueAsync().ContinueWith(task => { }); since I am updating on screen elements

Rough example:

bool hasData = false;
int index = 0;

//start getting baseData
System.Threading.Tasks.Task<DataSnapshot> getLeaderboardTask = databaseReference.Child(DB_LEADERBOARD).Child(season).OrderByValue().LimitToLast(100).GetValueAsync();

//wait for data to be loaded
while (!getLeaderboardTask.IsCompleted) yield return null;

//Check for player value
foreach (DataSnapshot snap in getLeaderboardTask.Result.Children)
{
    if (snap.Key.Equals(playerName))
    {
        hasData = true;

        //Do something with the data - Remember to get index, you should use `((int)getLeaderboardTask.Result.ChildrenCount) - index` because Firebase counts in ascending order

        break;
    }

    index  ;
}

//check if we need to iterate deeper
if (!hasData)
{
    //get new task
    System.Threading.Tasks.Task<DataSnapshot> getLeaderboardTask = databaseReference.Child(DB_LEADERBOARD).Child(season).OrderByValue().EndAt(endAtValue).LimitToLast(100).GetValueAsync();

    //continue searching while data hasn't been found
    while (!hasData)
    {
        //Wait for current data
        while (!getLeaderboardTask.IsCompleted) yield return null;

        #region Check if we have the player ranks yet
        //get global Data
        if (!hasData)
        {
            int lastRank = 0;

            //loop through the data that we just grabbed to look for the player global rank
            foreach (DataSnapshot snap in getLeaderboardTask.Result.Children)
            {
                if (snap.Key.Equals(playerName))
                {
                    hasData = true;

                    //Do something with the data - Remember to get index, you should use `((int)getLeaderboardTask.Result.ChildrenCount) - index` because Firebase counts in ascending order

                    break;
                }

                lastRank = int.Parse(snap.GetRawJsonValue());
                index  ;
            }

            //we did not find the player yet, look for them 1 step deeper
            if (!hasData)
            {
                //we are caught in a loop unable to look deeper, search through all elements at this value
                if (endAtValue == lastRank)
                {
                    getLeaderboardTask = databaseReference.Child(DB_LEADERBOARD).Child(season).OrderByValue().StartAt(endAtValue).EndAt(endAtValue).GetValueAsync();

                    //decriment globalEndAtValue in case we do not find it at the current value
                    endAtValue--;
                }
                else
                {
                    endAtValue = lastRank;

                    //we are not in a loop and can continue searching at lastRank
                    getLeaderboardTask = databaseReference.Child(DB_LEADERBOARD).Child(season).OrderByValue().EndAt(endAtValue).LimitToLast(100).GetValueAsync();
                }
            }
        }
    }
}

CodePudding user response:

Not getting any query reults

In your code you have an uppercase P in Player0, but in the database all keys use a lowercase p like player0. That might explain why your reads are not giving results, as queries in Firebase are case-sensitive.

Getting the index of a node

I can [get the index] by ordering the data then grabbing all of the data in my database and counting the elements up to the entry that I want to find the index of. However, this is not a viable solution when the database goes online.

Unfortunately, counting the nodes before it is the only way to get the index of a node in the Firebase Realtime Database. There is nothing built into the database to get the index of a node, so even if it exposed an API for it - it would essentially be doing the same internally.

  • Related