Get the user’s profile image in Firebase iOS Swift

Updated: 6/8/2020

In this post I’m going to show you how to user’s profile image in Firebase in iOS Swift. Google’s Firebase Realtime database allows you to store data such as text in tree like structure. Google’s Storage is another database for blog storage like images, videos, etc.

In my ProMe Sports (A social network for athletes) app this is how I retrieve user’s properties like the userid, email address, full name, profile photo URL storage location, etc. My app is built using Swift 4.2 of course so I don’t know how this would work in Objective-C.

User object model

First create a user object in a separate file.

//
//  UserProfile.swift
//
//  Created by Waleed on 9/10/17.
//  Copyright © 2017 Waleed Sarwari. All rights reserved.
//

import Foundation

struct UserProfile {
    var userId: String
    var emailAddress: String
    var fullName: String
    var profilePhotoURL: String
   
    // MARK: - Firebase Keys
    
    enum UserInfoKey {
        static let email = "email"
        static let name = "name"
        static let profilePhotoURL = "profilePhotoURL"
        static let age = "age"
    }
    
    
    init(userId: String, fullName: String, emailAddress: String, profilePicture: String) {
        self.userId = userId
        self.emailAddress = emailAddress
        self.fullName = fullName
        self.profilePhotoURL = profilePhotoURL
    }
    
    init?(userId: String, userInfo: [String: Any]) {
        let fullname = userInfo[UserInfoKey.name] as? String ?? ""
        let dateJoined = userInfo[UserInfoKey.dateJoined] as? Int ?? 0
        let photoURL = userInfo[UserInfoKey.photoURL] as? String ?? ""
        let emailAddress = userInfo[UserInfoKey.email] as? String ?? ""
        self = UserProfile(userId: userId, fullName: fullname, emailAddress: emailAddress, profilePhotoURL: profilePhotoURL)
    }
}

Upload user profile

Timing and correct steps are essential to clean user experience. If you have programmed long enough you’ll know “race conditions“. In IOS I use PromiseKit to control one execution completes before another starts. Now you may not need a Promise, it all depends on what you are doing.

func uploadUserProfileImage(profileImage: UIImage) -> Promise<()> {
        return Promise<()> { seal -> Void in
            
            // Use the auto id for the image name
            // Generate a unique ID for the post and prepare the post database reference
            let imageStorageRef = PROFILE_IMGS_STORAGE_REF.child(Utilities.getCurrentUserId()).child("default.jpg")
            
            // Resize the image
            //        let scaledImage = image.scale(newWidth: 640.0)
            guard let imageData = profileImage.jpegData(compressionQuality: 0.9) else {
                return
            }
            
            // Create the file metadata
            let metadata = StorageMetadata()
            metadata.contentType = "image/jpeg"
            
            
            // Upload the image to the userProfileImages storage
            let uploadTask = imageStorageRef.putData(imageData, metadata: metadata, completion: {
                (data, error) in
                imageStorageRef.downloadURL(completion: { (url, error) in
                    if let uploadedImageURL = url?.absoluteString {
                        
                        // Get the image url and assign to photoUrl for the current user and update
                        
                        if let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest() {
                            
                            changeRequest.photoURL = URL(string: uploadedImageURL)
                            changeRequest.commitChanges(completion: { (error) in
                                
                                if let error = error {
                                    print("Failed to change the profile image: \(error.localizedDescription)")
                                }else {
                                    print("Changed user profile image")
                                    
                                    guard let userId = Auth.auth().currentUser?.uid else {
                                        return
                                    }
                                    
                                    // Save the profile of the user
                                    let values = [UserProfile.UserInfoKey.profilePhotoURL: uploadedImageURL]
                                    USERS_DB_REF.child(userId).updateChildValues(values, withCompletionBlock: { (error, ref) in
                                        if error != nil {
                                            print(error!)
                                            return
                                        }
                                        
                                        print("Updated user photoUrl")
                                        // Update cache
                                        CacheManager.shared.cache(object: profileImage, key: userId)
                                        seal.fulfill(())
                                        
                                    })
                                }
                            })
                        }
                    }
                })
            })
            
            uploadTask.observe(.failure) { (snapshot) in
                
                if let error = snapshot.error {
                    print(error.localizedDescription)
                }
            }
            
            // Observe the upload status
            uploadTask.observe(.success) { (snapshot) in
            }
        }
    }

