Datastore.save not returning any response

Backendless Version (3.x / 5.x, Online / Managed / Pro )

5.x

Client SDK (REST / Android / Objective-C / Swift / JS )

BackendlessSwift 5.5.3

Application ID

130CA81A-36A3-9479-FFEE-AB623F4D0E00

Expected Behavior

When executing the following code

                    let dataStore = backendless.data.of(Alert.self)
                dataStore.save(entity: currentAlert, responseHandler: { (savedAlert) in
                    print("Alert has been saved: \(savedAlert)")
                }) { (fault) in
                    print("Server reported error: \(fault)")
                }
                print("should have saved alert")

The object currentAlert should be saved to backendless data table. The response handler should return the object savedAlert from which the objectId can be retrieved.

Actual Behavior

The above code successfully saves the data object in backendless table. The response handler does not return any value. There is also no error returned.

Hi, Mitchell!

I’ve checked saving new object with the next code:

@objcMembers class MyAlert: NSObject {
    var objectId: String?
    var title: String?
    var count = 42
}

func saveObject() {
    let newAlert = MyAlert()
    newAlert.title = "Test Title"
    
    Backendless.shared.data.of(MyAlert.self)
        .save(entity: newAlert, responseHandler: {
            print("Did saved new alert: \($0)"
        }, errorHandler: {
            print("Error: \($0)")
        })
}

And got Did saved new alert: <BECheck.MyAlert: 0x6000019cf3c0> in logs.

ResponseHandler isn’t called in your case, is it?
Can you show the class, which you’re trying to save?

Hi Andrii,

Yeah in my case the ResponseHandler is not called at all. The class is as follows:

import Foundation

@objcMembers
class Alert: NSObject, NSCoding {
    
    // MARK: Properties
    
    var deviceId: String
    var coin: String
    var criteria: String
    var value: Double
    var dateCreated: String
    var enabled: Bool
    var objectId : String?
    var price: Bool?
    var volAUD: Bool?
    var volUSD: Bool?

    
    // MARK: Archiving paths
    
    static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("Alerts")
    
    // MARK: Types
    
    struct PropertyKey {
        static let coin = "coin"
        static let criteria = "criteria"
        static let value = "value"
        static let dateCreated = "dateCreated"
        static let enabled = "enabled"
        static let objectId = "objectId"
        static let deviceId = "deviceId"
        static let price = "price"
        static let volAUD = "volAUD"
        static let volUSD = "volUSD"
        
    }
    
    // MARK: Initialisation
    
    override init() {
        self.coin = "coin"
        self.criteria = "criteria"
        self.value = 0
        self.dateCreated = "dateCreated"
        self.enabled = true
        self.objectId = "object"
        self.deviceId = "deviceId"
        self.price = false
        self.volAUD = false
        self.volUSD = false

        
    }
    
    init?(deviceId: String, coin: String, criteria: String, value: Double, dateCreated: String, enabled: Bool, objectId: String?, price: Bool?, volAUD: Bool?, volUSD: Bool?) {
        
        self.deviceId = deviceId
        self.coin = coin
        self.criteria = criteria
        self.value = value
        self.dateCreated = dateCreated
        self.enabled = enabled
        self.objectId = objectId
        self.price = price
        self.volAUD = volAUD
        self.volUSD = volUSD

    }

    // MARK: NSCoding
    func encode(with aCoder: NSCoder) {
        aCoder.encode(deviceId, forKey: PropertyKey.deviceId)
        aCoder.encode(coin, forKey: PropertyKey.coin)
        aCoder.encode(criteria, forKey: PropertyKey.criteria)
        aCoder.encode(value, forKey: PropertyKey.value)
        aCoder.encode(dateCreated, forKey: PropertyKey.dateCreated)
        aCoder.encode(enabled, forKey: PropertyKey.enabled)
        aCoder.encode(objectId, forKey: PropertyKey.objectId)
        aCoder.encode(price, forKey: PropertyKey.price)
        aCoder.encode(volAUD, forKey: PropertyKey.volAUD)
        aCoder.encode(volUSD, forKey: PropertyKey.volUSD)
        
        
    }
    
    required convenience init?(coder aDecoder: NSCoder) {
        
        let deviceId = aDecoder.decodeObject(forKey: PropertyKey.deviceId) as! String
        let coin = aDecoder.decodeObject(forKey: PropertyKey.coin) as! String
        let criteria = aDecoder.decodeObject(forKey: PropertyKey.criteria) as! String
        let value = aDecoder.decodeDouble(forKey: PropertyKey.value)
        let dateCreated = aDecoder.decodeObject(forKey: PropertyKey.dateCreated) as! String
        let enabled = aDecoder.decodeBool(forKey: PropertyKey.enabled)
        let objectId = aDecoder.decodeObject(forKey: PropertyKey.objectId) as? String
        let price = aDecoder.decodeObject(forKey: PropertyKey.price) as? Bool
        let volAUD = aDecoder.decodeObject(forKey: PropertyKey.volAUD) as? Bool
        let volUSD = aDecoder.decodeObject(forKey: PropertyKey.volUSD) as? Bool
        
        self.init(deviceId: deviceId, coin: coin, criteria: criteria, value: value, dateCreated: dateCreated, enabled: enabled, objectId: objectId, price: price, volAUD: volAUD, volUSD: volUSD)
    }
}

I also tried running the code you provided:

        @objcMembers class MyAlert: NSObject {
            var objectId: String?
            var title: String?
            var count = 42
        }

            let newAlert = MyAlert()
            newAlert.title = "Test Title"
        
        Backendless.shared.data.of(MyAlert.self)
        .save(entity: newAlert, responseHandler: {
            print("Did saved new alert: \($0)")
        }, errorHandler: {
            print("Error alert: \($0)")
        })

I am getting the same behaviour. Object is saved to backendless table but no responseHandler

I have had a look through the SDK code to try and see what is happening. The function save in DataStoreFactory.swift

public func save(entity: Any, responseHandler: ((Any) -> Void)!, errorHandler: ((Fault) -> Void)!) {
    let entityDictionary = persistenceServiceUtils.entityToDictionary(entity: entity)
    let wrappedBlock: ([String: Any]) -> () = { responseDictionary in
        if let resultEntity = self.persistenceServiceUtils.dictionaryToEntity(dictionary: responseDictionary, className: self.persistenceServiceUtils.getClassName(entity: self.entityClass)) {
            responseHandler(resultEntity)
            print("did get resultEntity")
        }
    }        
    persistenceServiceUtils.save(entity: entityDictionary, responseHandler: wrappedBlock, errorHandler: errorHandler)
}

I never receive the message “did get resultEntity”. In addition, if I change the if let resultEntity to let resultEntity I receive a response in the responseHandler but it is nil

Hello Mitchell,

Tried to save a new object and everything works fine - I’ve took your class

@objcMembers class Alert: NSObject, NSCoding

and your code

let dataStore = backendless.data.of(Alert.self)
dataStore.save(entity: currentAlert, responseHandler: { (savedAlert) in
    print("Alert has been saved: \(savedAlert)")
}) { (fault) in
    print("Server reported error: \(fault)")
}
print("should have saved alert")

and here is the result:

I the issue still exists for you please provide the minimal sample that reproduces the issue.

Regards,
Olha

Hi Olha,

To create a test case I downloaded the ‘project template’ from the Backendless console. I then edited the following function:

func saveTestObject() {
        
        @objcMembers class MyAlert: NSObject {
             var objectId: String?
             var title: String?
             var count = 42
         }

             let newAlert = MyAlert()
             newAlert.title = "Test Title"
         
         Backendless.shared.data.of(MyAlert.self)
         .save(entity: newAlert, responseHandler: {
             print("Did saved new alert: \($0)")
         }, errorHandler: {
             print("Error alert: \($0)")
         })
        
        
        dataStore = Backendless.shared.data.ofTable("TestTable")
        testObject = ["foo" : "Hello World"]

        dataStore?.save(entity: testObject, responseHandler: { savedTestObject in
            DispatchQueue.main.async {
                self.objectSavedLabel.text = "Object has been saved in the real-time database"
                self.liveUpdateObjectPropertyLabel.text = "Live update object property"
                self.propertyLabel.text = savedTestObject["foo"] as? String
            }
            self.testObject = savedTestObject
            let eventHandler = self.dataStore?.rt
            if let savedObjectId = savedTestObject["objectId"] as? String {
                let whereClause = String(format: "objectId = '%@'", savedObjectId)
                print("objectID: \(savedObjectId)")
                let _ = eventHandler?.addUpdateListener(whereClause: whereClause, responseHandler: { updatedTestObject in
                    if updatedTestObject["foo"] != nil {
                        self.propertyLabel.text = updatedTestObject["foo"] as? String
                    }
                }, errorHandler: { fault in
                    self.showErrorAlert(fault)
                })
            }
        }, errorHandler: { fault in
            self.showErrorAlert(fault)
        })
    }

In this case I successful receive the savedObjectId from the data saved to TestTable. I am still not receiving the response for the call to save to MyAlert table

Hi,

I have modified my code to convert from the Alert class to a [String : Any] data structure. See updated code below:

let dataStore = backendless.data.ofTable("Alert")
                    
                    var alertDataStructure: [String : Any]!
                    
                    alertDataStructure = ["deviceId":deviceID,"coin":alert.coin,"criteria":alert.criteria,"value":alert.value,"dateCreated":alert.dateCreated,"enabled":alert.enabled,"price":alert.price,"volAUD":volAUD,"volUSD":volUSD]
                    
                    dataStore.save(entity: alertDataStructure, responseHandler: { (savedAlert) in
                        if let savedObjectId = savedAlert["objectId"] as? String {
                            print("alert savedObjectId = \(savedObjectId)")
                            self.alerts[tableRow].objectId = savedObjectId
                            self.saveAlerts()
                        }
                        
                    }) { (fault) in
                        print("Server alert reported error: \(fault)")
                    }
                    print("should have saved alert")

By calling let dataStore = backendless.data.ofTable("Alert") rather than let dataStore = backendless.data.of(Alert.self) I am now successfully receiving the ObjectId in the response handler.

Not sure why you cannot reproduce the issue, but it seems that I can use this method as a work around for now.

Can you find PersistenceServiceUtils.swift file in SDK, set breakpoint at the 522th line and check value of resultEntityTypeName variable after calling save()?

Hello @mitchell,

  1. I’ve connected to your Application
  2. I’ve created an Alert class you’ve described here
  3. I’ve added a test method to save a new Alert object:
let newAlert = Alert()
newAlert.coin = "TEST"
        
Backendless.shared.data.of(Alert.self).save(entity: newAlert, responseHandler: { savedAlert in
    guard let savedAlert = savedAlert as? Alert else { return }
    print("Did saved new alert")
}, errorHandler: { fault in
    print("Error alert: \(fault.message ?? "")")
})
  1. I’ve received this response: Did saved new alert
  2. The new Alert object also appeared in the console:

Please check your project once more.
I’ve deleted my Alert object from your database after this test.

Regards,
Olha