1

I am developing an android application where older kids can pick up younger kids and walk to school. With the application the authenticated (email and password) younger kid can choose between three adresses to get picked up. As of right now my realtime database looks like this:

enter image description here

I want to retrieve the different addresses and the users who picked the addresses. I am thinking I have to use recyclerview to get the data, but I am unsure on if it is possible to do with my database structure.

enter image description here

KENdi
  • 7,576
  • 2
  • 16
  • 31
JDoe
  • 221
  • 4
  • 14
  • That sounds feasible, but I'd definitely recommend looking at FirebaseUI to help with this. It has special adapters such as `FirebaseRecyclerAdapter`. For this specifically I'd look at: https://github.com/firebase/FirebaseUI-Android/blob/master/database/README.md#using-firebaseui-with-indexed-data – Frank van Puffelen Oct 13 '17 at 15:26
  • Thanks for the answer, I looked at the link you provided, but I am still in doubt on how to query the nodes. How do I get the username from the Adresses node? – JDoe Oct 13 '17 at 16:17
  • @FrankvanPuffelen I hope you don't mind me jumping in with an answer here - I had it drafted before spotting your comment! – Grimthorr Oct 13 '17 at 16:39
  • 1
    @Grimthorr Perfect! – Frank van Puffelen Oct 13 '17 at 16:47

2 Answers2

3

Using the FirebaseUI database package makes it simple to bind data from the Firebase Realtime Database to your app's UI. Specifically using FirebaseUI with indexed data is applicable for your current database structure.

For example, you'd use something similar to:

// keyQuery - the Firebase location containing the list of keys to be found in dataRef
// dataRef - the Firebase location to watch for data changes. Each key found at 
//           keyRef's location represents a list item.

Query keyQuery = FirebaseDatabase.getInstance().getReference("/Addresses/Street 10/users");
DatabaseReference dataRef = FirebaseDatabase.getInstance().getReference("/User");

FirebaseRecyclerOptions<User> options = new FirebaseRecyclerOptions.Builder<User>()
        .setIndexedQuery(keyQuery, dataRef, User.class)
        .build();

Where your User class is:

public class User {
    private String username;

    public User() {}

