Home > Blockchain >  Best way to work with store hours in Swift
Best way to work with store hours in Swift

Time:02-24

I'm building an app in Swift that will take in a store's hours from Firebase and want to represent it in my code as something like "monday.close" to get 02:30. What's the best way to do something like this? Pic below is what the data looks like in Firebase.

Firebase Data

init(dictionary: [String: Any]) {
    //initialize food item here
    self.name = dictionary["name"] as? String ?? ""
    self.address = dictionary["address"] as? String ?? ""
    self.restaurantLogo = UIImage(named: "")
    self.menuItems = dictionary["menuItems"] as? [Food] ?? []
    self.deliverUberEats = dictionary["uberEatsLink"] as? String ?? ""
    self.deliverDoorDash = dictionary["doordashLink"] as? String ?? ""
    self.deliverGrubhub = dictionary["grubhubLink"] as? String ?? ""
    self.restaurantID = ""
    self.priceRange = dictionary["price"] as? Int ?? 0
    self.hoursOpen = dictionary["hours"] as? [String : [String: String]]
}

struct RestaurantHours {
    var MonOpen: Date?
    var MonClose: Date?
}

CodePudding user response:

I see you already have a dictionary to store the Firebase opening hours, called hoursOpen.

Instead of using a notation like "monday.open", one option you have is to keep that dictionary as your source of data and retrieve the hours using a function, like this:

    func restaurantHours(day: String, when: String) -> String {
        let safeWhen = hoursOpen[day.lowercased()] ?? ["open": "closed"]
        let safeTime = safeWhen[when.lowercased()] ?? "closed"
        return safeTime
    }

Here's how you use it (but correct Firebase or parse your data to keep all weekdays lowercased!):

Text(restaurantHours(day: "Monday", when: "open"))    // 02.30
Text(restaurantHours(day: "MondAy", when: "close"))   // 22.00
Text(restaurantHours(day: "Tuesday", when: "open"))   // 02.30
Text(restaurantHours(day: "frIDAy", when: "clOSe"))   // closed

CodePudding user response:

Generally you can solve this by using Decodable protocol. I don't know how your Firebase database / node structure, so I will go with some simple example.

First you can make a helper method that is going to convert your JSON to data to a model of specified type.

func parseData<T: Decodable>(type: T.Type, data: Any) -> Any? {
    do {

        if case Optional<Any>.none = data {
            return nil
        }
        if data is NSNull {
            return nil
        }
        
        let modelJSON = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
        let jsonDecoder = JSONDecoder()
        return try jsonDecoder.decode(T.self, from: modelJSON)
    } catch let decodingError {
        return decodingError
    }
}

Then lets say we have some JSON that comes from Firebase and you want to make Model class based on it ( lets use some standard auth server response):

// MARK: - AuthDataModel
struct AuthDataModel: Codable {
    let tokenType: String
    let accessToken: String
    let refreshToken: String

    // If needed, this is how You can match key-names with what you get from Firebase/Server response
    enum CodingKeys: String, CodingKey {
        case tokenType = "token_type"
        case accessToken = "access_token"
        case refreshToken = "refresh_token"
    }
}

So instead of AuthDataModel here, You will have your Firebase node structure based model (if You give me JSON that describe your node, I could suggest you how how it should look).

And then, just when you receive snapshot from Firebase, you do something like this:

func getFirebaseData(completion: @escaping (AuthDataModel?)->Void, failure: @escaping (Error?)->Void) {
   
    yourNodeReference?.getData(completion:  { error, snapshot in
        guard error == nil else {
            print(error?.localizedDescription)
            failure(error)
            return
        }
        if let value = snapshot.value {
            if let data = self.parseData(type: AuthDataModel.self, data: value) as? AuthDataModel {
                completion(data)
            } else {
                failure(nil) 
            }
        }else{
            //whatever you like
        }
    })
}

This way you have created you model object, and you can access its properties later by authModel.property eg. authModel.accessToken.

Now when it comes to Firebase, iirc, null values can't be stored. So, you will have a case that a model that you define will not be decoded correctly by Codable protocol, cause one of the keys will be missing.

You can handle that with writing custom init in your model class that conforms to Codable.

Something like this:

 init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            self.exampleProperty = try values.decode(String.self, forKey: .exampleProperty)
            
   if values.contains(.someFieldThatCanBeMissing) {
        let value = try values.decodeIfPresent(TypeOfThatField.self, forKey: .someFieldThatCanBeMissing)
       self.someFieldThatCanBeMissing = value
       } else {
        self.someFieldThatCanBeMissing = nil
    }
 }

Also I would maybe go with a bit different kind of node structure. Something like this:

{
  
    "workingDays" : [ {
      "close" : "22:30",
      "name" : "Monday",
      "open" : "10:30"
    }, {
      "close" : "22:00",
      "name" : "Thuesday",
      "open" : "10:00"
    } ]
  
}

and then I would model my Model class like this:

struct MySpecificNodeModel: Codable {
    let workingDays: [WorkingDay]
}

// MARK: - WorkingDay
struct WorkingDay: Codable {
    let close, name, open: String

    enum CodingKeys: String, CodingKey {
        case close, name, open
        
    }
}

So I have put all working days in an array. You can of course go with a way you do, to have name of days as keys.

  • Related