Swift 4 and the Data API

Document created by Brian Hamm on Jul 11, 2018Last modified by Brian Hamm on Dec 30, 2018
Version 86Show Document
  • View in full screen mode

This is part of a SwiftFM service class I wrote for the Data API. To see a list of functions and how to call them, visit:

 

GitHub - starsite/SwiftFM: A service class for Swift to work with the FileMaker Data API

 

The following example will get you started checking the status of an existing token, refreshing expired tokens, and making sure you're passing active tokens in your requests, where possible. Fetching a new session token for every request is lazy. Don't be that guy.

 

1. A let is a constant, in Swift. During testing, you can hardcode baseURL and auth values as below, but best practice is to keep sensitive info (such as API keys, etc.) outside of Bundle.main. It's safer to fetch that information from elsewhere and park it in UserDefaults. I like to fetch my environment settings from CloudKit, in didFinishLaunching. Doing it this way also provides a remote kill-switch, if necessary.

class ViewController: UIViewController {

 

//  let baseURL = UserDefaults.standard.string(forKey: "fm-db-path")   // better

//  let auth    = UserDefaults.standard.string(forKey: "fm-auth")      // better

 

    let baseURL = "https: //<hostName>/fmi/data/v1/databases/<databaseName>"

    let auth    = "xxxxxxxabcdefg1234567"  // base64 "user:pass"

 

    var token   = UserDefaults.standard.string(forKey: "fm-token")

    var expiry  = UserDefaults.standard.object(forKey: "fm-token-expiry") as? Date ?? Date(timeIntervalSince1970: 0)

 

    // ... (cont'd)

 

 

2. Bool check to see if there's an existing token and whether or not it's expired. The _ means we aren't using (don't care about) the token value right now, we only care that there /is/ one.

    // active token?

    func isActiveToken() -> Bool {

        if let _ = self.token, self.expiry > Date() {

            return true

        } else {

            return false

        }

    }

 

 

3. Refresh an expired token. The @escaping marker allows the token and expiry types to be used later (they're permitted to "escape" or outlive the function). That's typical for async calls in Swift. We'll call this later, in viewDidLoad().

    // refresh token

    func refreshToken(for auth: String, completion: @escaping (String, Date) -> Void) {

       

        guard let baseURL = URL(string: self.baseURL) else { return }

      

        let url = baseURL.appendingPathComponent("/sessions")

 

        let expiry = Date(timeIntervalSinceNow: 900// 15 minutes

       

        var request = URLRequest(url: url)

        request.httpMethod = "POST"

        request.setValue("Basic \(auth)", forHTTPHeaderField: "Authorization")

        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

       

        // task

        URLSession.shared.dataTask(with: request) { data, _, error in

           

            guard   let data  = data, error == nil,

                    let json  = try? JSONSerialization.jsonObject(with: data) as! [String: Any],

                    let resp  = json["response"] as? [String: Any],

                    let token = resp["token"] as? String

            else {

                    print("refresh token sad")

                    return

            }

           

            // prefs

            UserDefaults.standard.set(token, forKey: "fm-token")

            UserDefaults.standard.set(expiry, forKey: "fm-token-expiry")

           

            completion(token, expiry)  // out^

           

        }.resume()

    }

 

 

4. This example shows an "or" request. Note the difference in payload for an "and" request. Set the payload from a UITextField (or hardcode a query, like this) and pass it as a parameter.

    // query

    var payload = ["query": [   // "or" request ->[[p1],[p2]]   "and" request ->[[p1,p2]]

        ["bandName": "Daniel Markham"],

        ["bandName": "Sudie"]

    ]]

 

    /// data api find request

    func findRequest(with token: String, layout: String, payload: [String: Any]) {

       

        guard   let baseURL = URL(string: self.baseURL),

                let body = try? JSONSerialization.data(withJSONObject: payload) else { return }

 

        let url = baseURL.appendingPathComponent("/sessions")

       

        var request = URLRequest(url: url)

        request.httpMethod = "POST"

        request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        request.httpBody = body

       

        // task

        URLSession.shared.dataTask(with: request) { data, _, error in

           

            guard   let data = data, error == nil,

                    let json = try? JSONSerialization.jsonObject(with: data) as! [String: Any]

            else {

                    print("api request sad")

                    return

            }

           

            print("\n\(json)"// disco!

           

        }.resume()

    }

 

 

5. Here, we check for an active token (self.token) and hand that to our find request. If the token is missing or expired, we fetch a new one and pass newToken instead. If you're new to Swift, viewDidLoad() is called only when stepping into a view. It is not called when navigating backward/down the stack. If you need to call a function every time the user enters a view, that's done in viewWillAppear() or viewDidAppear().

   // did load

    override func viewDidLoad() {

        super.viewDidLoad()

   

        // apiRequest

        switch isActiveToken() {   

        case true:

            print("active token - expiry: \(self.expiry)")

            findRequest(with: self.token!, layout: "Bands", payload: self.payload)

 

        case false:

            refreshToken(for: auth, completion: { newToken, newExpiry in    // async

                print("new token - expiry: \(newExpiry)")

                self.findRequest(with: newToken, layout: "Bands", payload: self.payload)

            })

        }

        // ....

 

    // .did load

10 people found this helpful

Attachments

    Outcomes