    public String getUsername() {
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    // ...
}

You can then use the above created options variable to create a FirebaseRecyclerAdapter instance and then call startListening() on it*.

Using FirebaseUI in this way will automatically handle matching the keys under /Addresses/Street 10/users to the /User node.


* Version 2.x of FirebaseUI uses FirebaseIndexRecyclerAdapter instead and starts listening automatically so doesn't require a startListening() call. The above example is applicable for version 3.0, see FirebaseUI 3.0 upgrade guide.

Grimthorr
  • 6,856
  • 5
  • 41
  • 53
  • Can I ask how the constructor can be public Item? – JDoe Oct 13 '17 at 16:49
  • Thank you for your answer :) I'll try it out – JDoe Oct 13 '17 at 17:25
  • I tried it, but i am getting a weird error on FirebaseIndexRecyclerAdapter which says "cannot resolve symbol". I have used pastebin to paste my code. https://pastebin.com/cJJqJwaW – JDoe Oct 13 '17 at 21:02
  • Hmm it seems like version 3.0 of FirebaseUI has done away with the dedicated `FirebaseIndexRecyclerAdapter` but hasn't updated their documentation! See the "indexed adapters" note in the [FirebaseUI 3.0 upgrade guide](https://github.com/firebase/FirebaseUI-Android/blob/master/docs/upgrade-to-3.0.md). Therefore in this version you can use the the standard `FirebaseRecyclerAdapter` along with the `setIndexedQuery()` option for the same features. – Grimthorr Oct 13 '17 at 22:02
  • I tried this: https://pastebin.com/Y5hejduc, but I am not getting anything on screen. – JDoe Oct 14 '17 at 09:01
  • I checked the adapter and it is null. I don't get why it is empty – JDoe Oct 14 '17 at 12:51
  • 1
    This is another [change made in version 3.0 of FirebaseUI](https://github.com/firebase/FirebaseUI-Android/blob/master/docs/upgrade-to-3.0.md) (see "Adapter Lifecycle" note) - you need to explicity [call startListening() on the adapter](https://github.com/firebase/FirebaseUI-Android/blob/master/database/README.md#firebaserecycleradapter-lifecycle). Sorry, I've not used version 3.0 as it was only released recently. I've updated my answer with these changes. – Grimthorr Oct 15 '17 at 12:28
  • This kind of work, but the problem here is that each user has its own row. So instead of all users assigned to the street being showed on the same index, each user has its own – JDoe Oct 15 '17 at 12:53
  • That's correct. To list all addresses you can use a simple `setQuery()` option for the `/Addresses` node instead. From there, when the user selects one of these addresses, you can use the above adapter to list the users that have chosen this address (in a separate fragment for instance). I think this approach is preferable, because showing a list of all addresses with associated users in the same adapter would be costly to data and screen space. Do you see what I mean? – Grimthorr Oct 15 '17 at 13:57
  • Okay, I am a bit confused. What I originally wanted to display per row was: The name of the address and every username that has picked that address – JDoe Oct 15 '17 at 15:14
  • Ah I see. This answer is for listing the users for one specific address using the indexed query. To do what you need, you'll need to start denormalizing & duplicating your data. I'll add another answer for this instead of changing this one again. – Grimthorr Oct 15 '17 at 16:51
2

Following on from my previous answer, this one should accommodate your requirement to create a list of all addresses and their associated users, which may be closer to what you're looking for.

Again you can use the FirebaseUI database package to simplify the RecyclerView creation.

You'll need to start denormalizing your data, so your data structure should also include usernames in the addresses node:

{
  "Addresses" : {
    "Street 10" : {
      "name" : "Street 10",
      "users" : {
        "VAzdMWafK6cyhmJnOI4br5xiQg93" : "John"
      }
    }
  },
  "User" : {
    "VAzdMWafK6cyhmJnOI4br5xiQg93" : {
      "username" : "John",
      "address" : "Street 10"
    }
  }
}

Note: you only need to add user IDs to their chosen address (and remove the node if they change selection), so don't use "VAzdMWafK6cyhmJnOI4br5xiQg93" : false for addresses the user has not selected as this could cause confusion.

Then you can use:

Query query = FirebaseDatabase.getInstance().getReference("/Addresses");

FirebaseRecyclerOptions<Address> options = new FirebaseRecyclerOptions.Builder<Address>()
        .setQuery(query, Address.class)
        .build();

Where Address is something like:

public class Address {
    private String name;
    private Map<String, String> users;

    public Address() {}

    public Map<String, String> getUsers() {
        return this.users;
    }

    // ...
}

And create a FirebaseRecyclerAdapter instance from the options variable. Then when binding the viewholder in the adapter, you can access the users map to list each user that has selected this address, without the need to load the entire User object unnecessarily.

This pattern is called denormalization and is the suggested approach when using NoSQL databases (like Firebase Realtime Database). The main downside to this is data duplication: so for example, when a user changes their selected address, you'll need to change:

  • The address value under the user, and
  • the users list under the address.

Likewise, if a user is allowed to change their username, you'll need to update the username under their chosen address as well as in the user's node.

For details on dealing with this, see this answer which explains a number of methods (although the examples are in JavaScript, the premise still applies).

Grimthorr
  • 6,856
  • 5
  • 41
  • 53
  • Thank you, i'll try this. I am sorry for the confusion from the original post – JDoe Oct 15 '17 at 17:32
  • I have just finished creating the FirebaseRecyclerAdapter instance. I am in doubt on how to access the users map to list the users in my onBindViewHolder method. Any ideas? – JDoe Oct 16 '17 at 07:58
  • So you should have access to the `model` parameter from `onBindViewHolder()` which will be an instance of the `Address` model for each row. You can then use `getUsers()` and [iterate over this map](https://stackoverflow.com/a/1066607). For example: `for (String username : model.getUsers.values()) { textView.append(username + " & ") }`. – Grimthorr Oct 16 '17 at 08:46
  • Yes, I did this: https://pastebin.com/TE2ta8tL. But I am getting a database error "DatabaseException: Failed to convert value of type java.lang.Boolean to String". – JDoe Oct 16 '17 at 09:08
  • Check your database data to ensure there are no boolean values remaining under any of the `/Addresses/$address/users` nodes (see my example structure above). – Grimthorr Oct 16 '17 at 09:16
  • Thanks for all the help :) – JDoe Oct 16 '17 at 15:51
  • That's OK, no problem, I'm glad I could help. Thanks for accepting the answer. – Grimthorr Oct 16 '17 at 15:53