From parse server to local sqlite (Swift 3)

403 views Asked by At

In my app, I have a local SQLite database and use a parse server (Back4App.com). The reason for the two is to allow sync between user devices and if a user deletes and later reinstalls app, can download data.

I am using the parse sign function. After they sign in, I check to see if the local database has data, if not, I go to Parse and download from there then to insert data into local database.

The strange thing is the sequence of events is not happening as I would expect and this is where my issue is.

SigninVC is below:

@IBAction func signinButton(sender: UIButton) {

    activityIndicator.startAnimating()

    // hides keyboard
    self.view.endEditing(true)


    // if username or password is empty
    if usernameTextField.text!.isEmpty || passwordTextField.text!.isEmpty {


        SCLAlertView().showError("Opps".localized, subTitle: "missingDetail1".localized)

    }

    // login functions
    PFUser.logInWithUsername(inBackground: self.usernameTextField.text!, password: self.passwordTextField.text!) {
        (user: PFUser?, error: Error?) -> Void in
        if let error = error {
            if let errorString = (error as NSError).userInfo["error"] as? String {
                self.activityIndicator.stopAnimating()
                NSLog(errorString);
                SCLAlertView().showError("Error".localized, subTitle: "loginError".localized)
            }
        } else {

            self.userDefault.set(PFUser.current()!.username, forKey: "username")
            self.userDefault.synchronize()
            self.activityIndicator.stopAnimating()
            self.signInCompleted ()
        }


        self.rlDatabase.checkForEmtpySpeciesDetail ()

        let datbaseCheckResult = self.userDefault.bool (forKey: "databaseEmpty")

        if datbaseCheckResult == true {

            print("No data in RLDatabase, now checking if Parse has Data")

            print("1 Calling downloadFromParse function")


            self.downloadFromParse { [weak self] in

                print("6 Calling insertParseToSQLite function")

                self?.rlDatabase.insertParseToSQLite()
            }


        } else {

//                print("Database Not empty, no update needed")

        }

    }
}

func downloadFromParse (_ completion: ()->()) {

    print("2 Starting downloadFromParse function")

    userDefault.bool (forKey: "parseHasData")

    var userCommonNameArray = parseArrayModel.userCommonNameArray
    var userCommonNameESArray = parseArrayModel.userCommonNameESArray
    var userCommonNameFRArray = parseArrayModel.userCommonNameFRArray
    var userCommonNameDEArray = parseArrayModel.userCommonNameDEArray
    var speciesNameArray = parseArrayModel.speciesNameArray
    var userNotesArray = parseArrayModel.userNotesArray


    let downloadQuery = PFQuery(className:"ReefLifeApps")
    downloadQuery.whereKey("username", equalTo: PFUser.current()!.username!)

    downloadQuery.findObjectsInBackground {(downloadedData, error) -> Void in
        if error == nil {

            print("3 begining downloadQuery")

            for downloadData in downloadedData! {

                userCommonNameArray.append(downloadData.object(forKey: "userCommonName") as! String)
                userCommonNameESArray.append(downloadData.object(forKey: "userCommonNameES") as! String)
                userCommonNameFRArray.append(downloadData.object(forKey: "userCommonNameFR") as! String)
                userCommonNameDEArray.append(downloadData.object(forKey: "userCommonNameDE") as! String)
                speciesNameArray.append(downloadData.object(forKey: "speciesName") as! String)
                userNotesArray.append(downloadData.object(forKey: "userNotes") as! String)

                print (speciesNameArray)

                print("4 finished downloadQuery")

            }

        } else {

            self.userDefault.set(false, forKey: "parseHasData")

//                print("No username registered in parse")

        }
    }

    if speciesNameArray.isEmpty == true {
        self.userDefault.set(false, forKey: "parseHasData")
    } else {
        self.userDefault.set(true, forKey: "parseHasData")
    }
    completion()

    print("5 finished downloadFromParse function")

}

RLDatabase.swift has the insert to SQLite

func insertParseToSQLite () {

    print("7 begining insertParseToSQLite function")

    let parseCheckResult = self.userDefault.bool (forKey: "parseHasData")

    if parseCheckResult == true {

        let userCommonNameArray = parseArrayModel.userCommonNameArray
        let userCommonNameESArray = parseArrayModel.userCommonNameESArray
        let userCommonNameFRArray = parseArrayModel.userCommonNameFRArray
        let userCommonNameDEArray = parseArrayModel.userCommonNameDEArray
        let speciesNameArray = parseArrayModel.speciesNameArray
        let userNotesArray = parseArrayModel.userNotesArray


            var statement: OpaquePointer? = nil

            let update = String(format:"UPDATE RL_Species SET user_common_name = '\(userCommonNameArray)', user_common_name_fr = '\(userCommonNameFRArray)', user_common_name_es = '\(userCommonNameESArray)', user_common_name_de = '\(userCommonNameDEArray)', user_notes = '\(userNotesArray)' WHERE RL_Species.specie = '\(speciesNameArray)';")

        print("8 starting SQLite3 insert")

            if sqlite3_prepare_v2(database, update, -1, &statement, nil) == SQLITE_OK {

                if sqlite3_step(statement) == SQLITE_DONE {
//                        print("Successfully updated database with Parse data.")
                } else {
//                        print("Could NOT Update database with Parse data.")
                }

            } else {

                // forces SQLite to send error messagesddd
                let errorMessage = String.init(validatingUTF8: sqlite3_errmsg(database))!
                print("update failed! \(errorMessage)")
        }

            sqlite3_finalize(statement)

    } else {

    }

    print("9 finishing insertParseToSQLite function")

}

ParseArrayModel.swift

