2

I have a struct holding different types of data and I would like to write it to Firebase. Some properties of my structure can be nil, therefore they should be written in the database.

How do I handle nil values when writing to Firebase?

 struct Booking{

  let bookingNumber:String
  let bookingCompleted:Bool
  let cancelledBy:[String:AnyObject]?

       init(bookingNumber:String,
          bookingCompleted:String){
         self.bookingNumber = bookingNumber
         self.bookingCompleted = bookingCompleted
        }

      init(cancelledBy:[String:AnyObject]){
           self.cancelledBy = cancelledBy
        }
}

class DisbursePayment {

   override func viewDidLoad() {
    super.viewDidLoad()
   //how to handle if cancelledBy is nil? It will throw error :  unexpectedly 
   //found nil while unwrapping an Optional value
   //How should I structure my data in `struct Booking`?
   let item = Booking(cancelledBy: valueCouldBeNil)  
  }
}
mugx
  • 9,869
  • 3
  • 43
  • 55
bibscy
  • 2,598
  • 4
  • 34
  • 82
  • First get rid of all those implicitly unwrapped optionals. It they can't be nil just make them non optional otherwise declare them as regular optionals `?` – Leo Dabus Dec 15 '17 at 01:28
  • @LeoDabus if I get rid of the optionals then in the initializers the compiler will complain "Return from initializer without storing all stored properties" – bibscy Dec 15 '17 at 01:34
  • Like I said those that can be nil declare them as regular optionals – Leo Dabus Dec 15 '17 at 01:35
  • Btw Swift native dictionary type since Swift 3 is `[String:Any]` – Leo Dabus Dec 15 '17 at 01:36
  • 1
    Side note about your properties naming: `bookingNumber` is redundant you should name it `number`. The same applies to `bookingCompleted`, change it to just `completed` – Leo Dabus Dec 15 '17 at 01:39
  • @LeoDabus if I mark the properties as `?` it will still trigger runtime error when `cancelledBy` is nil. fatal error: unexpectedly found nil while unwrapping an Optional value – bibscy Dec 15 '17 at 01:42
  • If you don't know what an optional type means you should take some time and read about it. Btw When using structures you don't need to create the initialisers most of the time and about the properties you should declare them as constants and if you ned to change a property value just create a new object with the correct values and discard the old one. – Leo Dabus Dec 15 '17 at 01:48
  • change your initializer property to optional type also. Btw Which properties that really can be nil? looks like only the `cancelledBy`. – Leo Dabus Dec 15 '17 at 01:50
  • Again I would make all properties constants but if you really want to work with the same object try `struct Booking {` `let number: String` `var completed: Bool` `var cancelledBy: [String: Any]?` `init(number: String, completed: Bool, cancelledBy: [String: Any]? = nil) {` `self.number = number` `self.completed = completed` `self.cancelledBy = cancelledBy` } }` – Leo Dabus Dec 15 '17 at 01:53
  • @LeoDabus Now I understand, you need to initialized the property with nil so that any instance create later will have nil value for that property, otherwise it will trigger run time error e.g `init(cancelledBy: [String: Any]? = nil)`. Also structures receive a default initializer and if the properties are declared optional, then you can omit them in the newly created instance of that structure. I hope I understood it now. Many thanks – bibscy Dec 15 '17 at 02:12
  • @LeoDabus could you help me out with a question I have posted? https://stackoverflow.com/questions/49286164 . Many thanks – bibscy Mar 15 '18 at 18:12

1 Answers1

1

From the comments it seems as though you have already figured out a solution, I’d like to add the following information for other’s as reference:

You should not be storing nil values in firebase (or any database for that matter as it breaks 1NF for repeating values) if a value is not required, it should only be set to the database if it exists. Otherwise initialize it as nil and use Optional Chaining to safely unwrap the value if it exists.

Consider the following object that initializes it’s non-required property ‘title’ to nil

Class CustomObject: NSObject {
     //…
     var title: String? = nil
     //…
}

Setting

When setting the value in firebase we can use optional chaining to set the value if it exists, otherwise, do not add it to the values to be saved to the child object’s reference in firebase and remove the current value if it exists.

   //Create a new reference to Firebase Database
   var ref: DatabaseReference!
   ref = Database.database().reference().child(<#your child id#>)

    //Make a Dictionary of values to set
    var values:[String:Any] = [:]

    //Set required values like uuid or other primary key

    //Use Optional Chaining to set the value
    //Note that if title is nil 
    //it doesn’t override any existing value in firebase for title i.e. the old value will still remain.
    if let title = myObject.title {
            values["title"] = title
          } else {
            //if the value was previously set but now is not, 
            //we should update firebase by removing the value.
            ref.child("title").removeValue()
         }

    //…

    //Finally, push the remaining values to firebase to up date the child
    ref.updateChildValues(values)

Getting

To get the value from firebase, we again use Optional Chaining to see if a value for the given key exists, here I am accessing the object by its child path, your query may be different but the concept is the same.

    //Create a new reference to Firebase Database
    var ref: DatabaseReference!
    ref = Database.database().reference().child(<# child path #>)
    ref.queryOrderedByValue().observeSingleEvent(of: .value) { (snapshot) in
            if (snapshot.value is NSNull) {
                print("No Items to Fetch")
            } else  {
                //enumerate over the Objects
                for child in snapshot.children.allObjects as! [DataSnapshot] {
                    if let object = child.value as? [String : AnyObject] {

                           let myObject = CustomObject()

                            if let title = object["title"] as? String {
                                   myObject.title = title
                              }
                               //If There is no value for 'title' it will not be set.
                               //…


//Then use the value as you normally would…
if (myObject.title != nil) {//..}
RLoniello
  • 2,309
  • 2
  • 19
  • 26