GET USER PROFILE BY USER ID

Simplified by first getting the profile storage URL from the Firebase Realtime Database.

func getUserProfileImgURL(userId: String, completionHandler: @escaping (String) -> Void) {
        
        // Get the rest of the user data
        USERS_DB_REF.child(userId).observeSingleEvent(of: .value, with: { (snapshot) in
            
            // Get user value
            if let userValues = snapshot.value as? NSDictionary {
                
                if let userPhotoURL = userValues[UserProfile.UserInfoKey.profilePhotoURL] as? String {
                    completionHandler(userPhotoURL)
                }
            }
        })
    }

This static function can now be called from anywhere.

static func loadUserProfilePhoto(userId: String, matchingUserId: String, imageView: UIImageView) {
        UserServices.shared.getUserProfileImgURL(userId: userId) { (profileImgURL) in
            
            let imageCacheId = userId

            if let image = CacheManager.shared.getFromCache(key: imageCacheId) as? UIImage {
                imageView.image = image

            } else {
                
                if let url = URL(string: profileImgURL) {
                    
                    let downloadTask = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
                        
                        guard let imageData = data else {
                            return
                        }
                        
                        OperationQueue.main.addOperation {
                            guard let image = UIImage(data: imageData) else { return }
                            
                            // In the completion handler of the download task, we add a simple verification to ensure that we only display the right image. For the image that is not supposed to be displayed by the current cell, we just keep it in cache.
                            //
                            if matchingUserId == userId {
                                imageView.image = image
                            }
                            
                            // Add the downloaded image to cache
                            CacheManager.shared.cache(object: image, key: userId)
                        }
                        
                    })
                    
                    downloadTask.resume()
                }
            }
        }
    }

You can place this static function wherever, I put it a “Utilities” class. The “matchingUserId” is for caching purposes. When the user scrolls or views the user’s profile for the first time it will download and cache in the cache manager. The second or more time during that session it will look for the cached version of the photo. This helps with speed and accuracy of loading images.

Calling the static function example

Utilities.loadUsersProfilePhoto(userId: userId, matchingUserId: self.user.object.userId, imageView: self.userImage)

This is how I retrieve the user image as data using the user Id and the photo URL location. Remember the user id comes from the Firebase Realtime Database and the actual image is stored in the Firebase Storage. This is stored in another file that does storing and retrieving only.

This is also doing some simple error checking. If there isn’t any profile image then use the default profile image that’s stored within the app assets.

Remember this all assumes the Firebase rules are allowing this, checkout this article for details. Also don’t forget to lockup your API keys!

Don’t forget to subscribe and share 🙂

Google Firebase detailed security rules Part 1

I’ll be showing you how Google’s Firebase security rules in detail with my custom iOS app with slight changes. In addition to securing your Google Firebase API’s, you must also secure your Firebase Realtime Database. This is secured by applying security rules to the database in the Firebase console. I highly recommend using the provided simulator to test and verify the expected results that you expect for your web or application! I will not be responsible for your misconfiguration or lack of understanding of what this article entails.

Basic rules

100% Closed

// These rules don't allow anyone to read or write access to your database
{
  "rules": {
    ".read": false,
    ".write": false
  }
}

This is the MOST restrictive rule, no one is allowed to do any CRUD operations. Also, this is the least useful one. (I don’t know why I even bothered with typing this 🙂 let’s continue)

This below is open for anyone in the world to read and write, even worse than the most restrictive security rules.

100% Open


// OPEN TO THE WHOLE WORLD
// read and write access to your database
{
  "rules": {
    ".read": true,
    ".write": true
  }
}

This one below is a good starting point but it does require modification depending on your needs. If you’re going to have users sign up and sign in functionality then start with this.

User ID based

// These rules grant access to a node matching the authenticated
// user's ID from the Firebase auth token
{
  "rules": {
   ".read": "auth != null",
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    }
  }
}

The first rule means only authenticated users can read everything! Which is most likely what you want to start with. The second rule says only information from the “users” tree that has a node ID that is equal to the current user’s ID, may have full read and write permissions on that exact node. In the example below, the current user has full access to node1, not node2.

// assume current user id is 22222222222

someApp-1234
 users
  node1: 11111111111
    key: value
    key: value
  node2: 22222222222
    key: value
    key: value

References