1

I am using firebase to build a global leaderboard and social leaderboard for our mobile games. As the game would have 10s of 1000s of entries for highscores it would be not recommended to load the complete list of entries hence we would be using pagination. But I want to load the position of the current player without loading the entire list.

Example: A leaderboard has 100 players and user XYZ is on 65th position. Is there any way I can get 65 without loading the whole list?

Rohit Goyal
  • 1,521
  • 3
  • 24
  • 49
  • So what's the problem, to implement pagination or to get item by specified condition? [For 1](https://www.firebase.com/docs/web/api/query/limittofirst.html) , [For 2](https://www.firebase.com/docs/web/api/query/equalto.html) – Nazar Sakharenko Jun 14 '16 at 13:02
  • @Rohit Goyal, did you find a solution? – JCarlosR Aug 22 '17 at 20:27

4 Answers4

1

There is no way to know the index of a specific key, without downloading all keys. It's simply an operation that doesn't match well with Firebase's API, mostly due to the realtime nature of the database.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 4
    Hi @Frank As you work at Firebase I think Firebase is used for leaderboard? If yes then how do people handle this challenge? Also is there plan to address this issue from Firebase side? – Rohit Goyal Jun 15 '16 at 13:41
1

There isn't necessarily a simple solution for this. If you're looking to return the nth item, this post might be useful: Can I get the nth item of a firebase "query"?

However, you can definitely do this without loading the entire list using orderBy and limitToFirst to return the top 65. Something like yourRef.orderByChild('points').limitToFirst(65).

Probably not as simple as what you were hoping for, but I hope it helps.

Community
  • 1
  • 1
Luke Schlangen
  • 3,722
  • 4
  • 34
  • 69
  • I don't want to get the nth item, I want to get the n for a particular user. – Rohit Goyal Jun 15 '16 at 13:38
  • I don't know of a great solution for this. You could potentially denormalize the data and just store a user idea and score. Then you would still have to make a call for all of that data, but you wouldn't need to return quite as much data in order to find the answer? Another way might be to run a Cron process that updates that "Rank" property for each player. It wouldn't give them instant access to their current rank, but if you're ok with the ideas being updated daily, that could be an effective solution that doesn't require returning a ton of data as your user base grows. – Luke Schlangen Jun 15 '16 at 14:56
1

This may be a pretty simple answer but it really depends on how you structure your data.

If there are exactly 100 users at all times and you want everyone from position 65 to position 1, thats easy.

Likewise if you want what position user XYZ is, that's also easy.

There's also ways to load the top 2 users by position (or 35)

users:
   user_id_0
     position: 15
   user_id_1
     position: 32
   user_id_2
     position: 7

then to a query on the /users/position sort by position and then queryLimitedToFirst(2) will return user_id_2 and user_id_0.

Edit:

Based on more data, here are a couple more options. Given we have 5 users, each with a score (I noted their 'position' in the list)

uid_0: 50 //pos 2
uid_1: 25 //pos 1
uid_2: 73 //pos 4
uid_3: 10 //pos 0
uid_4: 58 //pos 3

If you want to know the position, based on score of uid_2 - I don't know your platform so I will give a generic flow:

step 1) query uid_2 to get his score, 73, then

step 2) queryOrderedByChild(score).startingAtValue(73), get nodes with scores from 73 to whatever the highest score is

step 3) then the result will be in a snapshot, so check snapshot.childrenCount, which will be the position if uid_2, which is 4. This will reduce the number of nodes loaded.

The downside is that the farther down the list, the more data would be loaded. If you only have 1000 nodes with just scores in them, that's not a lot of data.

The following solution avoids lots of data but requires more work by the app.

Of course, if your platform support array's this is a super simple task. Using the same structure above, load the children into and array and get the index number of the uid you want to know the position of.

Swift code

let ref = self.myRootRef.childByAppendingPath("scores")
ref.queryOrderedByValue().observeEventType(.Value, withBlock: { snapshot in

     var myArray = [String]()

     for child in snapshot.children {
          let key = child.key as String
          myArray.append(key)
     }

     let pos = myArray.indexOf("uid_0")
     print("postion = \(pos!)")
})
Jay
  • 34,438
  • 18
  • 52
  • 81
  • What you are doing is if I want to get a value at a particular position. But the issue is I don't know the player position. For example if there are 1000 player in a leaderboard then there are 2 things which are dynamic 1) Total number players 2) Score of each player – Rohit Goyal Jun 15 '16 at 13:38
  • @RohitGoyal in your question, you said *But I want to load the position of the current player without loading the entire list.* Is the position the same as the score? If not, what is the relationship? – Jay Jun 15 '16 at 13:54
  • So username is the key and score is the value. And I want to know the rank of a user in the leaderboard by score. Anyways Frank from Firebase answered that it is just not possible with Firebase as of now. – Rohit Goyal Jun 15 '16 at 14:11
  • @RohitGoyal I threw a couple more options on the answer as well. Maybe one of them will work for you! – Jay Jun 15 '16 at 18:49
0

One way might be to run a process that updates the "Rank" property for each player. It wouldn't give them instant access to their current rank, but if you're ok with the ideas being updated daily or on a regularly scheduled timing, that could be an effective solution that doesn't require returning a ton of data to the user as your user base grows. The process could be something like @pufs recommendation for getting the nth item:

var ref = new Firebase('https://yours.firebaseio.com/users');
var lastKnownScore = null;
var firstQuery = ref.orderByChild('points').limitToFirst(100);
var newRank = 1;
firstQuery.once('value', function(snapshot) {
  snapshot.forEach(function(childSnapshot) {
    childSnapshot.update({ rank: newRank });
    lastKnownScore = childSnapshot.value();
    newRank++;
  });
});

while(lastKnownScore > 0) {
  var nextQuery = ref.orderByChild('points').startAt(lastKnownScore).limitToFirst(100);

  nextQuery.once('value', function(snapshot) {
    snapshot.forEach(function(childSnapshot) {
      childSnapshot.update({ rank: newRank });
      lastKnownScore = childSnapshot.value();
      newRank++;
    });
  });
}

This will need some refactoring if you expect multiple players to have identical scores or players who might have 0 points, but hopefully you can see what I'm getting at. If you set up a process to run this code regularly, it could keep your ranks relatively up to date.

Luke Schlangen
  • 3,722
  • 4
  • 34
  • 69