6

I'm searching for a clean way to retrieve (and sometimes save) data from Firebase in Swift. It's annoying me that all my database calls are written in the middle of the view controller code. So I'm looking for some kind of custom data service class. I found this tutorial that's close to what I want: http://www.mobilecyberpunks.com/?p=82.

They promised a Part II but I cannot find this second part, so I guess this was never made. In this second part they promised to cover retrieving and saving data with this custom data service (which is the most important part of the whole thing for me).

I'm thinking of an API class (like in the tutorial) and when I'm retrieving data, and it finishes retrieving from firebase, I save it in a data set in this api class. Then I will posting a notification with Notification Center. But I'm am not sure whether this is best practice or a good way to do this.

Has anyone an idea how to do this (finishing this tutorial I found or in another way)?

Thanks in advance!

Dravidian
  • 9,945
  • 3
  • 34
  • 74
Charlotte1993
  • 589
  • 4
  • 27
  • Interesting question indeed, looking forward to the answers. An interesting followup question would be the strategies to achieve backend service independence so that you could use your custom backend API in your applications and it would be configurable to use Firebase, Parse, Backendless or any other service. – xpereta Oct 18 '16 at 08:19
  • Seems you got that right, what exactly are you looking for? How to communicate between a firebase class with your viewControllers? – Dravidian Oct 18 '16 at 11:42
  • @Dravidian yes that's correct. I'm looking for a way to communicate between Firebase class with my ViewControllers, but get rid of all the callbacks of the Firebase methods. Wrapping those methods up in a separate class. that class contains for example a method getUsers() -> [User] or just getUsers (first one is preferred though). – Charlotte1993 Oct 18 '16 at 11:52
  • 1
    @xpereta I worked out a handy structure for this (first part of the question: custom data service class). For the code, see the accepted answer I posted. For your follow-up question: very interesting, but at the moment it would be pretty useless to configure this for Parse, because Parse is shutting down in januari of 2017. I haven't tried other services, because Firebase has everything I need up until now. But if it comes across my work, and I develop such a structure, I will keep you (and everyone else) posted on this page! – Charlotte1993 Nov 23 '16 at 12:55

2 Answers2

4

Making a custom Class for the communicating is generally a good idea if you need extensive function's and make numerous calls to your server.

The two preferred methods for this are:-

  • Protocol-Delegate Method

  • _completionBlocks:

Below answer contains both.

Custom Class

import Foundation
import Firebase

@objc protocol FIRShowAlertDelegate {
    func showFIRAlert(_ message : String)
    @objc optional func activityIndic()
    }
class FIRController :{

  var delegate  : FIRShowAlertDelegate!

  func loginUser(_ emailAddress : String!, password : String , completionBlock : @escaping ((currentUserID : String!) -> Void)){

    FIRAuth.auth()?.signIn(withEmail: emailAddress, password: password,

                                    completion: {(user,err) in

                                        if err != nil{

                                            self.delegate.showFIRAlert("Error logging you in,\(err?.localizedDescription)")

                                             }else{

                                            completionBlock(user!.uid)
                                         }

                        })
        }

func retrieveUserData(_ currentId : String!, completionBlock : @escaping ((_ userName : String?) -> Void)){
  FIRDatabase.database().reference().child("Users").child(currentId).observeSingleEvent(of: .value, with: {(userSnap) in

        if userSnap.exists(){

            if let userDict = userSnap.value! as? [String:AnyObject]{
                 completionBlock(userDict["username"] as! String
            }
        }else{

            completionBlock(nil, nil)
            print("No such user exists: \(currentId)")
        }
    })
 }


} 

Your ViewController

class AnyViewController : UIViewController, FIRShowAlertDelegate{

    let firebaseControllerHandle  : FIRController = FIRController()

