Search code examples
firebase-realtime-databaseswift3

Swift request for Firebase database is taking 19 seconds


19 seconds to hear from Firebase

!! UPDATE: Firebase might not be the problem (data gotten in half a second it seems on ViewController main screen although connection may still be a hold up [?]; also, on AddUniversal screen, adding a new entity to Firebase database triggers the same delay, suggesting Firebase is the problem [?] as there's no sizing or collection view or much of anything on that screen) - on ViewController screen, I thought problem might lie somewhere between obtaining of data and plugging it into collectionView cells (cardviews) as per Brian Voong model of vertical sizing. I'll leave the original code below, and some of the Brian Voong stuff at the end. !!

I have tried removing encryption/decryption process, removing invocation of GoogleAppEngine (encryption key request), and removal of image (only text requested; probably under 200kB). I've also tried Frank van Puffelen's suggestion to add a "on" listener in addition to my "once" listeners. Nothing works. This app will be unusable for customers if I don't get it fixed.

Example code:

//
//  MIProcessor.swift
//  Bizzy Books
//
//  Created by Brad Caldwell on 12/19/17.
//  Copyright © 2017 Caldwell Contracting LLC. All rights reserved.
//

import UIKit
import Firebase

final class MIProcessor {

static let sharedMIP = MIProcessor()
private init() {}

public var firstTime: Bool = false
public var mIP: [MultiversalItem] = [MultiversalItem]()
public var sIP: [MultiversalItem] = [MultiversalItem]() // the search mip!
public var mipORsip: Int = Int()
public var mIPUniversals: [UniversalItem] = [UniversalItem]()
public var mIPProjects: [ProjectItem] = [ProjectItem]()
public var mIPEntities: [EntityItem] = [EntityItem]()
public var mIPAccounts: [AccountItem] = [AccountItem]()
public var mIPVehicles: [VehicleItem] = [VehicleItem]()
public var trueYou: String = String()
public var isUserCurrentlySubscribed: Bool = Bool()
private var tHeKeY: Data!
var theUser: User!
var universalsRef: DatabaseReference!
var entitiesRef: DatabaseReference!
var projectsRef: DatabaseReference!
var vehiclesRef: DatabaseReference!
var accountsRef: DatabaseReference!
var keyRef: DatabaseReference!
var obtainBalanceAfter = ObtainBalanceAfter()
var obtainProjectStatus = ObtainProjectStatus()
var balOneAfter: Int = 0
var balTwoAfter: Int = 0
var balsAfter: [Int?] = [Int?]()
var masterSearchArray: [SearchItem] = [SearchItem]()
var authorized: Bool!
var theKeyIsHere: String!

func loadTheMip(completion: @escaping () -> ()) {
    mipORsip = 0 // MIP!
    //obtainTheKey {

        self.universalsRef = Database.database().reference().child("users").child(userUID).child("universals")
        self.projectsRef = Database.database().reference().child("users").child(userUID).child("projects")
        self.entitiesRef = Database.database().reference().child("users").child(userUID).child("entities")
        self.accountsRef = Database.database().reference().child("users").child(userUID).child("accounts")
        self.vehiclesRef = Database.database().reference().child("users").child(userUID).child("vehicles")
        self.mIPUniversals.removeAll()
        self.mIPProjects.removeAll()
        self.mIPEntities.removeAll()
        self.mIPAccounts.removeAll()
        self.mIPVehicles.removeAll()
        self.universalsRef.observeSingleEvent(of: .value, with: { (snapshot) in
            for item in snapshot.children {
                self.mIPUniversals.append(UniversalItem(snapshot: item as! DataSnapshot))
            }
            self.projectsRef.observeSingleEvent(of: .value, with: { (snapshot) in
                for item in snapshot.children {
                    self.mIPProjects.append(ProjectItem(snapshot: item as! DataSnapshot))
                }
                self.entitiesRef.observeSingleEvent(of: .value, with: { (snapshot) in
                    for item in snapshot.children {
                        self.mIPEntities.append(EntityItem(snapshot: item as! DataSnapshot))
                    }
                    self.accountsRef.observeSingleEvent(of: .value, with: { (snapshot) in
                        for item in snapshot.children {
                            self.mIPAccounts.append(AccountItem(snapshot: item as! DataSnapshot))
                        }
                        self.vehiclesRef.observeSingleEvent(of: .value, with: { (snapshot) in
                            for item in snapshot.children {
                                self.mIPVehicles.append(VehicleItem(snapshot: item as! DataSnapshot))
                            }
                            let youRef = Database.database().reference().child("users").child(userUID).child("youEntity")
                            youRef.observeSingleEvent(of: .value, with: { (snapshot) in
                                if let youKey = snapshot.value as? String {
                                    self.trueYou = youKey
                                    completion()
                                }
                            })
                        })
                    })
                })
            })
        })
    //}
}

This code gets called from ViewController.swift during the log-in procedure, as shown below:

func checkLoggedIn() {

    Auth.auth().addStateDidChangeListener { auth, user in

        if user != nil {
            // User is signed in.
            userUID = (user?.uid)!
            self.theUser = user
            if user?.photoURL == nil {
            }else{
                if let imageUrl = NSData(contentsOf: (user?.photoURL)!){
                    self.profilePic.image = UIImage(data: imageUrl as Data)
                } else {
                    self.profilePic.image = UIImage(named: "bizzybooksbee")
                }
            }
            self.masterRef = Database.database().reference().child("users").child(userUID)
            self.projectsRef = Database.database().reference().child("users").child(userUID).child("projects")
            self.entitiesRef = Database.database().reference().child("users").child(userUID).child("entities")
            self.accountsRef = Database.database().reference().child("users").child(userUID).child("accounts")
            self.vehiclesRef = Database.database().reference().child("users").child(userUID).child("vehicles")
            self.youEntityRef = Database.database().reference().child("users").child(userUID).child("youEntity")
            self.firstTimeRef = Database.database().reference().child("users").child(userUID).child("firstTime")
            self.masterRef.observe(.value, with: { (snapshot) in
                print("Do NOTTTT ANY thingggggg")
            })
            self.firstTimeRef.observeSingleEvent(of: .value, with: { (snapshot) in
                if snapshot.exists() {
                    print("Do NOT any THING")
                    if self.shouldEnterLoop {
                        self.shouldEnterLoop = false
                        self.loadTheMIP()
                    }
                } else {
                    self.initializeIfFirstAppUse()
                }
            })
            print("ONE!")
            self.masterRef.observe(.childChanged, with: { (snapshot) in // GENIUS!!!!! This line loads MIP only when an item gets added/changed/deleted (and exactly WHEN an item gets added/changed/deleted) in Firebase database IN REALTIME!!!!
                print("TWO!")
                if self.shouldEnterLoop {
                    self.shouldEnterLoop = false
                    print("THREE!")
                    if MIProcessor.sharedMIP.mipORsip == 0 {
                        self.loadTheMIP()
                    } else {
                        DispatchQueue.main.async {
                            MIProcessor.sharedMIP.loadTheMip {
                                MIProcessor.sharedMIP.obtainTheBalancesAfter()
                                MIProcessor.sharedMIP.loadTheStatuses()
                                MIProcessor.sharedMIP.updateTheMIP()
                                MIProcessor.sharedMIP.updateTheSIP(i: self.thingToBeSearchedInt, name: self.thingToBeSearchedName)
                                self.cardViewCollectionView.reloadData()//Critical line - this makes or breaks the app :/
                            }
                        }
                    }

                    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: {
                        self.shouldEnterLoop = true
                    })
                }
            })
            if MIProcessor.sharedMIP.mIP.count == 0 {
                print("FOUR!")
                if self.shouldEnterLoop {
                    self.shouldEnterLoop = false
                    print("FIVE!")
                    self.loadTheMIP()
                    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: {
                        self.shouldEnterLoop = true
                    })
                }
            }
        } else {
            // No user is signed in.
            self.login()
        }
    }
}

