As I wrote in my commentary on your question, this answer is based on what we are doing in the real social application Impether using Swift + Firebase .
Data structure
Suppose you want to save the following information for a single user:
- Email
- Username
- name
- followers - the number of people who follow a specific user.
- after - the number of people that a particular user adheres to.
- avatar_url - URL of their avatar
- bio - additional text
Since all JSON objects are stored in Firebase, you can save the above structure under node using a path, for example, users/$userId , where $userId is the Firebase user UID that is created for each registered user if you use simple email / password. Firebase Resolution.
Firebase email / password addresses are described in their docs: https://www.firebase.com/docs/ios/guide/user-auth.html https://www.firebase.com/docs/ios/guide/login/ password.html
Note that there are both snippets of Obj-C and Swift. I am very well versed in the Firebase documentation, as it helped me a lot when creating our application.
For the purposes of this answer, suppose we have a user with a username of jack and a custom Firebase UID equal to jack_uid (this will actually be the string created by Firebase).
Then the sample data for this user will be stored on the users/jack_uid and may look like this:
{ "email" : " jack@example.com ", "username" : "jack", "name" : "Jack", "followers" : 8, "following" : 11, "avatar_url" : "http://yourstoragesystem.com/avatars/jack.jpg", "bio" : "Blogger, YouTuber", }
Firebase email and password protection works very well, but let me be honest, if a user wants to log into the application, it is much better for him to use his username than his email, which he gave when registering his account.
To do this, we decided to keep the mapping from user names to user identifiers. The idea is that if the user enters his username and password in the login form, we use this mapping to retrieve his user ID, and then try to sign it using his user ID and the password provided.
The mapping can be stored, for example, along the path username_to_uid and looks like this:
{ "sample_username_1": "firebase_generated_userid_1", "sample_username_2": "firebase_generated_userid_2", ... "jack": "jack_uid", "sample_username_123": "firebase_generated_userid_123" }
Then the creation of the profile may look like this, and this was done as soon as the registration of the new account was successful (this fragment is very close to the exact code that we use in the production process):
func createProfile(uid: String, email: String, username: String, avatarUrl: String, successBlock: () -> Void, errorBlock: () -> Void) { //path to user data node let userDataPath = "/users/\(uid)" //path to user username to uid mapping let usernameToUidDataPath = "/username_to_uid/\(username)" //you want to have JSON object representing user data //and we do use our User Swift structures to do that //but you can just create a raw JSON object here. //name, avatarUrl, bio, followers and following are //initialized with default values let user = User(uid: uid, username: username, name: "", avatarUrl: avatarUrl, bio: "", followers: 0, following: 0) //this produces a JSON object from User instance var userData = user.serialize() //we add email to JSON data, because we don't store //it directly in our objects userData["email"] = email //we use fanoutObject to update both user data //and username to uid mapping at the same time //this is very convinient, because either both //write are successful or in case of any error, //nothing is written, so you avoid inconsistencies //in you database. You can read more about that technique //here: https://www.firebase.com/blog/2015-10-07-how-to-keep-your-data-consistent.html var fanoutObject = [String:AnyObject]() fanoutObject[userDataPath] = userData fanoutObject[usernameToUidDataPath] = uid let ref = Firebase(url: "https://YOUR-FIREBASE-URL.firebaseio.com/images") ref.updateChildValues(fanoutObject, withCompletionBlock: { err, snap in if err == nil { //call success call back if there were no errors successBlock() } else { //handle error here errorBlock() } }) }
In addition to this, you might want to keep for each user a list of their subscribers and a separate list of users that they follow. This can only be done by storing user IDs along the path, for example followers/jack_uid , for example, it might look like this:
{ "firebase_generated_userid_4": true, "firebase_generated_userid_14": true }
This is a way to store value sets in our application. This is very convenient because it is really a user to update it and check if there is any value.
To count the number of followers, we directly put this counter in user data. This makes reading the counter very efficient. However, updating this counter requires using transactional entries, and the idea is almost the same as in my answer: Upvote / Downvote system in Swift via Firebase
Read / Write Permissions
Part of your question is how to handle permissions to store data. The good news is that Firebase is very good here. If you go to the Firebase control panel, there is a tab called Security&Rules , and this is the place where you manage permissions on your data.
What's good about Firebase rules is that they are declarative, which makes them very easy to use and maintain. However, writing rules in pure JSON is not a good idea, since itโs quite difficult to control them when you want to combine some atomic rules into a larger rule or your application just grows and more and more data is stored in your Firebase database Fortunately, the Firebase team wrote "Bolt", a language in which you can easily write all the rules that you need very easily.
First of all, I recommend reading Firebase's security docs, especially how node permissions affect permission for their children. Then you can take a look at Bolt here:
https://www.firebase.com/docs/security/bolt/guide.html https://www.firebase.com/blog/2015-11-09-introducing-the-bolt-compiler.html https: // github.com/firebase/bolt/blob/master/docs/guide.md
For example, we use rules to manage user data like this:
//global helpers isCurrentUser(userId) { auth != null && auth.uid == userId; } isLogged() { auth != null; } //custom types, you can extend them //if you want to type UserId extends String; type Username extends String; type AvatarUrl extends String; type Email extends String; type User { avatar_url: AvatarUrl, bio: String, email: Email, followers: Number, following: Number, name: String, username: Username, } //user data rules path /users/{$userId} is User { write() { isCurrentUser($userId) } read() { isLogged() } } //user followers rules //rules for users a particular //user follows are similar path /followers/{$userId} { read() { isLogged() } } path /followers/{$userId}/{$followerId} is Boolean { create() { isCurrentUser($followerId) && this == true } delete() { isCurrentUser($followerId) } } //username to uid rules path /username_to_uid { read() { true } } path /username_to_uid/{$username} is UserId { create() { isCurrentUser(this) } }
In the end, you write the rules you want to use with Bolt , then compile them in JSON using the Bolt compiler, and then deploy them to your Firebase using the command line tools or pasting them into the control panel, but the command line is more efficient. A good additional feature is that you can test your rules using the tools on the Simulator tab in the control panel.
Summary
For me, Firebase is a great tool for implementing the required system. However, I recommend starting with simple features and learning how to use Firebase first. Implementing a social application with features such as Instagram is quite a challenge, especially if you want to do it right :) Itโs very tempting to put all the functionality there very quickly, and Firebase makes it relatively easy, but I recommend being patient here.
Also, take your time and invest in writing tools. For example, we have two separate Firebase databases, one for production and one for testing, which is very important if you want to perform unit and UI tests efficiently.
In addition, I recommend creating permission rules from the start. Adding them later can be seductive, but also quite overwhelming.
Last but not least, keep an eye on Firebase blogs. They are published regularly, and you can keep abreast of their latest features and updates - this is how I learned to use parallel recordings using branching technology.