Quick start guide

Framework integration

You can either use CocoaPods to install and configure the Operam SDK framework automatically in your Xcode project, or you can do it manually.

1. Automatic / CocoaPods

Modify your existing Podfile to specify the Operam pod:


pod 'Operam'
    

Install the pod by running pod install.

2. Manual

Download the Operam SDK, unzip it, and add Operam.framework to your Xcode project.

  1. Open your Xcode project and choose "File -> Add Files to Project_Name..."
  2. Select Operam.framework and check the "Copy items if needed" option.
  3. Select the targets by checking the appropriate checkboxes under the "Add to target" section.
  4. Add Operam.framework to the "Embedded Binaries" list under "General".
  5. Add a new "Run Script Phase" and set the following command:


bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Operam.framework/strip-frameworks.sh"
  

Activating the framework

Import the Operam SDK at the top of the app delegate file:


import Operam
    

Modify your app delegate to instantiate Operam with your API key. You can find your API key on the Mobile SDK Settings tab of the Operam dashboard.


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
    Operam.start(withAPIKey: "ABCDEFGHIJK")
  
    // rest of existing code...
}
    

Invocation Options

By default Operam's user feedback reporter interface is triggered by a shake gesture. If this is not appropriate for your application, you can change it to either a manual process, where your app would provide a way to manually bring up the reporter interface, or you can use a screenshot to trigger the process.

1. Shake Gesture (Default)


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
    Operam.start(withAPIKey: "ABCDEFGHIJK")
  
    // rest of existing code...
}
    

2. Manual Process


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
    Operam.start(withAPIKey: "ABCDEFGHIJK", processType:.manual)

    // invoke the reporter interface by using the following line of code:
    // Operam.shared().presentReportIssueController()

    // rest of existing code...
}
    

The manual invocation process is usually triggered via a "floating button" in the user interface of the application.

3. Screenshot


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
    Operam.start(withAPIKey: "ABCDEFGHIJK", processType:.screenshot)

    // rest of existing code...
}
    

Events

Events are similar to user generated issues, except that they are created programmatically through code, they don't include screenshots, and they have a different workflow within the Operam Dashboard.

They are meant to be used as an investigation tool while trying to solve problems within an app. Usually events would only be generated in the case of a rare exception, and they will include lots of information so you can fix the underlying cause.

1. Typical usage

The following is an example of the use of events in your own code:


func saveRecords()
{
    if let req = buildRequest() {
        let task = URLSession.shared.dataTask(with: req, completionHandler: { (data, response, error) in
            if let data = data,
                let response = response as? HTTPURLResponse,
                response.statusCode == 200 {
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    // rest of the code...
                } catch {
                    Operam.shared().recordEvent("Failure to decode JSON on saveRecords", file: #file, function: #function, line: #line, includeAttachments: false)
                    print(error.localizedDescription)
                }
            }
        })
        task.resume()
    }
}
    

You may also create an event and include already specified attachments by passing includeAttachments: true or using the Objective-C OperamCreateEventWithAttachments macro. Read more about how to attach files to issues and events on the Attachments section.

User Properties

You can assign user information to the Operam instance to be able to tie an issue back to a user record. All issues and events created thereafter will contain the appropriate user information.

Here's an example doing just that within a piece of code that handles a successful user login:


@IBAction func didTapLoginButton(sender: AnyObject) {
    if let req = buildRequest() {
        let task = URLSession.shared.dataTask(with: req, completionHandler: { (data, response, error) in
            if let data = data,
                let response = response as? HTTPURLResponse,
                response.statusCode == 200 {
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    if let json = json as? [String: String] {
                        let userId = json["userId"]
                        let username = json["username"]
                        let fullName = json["fullName"]
                        let email = json["email"]

                        Operam.shared().setUserIdentifier(userId)
                        Operam.shared().setUsername(username)
                        Operam.shared().setUserFullName(fullName)
                        Operam.shared().setUserEmail(email)
                    }
                } catch {
                    print(error.localizedDescription)
                }
            }
        })
        task.resume()
    }
}
    

Note that if you set the user email address with -[Operam setUserEmail:] then the email field will be pre-filled with that value to make the end user's life a bit easier.

Custom Metadata

You can also save other pieces of information via the metadata support. It's essentially a key-value store, to be associated with any issues/events.

Here's an example of storing some metadata to be inspected later:


func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
    Operam.shared().setString(searchBar.text, forKey: "last search query")

    if let req = buildRequest() {
        let task = URLSession.shared.dataTask(with: req, completionHandler: { (data, response, error) in
            if let data = data,
                let response = response as? HTTPURLResponse,
                response.statusCode == 200 {
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    if let json = json as? [String: Any] {
                        if let results = json["results"] as? [String] {
                            Operam.shared().setIntValue(results.count, forKey: "last search result count")
                        }
                    }
                } catch {
                    print(error.localizedDescription)
                }
            }
        })
        task.resume()
    }
}
    

Note that as in typical key-value store fashion, multiple calls to this method above will overwrite the existing metadata values. Keep that in mind when using this feature.

You may also store other kinds of values, like NSDictionary, BOOL or float. Consult the header file for more details on these options.

Debugging Logs

Have you ever wished that you could log messages throughout your application, and then wished you could have access to to those later at a later point while debugging a problem? Your wishes have been granted!

You can log messages with the OperamLogError, OperamLogInfo and OperamLogDebug macros. They behave similarly to the standard NSLog macro, but they also record the filename, line number and method signature where they were invoked from.

Here's the same metadata example above, but using debugging logs which are not overwritten since this is not a key-value store:


func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
    let message = "Search query: " + searchBar.text!
    Operam.shared().logMessage(message, level: .debug, file: #file, function: #function, line: #line)

    if let req = buildRequest() {
        let task = URLSession.shared.dataTask(with: req, completionHandler: { (data, response, error) in
            if let data = data,
                let response = response as? HTTPURLResponse,
                response.statusCode == 200 {
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    if let json = json as? [String: Any] {
                        if let results = json["results"] as? [String] {
                            let message = "Search result count: " + String(results.count)
                            Operam.shared().logMessage(message, level: .debug, file: #file, function: #function, line: #line)
                        }
                    }
                } catch {
                    Operam.shared().logMessage(message, level: .error, file: #file, function: #function, line: #line)
                }
            }
        })
        task.resume()
    }
}
    

Note the use of OperamLogDebug and OperamLogError to represent different log contexts.

Attachments

You can specify files that should be included with issues and events to help diagnose problems with your application. Attachments are defined with a file URL, and Operam will upload them together with the user feedback if/when it happens.


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
    Operam.start(withAPIKey: "ABCDEFGHIJK")
  

    let dbPath = sqliteDbPath()
    let dbFileUrl = URL(fileURLWithPath: dbPath)
    Operam.shared().addAttachment(withFileUrl: dbFileUrl)

    // rest of existing code...
}
    

Note that file sizes must be within the 100 KB limit.

User Defaults

Operam collects the contents of NSUserDefaults by default. This can be a very valuable feature for troubleshooting applications that are not working properly because of a non-expected NSUserDefaults entry.

However, you can still disable this automatic collection feature, like so:


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
    let manager = Operam.start(withAPIKey: "ABCDEFGHIJK")
    manager.collectUserDefaults = false
  
    // rest of existing code...
}
    

Benchmarks

Performance timings can be recorded in a key-value store by calling the start/end methods for a particular long running operation.


func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
    Operam.shared().setString(searchBar.text, forKey: "last search query")
    Operam.shared().startTrackingTime(forKey: "lastSearch")

    if let req = buildRequest() {
        let task = URLSession.shared.dataTask(with: req, completionHandler: { (data, response, error) in
            Operam.shared().stopTrackingTime(forKey: "lastSearch")
            if let data = data,
                let response = response as? HTTPURLResponse,
                response.statusCode == 200 {
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: [])
                    if let json = json as? [String: Any] {
                        if let results = json["results"] as? [String] {
                            Operam.shared().setIntValue(results.count, forKey: "last search result count")
                        }
                    }
                } catch {
                    print(error.localizedDescription)
                }
            }
        })
        task.resume()
    }
}
    

Operam Delegate

You may further customize the Operam integration with your own application by building personalized widgets for the "instruction" and "confirmation" messages.

The instruction widget is displayed a few seconds after the application launches, and tries to educate the user that they can send feedback about the app. This is specially important when the invocation option for Operam is either a shake gesture, or a manual screenshot.

The confirmation message dialog is displayed after the user feedback is saved and added for submission.

You may customize the look and feel of these messages by implementing the OperamDelegate protocol, and then responding to the appropriate callbacks.