0

When a user first creates an account I check to see if the username they pick is available underneath all_usernames ref which is just a pool of all the users names. If the name isn't in there I add their userId and username to that ref (so no other users can pick it) and I also add it underneath the users ref (this is where their profile photo, username, etc would be for posts)

-all_usernames
   |
   @--userId // kim_k's uid
       |
       |----username: "kim_k"

-users
   |
   @--userId // kim_k's uid
        |
        |---username: "kim_k"
        |---profileURL: "https//..."
        |---age: 27

My app has a followers and following ref. I have a searchBar where users can search for people who are following them by name.

enter image description here

Everything works fine but I ran into hiccup. Let's say the user they are following is "kim_k".

let searchText = searchController.searchBar.text?.lowercased() // "kim_k"

let currentUserId = Auth.auth().currentUser!.uid // a different user

followingRef.child(currentUserId)
    .queryOrdered(byChild: "username")
    .queryStarting(atValue: searchText)
    .queryEnding(atValue: searchText+"\u{f8ff}")
    .observeSingleEvent(of: .value, with: { (snapshot) in ... }

Using the code above the currentUser who is performing the search will find "kim_k" underneath their following ref. So will Jill and Jane (separate users) if they were to search for her name underneath their ref (the lat/lon locations are necessary for a separate reason):

-following
    |
    @--userId // currentUserId
    |    |
    |    @----userId // kim_k's uid
    |           |
    |           |----username: "kim_k"
    |           |----lat: 0.111
    |           |----lon: 0.222
    |
    |
    @--userId // jill's uid
    |    |
    |    @----userId // kim_k uid
    |           |
    |           |----username: "kim_k"
    |           |----lat: 0.333
    |           |----lon: 0.444
    |
    @--userId // jane's uid
         |
         @----userId // kim_k's uid
                |
                |----username: "kim_k"
                |----lat: 0.555
                |----lon: 0.555

The hiccup is what happens if "kim_k" changes her name to "kim_kardashian"?

If she were to change it it would be updated under the users ref and the all_usernames ref. When the currentUserId, Jill, and Jane search for her it would return her old username.

The "kim_kardashian" name change would have to happen at the users ref, all_usernames ref, following ref, and the followers refs.

How can I fix this?

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
  • 1
    I am all about denormalizing data but that extra structure seems unnecessary. Why query for the username within the *all_usernames* node when you can just query the *users* node for the same thing? Also, if you are tracking users you are following, why not leverage their uid instead of their name? Then they can change their name to anything and will not affect your structure or queries. – Jay Dec 31 '19 at 19:56
  • hmmmmm, I never thought about that. I guess that's a separate issue. I was under the impression that usernames should be in an entirely different ref. Now that you bring it up it makes sense. Thanks. But even if I don't use the all_usernames node (I'm going to remove it) I'm still in a situation where if kim_k changed her username the change would need to be reflected under the followers and following nodes – Lance Samaria Dec 31 '19 at 20:01
  • @Jay How do I query their userId? When the user types the name "kim_k" inside the searchBar, I need to reference the username key. How would I do that using the uid? Can you give me an example of what you mean? – Lance Samaria Dec 31 '19 at 20:03
  • Ok, first question about having to change the usernames under the following nodes. Don't do that. In my comment I suggested storing the users uid instead of their name. That way the name, or any other data, can change all it wants and it won't affect your structure or your queries as they are tied to the users unique uid. – Jay Dec 31 '19 at 20:15
  • Second question is a little unclear; your users are stored like this */users/uid_0/user_name: value* and */users/uid_1/user_name: value* so if you are searching for a user, query the */users node* by *user_name* for the value (the name) you want. That will return that users node with the uid as the snapshot.key – Jay Dec 31 '19 at 20:18
  • When using the method to search that happens under the following node, not the users node. eg. **followingRef.child(currentUserId) .queryOrdered(byChild: "username").queryStarting(atValue: searchText** How would I bounce from there to the **users** nodes? – Lance Samaria Dec 31 '19 at 20:20
  • I think you're missing what I am saying... I may just not be saying it the right way. You don't bounce - you keep track of who you are following by the uid. Get the list of uid's which will then enable you to get those user's names from the /users node. Let me add a little more to my answer. – Jay Dec 31 '19 at 20:24
  • ok thanks. Yeah I'm confused – Lance Samaria Dec 31 '19 at 20:25
  • 1
    Recommended reading: https://stackoverflow.com/questions/30693785/how-to-write-denormalized-data-in-firebase – Frank van Puffelen Dec 31 '19 at 20:26
  • @FrankvanPuffelen I'm going to look through the link you sent and Jay's answer. I have to absorb them both. I'll get back to you. THANKS and HAPPY NEW YEAR!!! – Lance Samaria Dec 31 '19 at 20:45

1 Answers1

1

To answer the question

Swift Firebase -How to Update a Value at multiple refs?

Updating multiple refs is easy. Suppose you have a structure

items
   item_0
      item_name: "some item 0"
   item_1
      item_name: "some item 1"

and you want update both item names. Here's the swift code.

func updateMultipleValues() {
    let path0 = "items/item_0/item_name"
    let path1 = "items/item_1/item_name"

    let childUpdates = [ path0: "Hello",
                         path1: "World"
    ]

    self.ref.updateChildValues(childUpdates) //self.ref points to my firebase
}

and the result is

items
   item_0
      item_name: "Hello"
   item_1
      item_name: "World"

EDIT

There are acutally a couple of questions going on here so the above specifically shows how to update multiple refs at one time. However, that's actually unrelated to what the actual issue is.

Don't store user names in multiple places (for this use case). There are times whan that's needed (denormalizing) but not here.

The 10,000' view is as follows

If I want to know the names of the users who are following the current user, here's a start.

/users
   uid_0 //assume this is the logged in user
      name: "My Name"
      followers
         uid_1 //uid_0 is being followed by uid_1
            some data
         uid_2
            some data
      following
         uid_3
  uid_1
      name: "Some other user name"
      followers
         whoever is following this user
      following
         uid_0 //uid_1 is following uid_0

then to get the names of who is following the the logged in user we'll use some pseudo code to keep it short.

let myFollowerNode = firebase.users.myuid.followers //read in the followers node
myFollowerNode.observeSingleEvent(.value, with { snapshot in
    //get the array of keys from the snapshot which would be
    let userIdArray = keys(snapshot)
    //  uid_1
    //  uid_2
    for uid in userIdArray { //iterate over the uid's, reading in their names
       let aUser = firebase.users.uid.name
       aUser.observeSingleEvent(.value..... 
           let name = snapshot.value as! string //that users name
Jay
  • 34,438
  • 18
  • 52
  • 81
  • Thanks for the help. Maybe I'm seeing this incorrectly. It looks like "item_0" and "item_1" would be Jill and Jane's userIds but that means I would need to that beforehand. With a couple of people that's fine but with hundreds or thousands fo people it wouldn't work – Lance Samaria Dec 31 '19 at 20:16
  • @LanceSamaria So that answer is specific to the question about how to update multiple refs. It could still be done (as you would know the users uid) but the easier solution is not *not* store the usernames anywhere else but in the /users/uid/username node. – Jay Dec 31 '19 at 20:21
  • That's the part I'm confused with. I understand that you're saying I'm doing it incorrectly or should just do it in a more efficient way. The part I'm not understanding is how to do it. When the search query occurs here: **followingRef.child(currentUserId) .queryOrdered(byChild: "username").queryStarting(atValue: searchText** I need to know who they are following and the username. I don't see where the users node connects with the following node – Lance Samaria Dec 31 '19 at 20:23
  • 1) I see your point, you did answer the question correctly, thanks. 2) I do have multiple things going on here and I sort of confused myself. I probably should've asked the question differently. 3) Your updated answer you don't use **.queryOrdered()/.queryStarting()**. 4) I'm still confused about your updated answer. I have some more questions and I'l probably need to ask them in an entirely different thread. Let me look this over and get back to you. Happy New Year and THANKS!!! – Lance Samaria Dec 31 '19 at 20:44
  • @LanceSamaria So in your question you state *I have a searchBar where users can search for people who are following them by name.* My question is; for what reason are you searching for them. In other words, in my answer, if you were uid_0, you would know that uid_1 and uid_2 are following you. What more are you needing to search for. I am not using .query (yet) because the list of users is in your followers list - as I mentioned, what are your querying for since they are already there. – Jay Dec 31 '19 at 20:44
  • Say you have 500 users following you but you want to look for a specific user. You can scroll through all 500 until you find that user or you can search for them and they instantly appear. That's the way Instagram does it, I tried to mimic their flow – Lance Samaria Dec 31 '19 at 20:51
  • @LanceSamaria I think you answered you own question there. If you have a list of followers that you could scroll through, as a typical app would, you have already loaded in the names of the users who are following you into the tableView datasource. That array would have a class that stores the username and the uid. You can then just find the name within that list. The other advantage is you can do a partial inner string search which Firebase doesn't directly offer (or a location search or name and location search etc) – Jay Dec 31 '19 at 21:24
  • I have 2 scenarios here. 1) when the searchBar is empty I fill up the tableView with the first 25 followers. The user can then paginate 25 more until whenever they decide to stop. If they had 500 users they would paginate 20x. 2) the user enters in a name inside the searchBar, I clear the tableView and then as they continue to type all the results that match appear until they finally found what they are looking for. In both situations I never pull all the data from the db. In situation 1) if they had 20,000 followers and I loaded everyone, it would be overkill – Lance Samaria Dec 31 '19 at 21:51
  • @LanceSamaria That adds an additional layer of complexity as if you are using Firebase Pagination, you'll retrieve results based on and *and* query situation; *name begins with some_name AND is following you*. One solution is to ignore results that don't contain the current users uid in their following list. In other words, suppose you're looking for all users whose name is Steve and are following you. The query would be for all (or 25) users named Steve, which will present those, then as the nodes are presented ignore then ones where your uid is not included in that nodes following list. – Jay Jan 01 '20 at 14:16
  • ...Additionally, because you are paginating (not in the question) it would be a better use for denormalizing data and going back to updating multiple child nodes (in the first part of my answer) if the user changes their name. – Jay Jan 01 '20 at 14:19
  • thanks for the update. Im definitely paginating. One thing I notice about atomic updates is that it overwrites everything at the node. For eg, if I were to do what you said in the first part of the answer then the lat/lon would get overwritten in my db examples. It seems I would have to put them at another node. – Lance Samaria Jan 02 '20 at 00:16
  • @LanceSamaria With .updateChildValues, it only updates that specific child and does not affect the rest of the child nodes at the same level as the node being updated. You can actually see it happen if you have the Firebase Console open in a browser - only the specific child nodes are affected. In your case, updating */following/user_id/user_id/user_name* would not affect the lat/lon nodes at that same level as the user_name node. – Jay Jan 02 '20 at 16:42
  • thanks. I used atomic updates throughout my app (I should've asked this question differently) and every time I did it using .updateChildValues the other children got written over. I'll try it again later on this evening and if it gets written over I'll post the code as an entirely new question. I must be doing something wrong. Thanks – Lance Samaria Jan 02 '20 at 16:46