Brian Voong collectionView vertical sizing stuff...

//Brian Voong inspiration... see if we can get vertical sizing of collectionview cells ie cardviews
extension ViewController: UICollectionViewDelegateFlowLayout {

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let i = indexPath.item
    var baseHeight: CGFloat = 150
    var sentenceOneHeight: CGFloat = 0
    var sentenceTwoHeight: CGFloat = 0
    var phoneHeight: CGFloat = 0
    var emailHeight: CGFloat = 0
    var geoHeight: CGFloat = 0
    var ssnHeight: CGFloat = 0
    var einHeight: CGFloat = 0
    var imageHeight: CGFloat = 1
    switch MIProcessor.sharedMIP.mipORsip {
    case 1: // SIP!
        switch MIProcessor.sharedMIP.sIP[i].multiversalType {
        case 1: // Project
            baseHeight = 130
            if let projectItem = MIProcessor.sharedMIP.sIP[i] as? ProjectItem {
                if projectItem.projectNotes != "" {
                    if projectItem.projectNotes.count < 30 {
                        phoneHeight = 25
                    } else if projectItem.projectNotes.count < 60 {
                        phoneHeight = 45
                    } else {
                        phoneHeight = 80
                    }
                }
                if projectItem.projectTags != "" {
                    if projectItem.projectTags.count < 30 {
                        emailHeight = 30
                    } else if projectItem.projectTags.count < 60 {
                        emailHeight = 60
                    } else {
                        emailHeight = 90
                    }
                }
                if projectItem.projectAddressStreet != "" {
                    sentenceOneHeight = 140
                } else {
                    sentenceOneHeight = 100
                }
            }
            sentenceTwoHeight = 180
        case 2: // Entity
            baseHeight = 160 //92
            if let entityItem = MIProcessor.sharedMIP.sIP[i] as? EntityItem {
                if (entityItem.phoneNumber != "") || (entityItem.email != "") || (entityItem.city != "") {
                    phoneHeight = 118
                }
                if entityItem.ssn != "" {
                    ssnHeight = 30
                }
                if entityItem.ein != "" {
                    einHeight = 30
                }
            }
        case 3: // Account
            baseHeight = 140 //92
            if let accountItem = MIProcessor.sharedMIP.sIP[i] as? AccountItem {
                if accountItem.phoneNumber != "" {
                    phoneHeight = 30
                }
                if accountItem.email != "" {
                    emailHeight = 38
                }
                if accountItem.street != "" {
                    if accountItem.city != "" {
                        if accountItem.state != "" {
                            geoHeight = 50
                        }
                    }
                }
            }
        case 4: // Vehicle
            baseHeight = 140 //92
            if let vehicleItem = MIProcessor.sharedMIP.sIP[i] as? VehicleItem {
                if vehicleItem.licensePlateNumber != "" {
                    phoneHeight = 25
                }
                if vehicleItem.vehicleIdentificationNumber != "" {
                    emailHeight = 25
                }
                if vehicleItem.placedInCommissionDate != "" {
                    geoHeight = 25
                }
            }
        default: // Universal - Ie case 0 the most frequent
            var longString = ""
            if let universalItem = MIProcessor.sharedMIP.sIP[i] as? UniversalItem {
                if universalItem.notes != "" {
                    if universalItem.notes.count < 30 {
                        phoneHeight = 25
                    } else if universalItem.notes.count < 60 {
                        phoneHeight = 45
                    } else {
                        phoneHeight = 80
                    }
                }
                imageHeight = (CGFloat(universalItem.picAspectRatio) / 1000) * widthConstraintConstant
                switch universalItem.universalItemType {
                case 1: // Personal
                    baseHeight = 100
                    longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName
                case 2: // Mixed
                    baseHeight = 160
                    switch universalItem.taxReasonId {
                    case 2: // Labor ie wc
                        longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.workersCompName
                    case 5: // Vehicle
                        longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.vehicleName
                    case 6: // AdMeans
                        longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.advertisingMeansName
                    default: // Nothing important
                        longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName
                    }
                case 3: // Fuel
                    baseHeight = 70
                    longString = "At 234566 miles you paid $00.00 to " + universalItem.whomName + " for 00.000 gallons of 87 gas in your " + universalItem.vehicleName
                case 4: // Transfer
                    baseHeight = 150
                case 6: // Project Media
                    baseHeight = 80
                default:
                    baseHeight = 120
                    switch universalItem.taxReasonId {
                    case 2: // Labor ie wc
                        longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.workersCompName
                    case 5: // Vehicle
                        longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.vehicleName
                    case 6: // AdMeans
                        longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.advertisingMeansName
                    default: // Nothing important
                        longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName
                    }
                }
            }
            if longString.count < 15 {
                sentenceOneHeight = 80
            } else if longString.count < 40 {
                sentenceOneHeight = 110
            } else if longString.count < 65 {
                sentenceOneHeight = 140
            } else if longString.count < 80 {
                sentenceOneHeight = 170
            } else {
                sentenceOneHeight = 200
            }
        }
        let totalHeight = baseHeight + sentenceOneHeight + sentenceTwoHeight + phoneHeight + emailHeight + geoHeight + ssnHeight + einHeight + imageHeight
        var theWidth: CGFloat = 0
        if view.frame.width < 370 { //Protection for tiny iPhones
            theWidth = view.frame.width - 20
        } else { //Good for most iphones and safe for iPads
            theWidth = 350
        }
        return CGSize(width: theWidth, height: totalHeight)
    default: // I.e. case 0 MIP!
        switch MIProcessor.sharedMIP.mIP[i].multiversalType {
        case 1: // Project
            baseHeight = 130
            if let projectItem = MIProcessor.sharedMIP.mIP[i] as? ProjectItem {
                if projectItem.projectNotes != "" {
                    if projectItem.projectNotes.count < 30 {
                        phoneHeight = 25
                    } else if projectItem.projectNotes.count < 60 {
                        phoneHeight = 45
                    } else {
                        phoneHeight = 80
                    }
                }
                if projectItem.projectTags != "" {
                    if projectItem.projectTags.count < 30 {
                        emailHeight = 30
                    } else if projectItem.projectTags.count < 60 {
                        emailHeight = 60
                    } else {
                        emailHeight = 90
                    }
                }
                if projectItem.projectAddressStreet != "" {
                    sentenceOneHeight = 140
                } else {
                    sentenceOneHeight = 100
                }
            }
            sentenceTwoHeight = 180
        case 2: // Entity
            baseHeight = 160 //92
            if let entityItem = MIProcessor.sharedMIP.mIP[i] as? EntityItem {
                if (entityItem.phoneNumber != "") || (entityItem.email != "") || (entityItem.city != "") {
                    phoneHeight = 118
                }
                if entityItem.ssn != "" {
                    ssnHeight = 30
                }
                if entityItem.ein != "" {
                    einHeight = 30
                }
            }
        case 3: // Account
            baseHeight = 140 //92
            if let accountItem = MIProcessor.sharedMIP.mIP[i] as? AccountItem {
                if accountItem.phoneNumber != "" {
                    phoneHeight = 30
                }
                if accountItem.email != "" {
                    emailHeight = 38
                }
                if accountItem.street != "" {
                    if accountItem.city != "" {
                        if accountItem.state != "" {
                            geoHeight = 50
                        }
                    }
                }
            }
        case 4: // Vehicle
            baseHeight = 140 //92
            if let vehicleItem = MIProcessor.sharedMIP.mIP[i] as? VehicleItem {
                if vehicleItem.licensePlateNumber != "" {
                    phoneHeight = 25
                }
                if vehicleItem.vehicleIdentificationNumber != "" {
                    emailHeight = 25
                }
                if vehicleItem.placedInCommissionDate != "" {
                    geoHeight = 25
                }
            }
        default: // Universal - Ie case 0 the most frequent
            var longString = ""
            if let universalItem = MIProcessor.sharedMIP.mIP[i] as? UniversalItem {
                if universalItem.notes != "" {
                    if universalItem.notes.count < 30 {
                        phoneHeight = 25
                    } else if universalItem.notes.count < 60 {
                        phoneHeight = 45
                    } else {
                        phoneHeight = 80
                    }
                }
                imageHeight = (CGFloat(universalItem.picAspectRatio) / 1000) * widthConstraintConstant
                switch universalItem.universalItemType {
                case 1: // Personal
                    baseHeight = 100
                    longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName
                case 2: // Mixed
                    baseHeight = 160
                    switch universalItem.taxReasonId {
                    case 2: // Labor ie wc
                        longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.workersCompName
                    case 5: // Vehicle
                        longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.vehicleName
                    case 6: // AdMeans
                        longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName + universalItem.advertisingMeansName
                    default: // Nothing important
                        longString = universalItem.whoName + universalItem.whomName + universalItem.personalReasonName + universalItem.taxReasonName
                    }
                case 3: // Fuel
                    baseHeight = 70
                    longString = "At 234566 miles you paid $00.00 to " + universalItem.whomName + " for 00.000 gallons of 87 gas in your " + universalItem.vehicleName
                case 4: // Transfer
                    baseHeight = 150
                case 6: // Project Media
                    baseHeight = 80
                default:
                    baseHeight = 120
                    switch universalItem.taxReasonId {
                    case 2: // Labor ie wc
                        longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.workersCompName
                    case 5: // Vehicle
                        longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.vehicleName
                    case 6: // AdMeans
                        longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName + universalItem.advertisingMeansName
                    default: // Nothing important
                        longString = universalItem.whoName + universalItem.whomName + universalItem.taxReasonName
                    }
                }
            }
            if longString.count < 15 {
                sentenceOneHeight = 80
            } else if longString.count < 40 {
                sentenceOneHeight = 110
            } else if longString.count < 65 {
                sentenceOneHeight = 140
            } else if longString.count < 80 {
                sentenceOneHeight = 170
            } else {
                sentenceOneHeight = 200
            }
        }
        let totalHeight = baseHeight + sentenceOneHeight + sentenceTwoHeight + phoneHeight + emailHeight + geoHeight + ssnHeight + einHeight + imageHeight
        var theWidth: CGFloat = 0
        if view.frame.width < 370 { //Protection for tiny iPhones
            theWidth = view.frame.width - 20
        } else { //Good for most iphones and safe for iPads
            theWidth = 350
        }
        return CGSize(width: theWidth, height: totalHeight)
    }
}
}

Solution

  • I figured it out!!!

    Getting financial transaction data from Firebase was NOT the slow point.

    The problem was, AFTER getting the data, I ran through all the user's transactions starting with the starting bank balance and adjusted so that each transaction showed then-current bank balance. This apparently takes a heck of a lot of time to do for a couple thousand transactions.

    By setting all bank balances to $0.00 and placing the calculations in a background thread, and then updating UI once complete, it now only takes about a second to load (still wait 19 seconds to get current balances to update):

    DispatchQueue.global(qos: .background).async {
                    // do your job here
                    MIProcessor.sharedMIP.obtainTheBalancesAfter()
                    MIProcessor.sharedMIP.updateTheMIP()
    
                    DispatchQueue.main.async {
                        // update ui here
                        self.cardViewCollectionView.reloadData()
                    }
                }