struct ParseArrayModel
{
    var userCommonNameArray = [String]()
    var userCommonNameESArray = [String]()
    var userCommonNameFRArray = [String]()
    var userCommonNameDEArray = [String]()
    var speciesNameArray = [String]()
    var userNotesArray = [String]()

}

As you can see, the first call is to download from Parse, then call the insert function. However, what is happening is it goes to the downloadFromParse, but after the

downloadQuery.findObjectsInBackground {(downloadedData, error) -> Void in

it skips OVER the the remainder of the function, then goes to the insertToSQLite function and as there is no data in the array, nothing gets inserted. Then it goes back to the DownloadFromParse and then grabs the data. So, in the end, all the data is there, but in the wrong sequence of events.

Completely stumped. Any help is greatly appreciated.

EDIT: Added // print lines to show order after update.

Here is what the console displayed:

No data in RLDatabase, now checking if Parse has Data
1 Calling downloadFromParse function
2 Starting downloadFromParse function
6 Calling insertParseToSQLite function
7 begining insertParseToSQLite function
9 finishing insertParseToSQLite function
5 finished downloadFromParse function
3 begining downloadQuery
["Acanthaster planci"]
4 finished downloadQuery
2

There are 2 answers

0
David Sanford On BEST ANSWER

I am posting the results to help anyone whom may come across this issue of wanting to transfer data from a parse server to a local database, in my case SQLite.

Instead of trying to complete the download before calling the insertToSQLite func, I needed only to add the insertToSQLite func inside the query call.

Plus some other programmer errors on my side. Below are the results of what worked after testing:

var userCommonNameArray = String()
var userCommonNameESArray = String()
var userCommonNameFRArray = String()
var userCommonNameDEArray = String()
var speciesNameArray = String()
var userNotesArray = String()
var speciesIDArray = Int()

func txFromParseToSQLite () {

    let downloadQuery = PFQuery(className:"ReefLifeApps")

    downloadQuery.whereKey("username", equalTo: PFUser.current()!.username!)

    downloadQuery.findObjectsInBackground {(downloadedData, error) -> Void in
        if error == nil {

            if (downloadedData?.count == 0) {

                print("DownloadData.count == 0")

                return

            } else {

                if let downloadedData = downloadedData {
                    for downloadData in downloadedData {

                        self.userCommonNameArray = downloadData["userCommonName"] as! String
                        self.userCommonNameFRArray = downloadData["userCommonNameFR"] as! String
                        self.userCommonNameESArray = downloadData["userCommonNameES"] as! String
                        self.userCommonNameDEArray = downloadData["userCommonNameDE"] as! String
                        self.userNotesArray = downloadData["userNotes"] as! String
                        self.speciesNameArray = downloadData["speciesName"] as! String
                        self.speciesIDArray = downloadData["speciesID"] as! Int

                        print (downloadData)

                        self.insertParseToSQLite ()
                    }
                }
            }
        }
    }
}

func insertParseToSQLite () {

        var statement: OpaquePointer? = nil

        let update = String(format:"UPDATE RL_User SET user_common_name = '\(userCommonNameArray)', user_common_name_fr = '\(userCommonNameFRArray)', user_common_name_es = '\(userCommonNameESArray)', user_common_name_de = '\(userCommonNameDEArray)', user_notes = '\(userNotesArray)', speciesName = '\(speciesNameArray)' WHERE id = \(speciesIDArray);")

        if sqlite3_prepare_v2(databaseUser, update, -1, &statement, nil) == SQLITE_OK {

            if sqlite3_step(statement) == SQLITE_DONE {
            } else {
            }

        } else {
            let errorMessage = String.init(validatingUTF8: sqlite3_errmsg(databaseUser))!
            print("update failed! \(errorMessage)")
        }
    sqlite3_finalize(statement)
}
1
JustinM On

It's because your downloading async in the background so your code will keep executing while that process is ongoing. You will need to create a callback to let you know when the download is complete, then you can call the next function.

func downloadFromParse (_ completion: ()->()) {

userDefault.bool (forKey: "parseHasData")

var userCommonNameArray = parseArrayModel.userCommonNameArray
var userCommonNameESArray = parseArrayModel.userCommonNameESArray
var userCommonNameFRArray = parseArrayModel.userCommonNameFRArray
var userCommonNameDEArray = parseArrayModel.userCommonNameDEArray
var speciesNameArray = parseArrayModel.speciesNameArray
var userNotesArray = parseArrayModel.userNotesArray


let downloadQuery = PFQuery(className:"ReefLifeApps")
downloadQuery.whereKey("username", equalTo: PFUser.current()!.username!)

downloadQuery.findObjectsInBackground {(downloadedData, error) -> Void in
    if error == nil {

        for downloadData in downloadedData! {

            userCommonNameArray.append(downloadData.object(forKey: "userCommonName") as! String)
            userCommonNameESArray.append(downloadData.object(forKey: "userCommonNameES") as! String)
            userCommonNameFRArray.append(downloadData.object(forKey: "userCommonNameFR") as! String)
            userCommonNameDEArray.append(downloadData.object(forKey: "userCommonNameDE") as! String)
            speciesNameArray.append(downloadData.object(forKey: "speciesName") as! String)
            userNotesArray.append(downloadData.object(forKey: "userNotes") as! String)

            print (speciesNameArray)


        }

    } else {

        self.userDefault.set(false, forKey: "parseHasData")

        print("No username registered in parse")

    }
}

if speciesNameArray.isEmpty == true {
    self.userDefault.set(false, forKey: "parseHasData")
} else {
    self.userDefault.set(true, forKey: "parseHasData")
}
 completion()
}

then you can do something like this:

 downloadFromParse { [weak self] in
     // call next function knowing the parse download is complete.
 }