Search code examples
swiftfirebasefacebook

What is the best practice to retrieve Facebook user picture with Firebase in Swift?


I have the following method in a ViewModel to handle user clicks on a Facebook login button:

import Foundation
import FirebaseAuth
import FacebookLogin

/// A view model that handles login and logout operations.
class SessionStore: ObservableObject {
    @Published var user: User?
    @Published var isAnon = false
    
    private var handle: AuthStateDidChangeListenerHandle?
    private let authRef = Auth.auth()
    
    /// A login manager for Facebook.
    private let loginManager = LoginManager()
    
    
    /// Listens to state changes made by Firebase operations.
    func listen()  {
        handle = authRef.addStateDidChangeListener {[self] (auth, user) in
            if user != nil {
                self.isAnon = false
                self.user = User(id: user!.uid, fbId: authRef.currentUser!.providerData[0].uid, name: user!.displayName!, email: user!.email!, profilePicURL: user!.photoURL)
            } else {
                self.isAnon = true
                self.user = nil
            }
        }
    }
    
    
    
    /// Logs the user in using `loginManager`.
    ///
    /// If successful, get the Facebook credential and sign in using `FirebaseAuth`.
    ///
    /// - SeeAlso: `loginManager`.
    func facebookLogin() {
       
        loginManager.logIn(permissions: [.publicProfile, .email], viewController: nil) { [self] loginResult in
            switch loginResult {
            case .failed(let error):
                print(error)
            case .cancelled:
                print("User cancelled login.")
            case .success:
                let credential = FacebookAuthProvider.credential(withAccessToken: AccessToken.current!.tokenString)
                authRef.signIn(with: credential) { (authResult, error) in
                    if let error = error {
                        print("Facebook auth with Firebase error: \(error)")
                        return
                        }
                    }
                }
            }
        }

    
}

In listen(), I'm trying to build my User model whenever Firebase detects state change (i.e., when a user is logged in). My User model is a simple struct like this:

/// A model for the current user.
struct User: Identifiable, Codable {
    var id: String
    var fbId: String
    var name: String
    var email: String
    var profilePicURL: URL?
}

The Problem

Right now, as you can see in my listen() method, I'm using Firebase's photoURL to get user's profile picture. However, it will only give me a low-quality thumbnail pic.

I would love to fetch normal photos from Facebook.

What I have tried

I tried, in my facebookLogin() to call GraphRequest to get the picture url. However, since my function is synchronous, I couldn't store the result to my User model.

I also tried directly using the Graph API link like "http://graph.facebook.com/user_id/picture?type=normal", but it seems like it's no longer the safe/suggested practice.

Question

Given my ViewModel structure, what is the best way to fetch and store Facebook user picture URL to my User model?


Solution

  • I found out that Firebase's photoURL is Facebook's Graph API URL: http://graph.facebook.com/user_id/picture. So, to get other sizes, all I need to do is to append a query string like ?type=normal to the photoURL.

    To use GraphRequest, consider @jnpdx's suggestion:

    Seems like you have at least a couple of options: 1) Use the GraphRequest and don't set your User model until you get the result back. 2) Set your User model as you are now and then update it with a new URL once your GraphRequest comes back.