Obsidian

Questrade client for iOS.

*Update

The process to become a partner developer with Questrade has made it hard to make a very functional experience, so I had to pull it from the AppStore for the time being. You can view the iOS source code here.

MVP

After signing up for Questrade and trying their iOS app, I was not satisfied with the slow web-wrapped experience. I wanted a fast mobile client for iOS, so I decided to see if I could make my own. For the initial release, I wanted to deliver an App that was fast, secure and native. With a simple focus on current orders, positions, and balances.

Growth

I wanted to push myself on two main fronts while building this. Build a Swift native client API to interact with Questrade, and learn about app distribution automation. While I also gained a deeper understanding of UIKit auto-layout, localization and interface theming. I want to focus on the former two aspects.

Client API

For this project I ended up building a completely native Swift API using powerful Swift features such as Generics and Codable protocols to deliver a lightweight, secure, powerful, and easy to use native API.

// First setup the keychain store for your app
let keychain = AuthKeychainStore(service: "Questrade", account: "Obsidian", data: [:])

// since the init could throw errors you will have to wrap it in a do catch
do {
    // Init iOSQuestAuth class with the keychain store
    let auth = try iOSQuestAuth(keychainStore: keychain)
    
    // Init QuestAPI with the authorizer
    let api = QuestAPI(authorizor: auth)
    
    // Optionally set a singleton to access the API
    QuestAPI.shared = api
} catch let err {
    // handle error
}

Automated Distribution

I learned the basics of fastlane to automate tedious tasks of capturing, uploading, and localizing App Store metadata. I learned how to use fastlane in conjunction with automated Xcode Tests to capture screenshots and publish versions to AppStore Connect using scoped lanes. This was very useful to save time when exporting screenshots for all devices in different localizations, and for pushing quick builds to TestFlight. After spending the time to learn the basics of fastlane I was able to automate processes that are usually very time consuming and tedious.

Fastlane

platform :ios do
    lane :test_beta do
        ensure_git_status_clean

        increment_build_number(
            build_number: latest_testflight_build_number + 1,
            xcodeproj: "Obsidian.xcodeproj"
        )

        commit_version_bump(xcodeproj: "Obsidian.xcodeproj")
        add_git_tag
        push_to_git_remote

        build_app(
            scheme: "Obsidian",
            workspace: "Obsidian.xcworkspace",
            include_bitcode: true,
            output_directory: "./fastlane/app"
        )

        upload_to_testflight
    end
end

UI Test

class ObsidianUITests: XCTestCase {
    let app = XCUIApplication()
    
    var isLoggedOut = true
    
    func makeSureAppIsLoggedIn() {
        if isLoggedOut {
            app.images["AppIcon"].press(forDuration: 0.3);
            isLoggedOut = false
        }
    }
    
    func testPositions() {
        makeSureAppIsLoggedIn()
        
        let button = app.tables.children(matching: .cell).element(boundBy: 1).buttons["PositionPriceLabel"]
        let expectation = XCTNSPredicateExpectation(predicate:NSPredicate(format: "exists == true"), object: button)
        wait(for: [expectation], timeout: 1)
        button.tap()

        app.otherElements["Graph View"].press(forDuration: 1)
        snapshot("01-SymbolModalGraph", timeWaitingForIdle: 0)
        
        app.navigationBars["Title"].buttons["DismissModal"].tap()
        snapshot("02-Positions", timeWaitingForIdle: 0)
    }
}

Outcome

In the end I came away with experience building an open source Questrade API written in Swift, the marketing site, and the published Questrade client app.