    override func viewDidLoad() {

    super.viewDidLoad()

         firebaseControllerHandle.delegate = self
         firebaseControllerHandle.loginUser("abc@xyz.com", password: "123454321", completionBlock: { (userID) in 
            print("user : \(userID), logged in")
        })       

        }
     func showFIRAlert(_ message : String){

       let alertController : UIAlertController = UIAlertController(title: "MyApp", message: message, preferredStyle: .alert)
       let okAction : UIAlertAction = UIAlertAction(title: "Ok", style: .default) { (alert) in
           print("User pressed ok function")
          }
       alertController.addAction(okAction)
       alertController.popoverPresentationController?.sourceView = view
       alertController.popoverPresentationController?.sourceRect = view.frame
       self.present(alertController, animated: true, completion: nil)

    }

    func activityIndic() {
       // Use for showing the activity indicator while the data is being retrieved
     }
    }
Dravidian
  • 9,945
  • 3
  • 34
  • 74
  • Thank you! This is exactly what I wanted! I used completion blocks, so I still have completion blocks in my ViewController, but way less code. All the code in the originally callback in the ViewController moved to th e – Charlotte1993 Oct 19 '16 at 11:13
  • Sorry, pressed enter too soon. So all the code in the originally callback moved to my custom class FirebaseAPI. Thanks! – Charlotte1993 Oct 19 '16 at 11:16
4

I started to use this solution and polished it a little bit, and I came to a pretty handy solution.

I created a custom class named FirebaseAPI. This is a singleton class. This class contains all the methods for Firebase (Authentication, Database, Storage, ...).

Example:

FirebaseAPI.swift

import FirebaseAuth
import FirebaseDatabase 

class FirebaseAPI {
    static let shared = FirebaseAPI()

    private init() {}

    //Authentication
    func logInUser(onCompletion: @escaping (String?) -> Void {
        FIRAuth.auth().signInAnonymously(completion: {(user, error) in 
            if error == nil {
                onCompletion(user!.uid)
            } else {
                onCompletion(nil)
            }
        })
    }

    //Database
    func getObjects(parameter: ParamaterClass, onCompletion: @escaping ([ObjectClass]) -> Void) {
        Constants.Firebase.References.Object?.observe(.value, with: { snapshot in
            var objects = [ObjectClass]()

            if snapshot.exists() {
                for child in snapshot.children.allObjects {
                    let object = Object(snapshot: child as! FIRDataSnapshot)
                    objects.append(object)
                }
            }
            onCompletion(objects)
        })
    }
}

Constants.swift

import FirebaseDatabase 

struct Constants {
    struct Firebase {
        static var CurrentUser: FIRDatabaseReference?
        static var Objects: FIRDatabaseReference?
    }
}

AppDelegate.swift

import UIKit
import Firebase 

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        FIRApp.configure()

        FirebaseAPI.shared.logInUser(onCompletion { uid in 
            if uid != nil {
                Constants.Firebase.References.CurrentUser = FIRDatabase.database().reference().child("users").child(uid!)
                Constants.Firebase.References.CurrentUser.keepSynced(true)

               Constants.Firebase.References.Objects = FIRDatabase.database().reference().child("objects")
               Constants.Firebase.Reference.Objects?.keepSynced(true)
            }
        })
    }
    return true
}

I can give you a example of calling methods in the FirebaseAPI in a ViewController, but an example of such a method is given in the code of the AppDelegate.swift up here (the FirebaseAPI.shared.logInUser method).

Used this structure in 3 different projects up till now and it works fluently!

Charlotte1993
  • 589
  • 4
  • 27
  • 2
    While your code is okay, I remember my CTO(boss) who is very experienced in iOS app development (both game and enterprise apps), told me that I should not get my AppDelegate class bloated. Anyway, you are using an Observe not SingleObserve of Firebase, and it seems you're not removing the observers. I've been using custom classes/API service for my apps connected to Firebase, but only for SingleObserveEvent. I'm posting a question about this, you might wanna answer or get interested. – Glenn Posadas Jan 22 '17 at 08:29
  • Hey, any explanation as to why you're writing it in a singleton pattern? – TheBen Feb 25 '17 at 17:56
  • Can you share how you use it in a VC if there are differences with the AppDelegate (like perhaps you don't want to have ALL objects for the VC? @Charlotte1993 – jessi Nov 16 '17 at 03:49