Andrew Copp Andrew Copp on

Authentication is often an afterthought when developing apps. It is a step users will need to complete just once before moving on to the actual features of your product. Hinge’s iOS client was a complicated state machine for a long time. There were a number of paths the user can take and we had zero test cases to ensure we were handling all of them. The mentality was very much, “if it ain’t broke, don’t fix it.”

Our outlook changed when Facebook announced their plans to deprecate v1.0 of the Graph API. We were going to have to update the Facebook SDK and replace many of the classes and methods throughout the app. We took this as an opportunity to revisit the architecture of our authentication process.

Facebook encourages the use of the Singleton Pattern and this had been abused in our code. Our Facebook object had a massive API which led to muddled responsibilities and hidden dependencies. It was confusing to know where in the authentication process a user was in at any given time. Lots of potential steps in the flow were overlooked.

We switched to creating a single instance of our HNGFacebookSession class in the HNGLoginViewController and passing it along through the app. We had immediate wins from this approach. Passing along the HNGFacebookSession object meant no more hidden dependencies. Dependency injection meant simpler tests. Composition left open the possibility for swapping out authentication services at runtime.

Before

FacebookService (Singleton Object)

+ (void) logout
{   
    [FBSession.activeSession closeAndClearTokenInformation];
    [[FacebookService sharedService] setUser:nil]; // clear the current user.
    
    [[SessionController defaultController].machine changeStateTo:LoggedOut];
    
    // Doubling up on screens?
    if (![SharedAppDelegate rootController].presentedViewController)
    {
        [SharedAppDelegate displayOnboarding:^{
            [SharedAppDelegate switchToTab:kHomeTab];
        }];
    }
}

SessionController (Singleton Object)

- (void) executeStep
{
        ...
        case HasAPIVersion:
            [self checkFacebookAuth];
            break;

        case NeedsFBLogin:
            [SharedAppDelegate removeSplash];
            break;
        ...
        case LoggedOut:
        {
            [self.machine changeStateTo:HasAPIVersion];
        }
        ...
    }
}

After
HNGFacebookSession

- (void)logout
{
    [[FBSession activeSession] closeAndClearTokenInformation];
}

- (void)sessionStateDidChangeWithSession:(FBSession *)session state:(FBSessionState)state error:(NSError *)error
{
    switch (state) {
    ...   
        case FBSessionStateClosed:
            [self sessionEnded];
            break;
    }
}

- (void)sessionEnded
{   
    [self.delegate sessionDidLogout:self];
}

HNGLoginViewController

- (void)sessionDidLogout:(NSObject *)session
{   
    [self.navigationController popToRootViewControllerAnimated:YES];
    // Cleanup
}

Don’t repeat yourself repeat yourself


Despite all these wins we found ourselves with a lot of repeated code. We were presenting a login modal anytime our user wasn’t authenticated. This meant every view controller theoretically needed to be able to handle an invalid token by presenting the login modal and handling the corresponding delegate callbacks.

 

Untangling_Facebook_Authentication_on_iOS



Modal Login


This approach did not make sense for us. Not only because of the tedium but because our app won’t function without a valid user. There is no point in letting the user past the login screen if they aren’t authenticated.


Root View Controller


The suggested solution was to optionally swap out the root view controller during app launch if the user wasn’t logged in. We have an asynchronous login on every app launch where we check in with our backend, so this wasn’t a solution. We weren’t big on the idea of swapping out the root view controller at runtime either.

We elected to make the login view controller the root view controller of our app. Doing so made authentication a priority by forcing the user through the authentication process on every app launch. This approach had the additional benefit of being able to pop to the root view controller instead of presenting a modal if the user ever logged out.

Untangling_Facebook_Authentication_on_iOS_(1)


Login as Root


This worked great except for having one major flaw—users saw the login view controller on every app launch. We had some business logic to check for a cached Facebook access token to automate the process but the experience was not ideal for users.

We solved the problem by subclassing the navigation controller and adding an imitation Launch Image as a property. The imitation Launch Image is the frontmost view when the app becomes responsive and gives the illusion the app is still starting. We are running our authentication process underneath and remove the curtain once we know what view controller to drop the user into.

Untangling_Facebook_Authentication_on_iOS_(2)


Login as Root w/ Curtain


Our new authentication flow gives users a consistent navigational experience. There is one way to enter the app and you have to go out the way you came in. The simplicity also allows us to deal with one centralized delegate rather than a handful of notifications strewn about. Less time digging through the app to figure out what caused a side effect means more time working on important new features.

Authentication will continue to evolve to meet the needs of our users and technical partners. Facebook released a new SDK since we broke ground on this project and, as a result, we will be revisiting this architecture shortly. We’d love to hear ways in which you’ve simplified your authentication flow so we can improve further on our next iteration.

Continue
Alistair Leszkiewicz Alistair Leszkiewicz on

Before switching to iOS development I worked as a web developer, and in the time since one of the things I miss is the feedback cycle involved with writing code for the web. Changes to CSS or Javascript can be reflected instantly in the web browser.

iOS Development is a lot less mature in this regard. Code changes bear the cost of a minimum ~10 second wait time between developer hitting ⌘B and seeing something on-screen. Doesn't sound like much, but that time is enough to get distracted and removed from a flow-like state.

This daily struggle leads me onto thinking more about workflow optimisations, and being pragmatic about where effort should be re-directed. Every developer has this problem in some regard, but depending on your platform, its severity on your ability to be productive is different.

Workflows


Workflows encompass the things we do to get our work from inception to production ready. At the early stages workflows are a private sandbox. Your laptop is customized to you with key bindings, applications and project structure being individual choices. As we get further along, our workflow gets more public, you may create a pull request, share a specification or even (!!!!) organize a meeting.

In the private stages the goal is move fast and break things. I often find myself writing documentation for methods only to delete the method later. Writing the method served a purpose of testing an implementation, the documentation was a little wasteful since nobody had an opportunity to read it.

move_fast_and_break_things

Your private workflow stage should be very enjoyable and creative; there's freedom to learn and make mistakes. But there's a downside: you're in a cave, a dream world. Everything you do in this state should be getting you ready for pushing to future stages of your workflow.

Once we hit the public stages things start to get interesting. In the public stages your goal shifts to iterate and review, while widening the audience at each later stage. We're now placing burden on other people's time, and the asynchronous feedback causes work to slow down.

Iterate, Review, and Audience


I have listed a flowchart of the development process used for the Hinge iOS app. This process should be familiar to any experienced developer so I'll skip over the details, it is used to illustrate 3 concepts in the public workflow: iterate, review and audience.

Each diamond halts the work for review, and iteration. Each successive stage expands the audience

flow

The non-public process here is the first Implement box, and each successive review can also be considered private.

Each next stage is a widening audience, to minimize the cost of review.


  • Pull Request is a peer review performed typically 1 other developer, time allocated: 5 minutes to an hour.

  • Internal QA is a deeper dive into the functionality by 1-4 QA experts, additional time is re-invested each time a new version is available. 100s of hours can be invested over the course of a release

  • External QA is included as production because the goal is to simulate those conditions, but technically still a pre-production. At this stage we rely mainly on analytics tools such as Mixpanel, Crittercism, and other tools to get feedback, and perform the review before the final green light.

  • App Store The widest audience we have, reviews here are by your customers


As the audience expands, getting feedback and iterating becomes more costly so the tools and process change to reflect this. Also work that fails a review is sent back to the beginning.

We utilize automation using Travis CI to give this process a rhythm.

Git versions and Travis build numbers become part of the vernacular when reviewing changes. This is to keep changes fully traceable, our internal version is an amalgamation of each step in the review.

A build uploaded to HockeyApp might be labeled:

Version 3.4.1 (10343.502.274a09f)


The format follows:

Version A.B.C - {external build number}.{travis build number}.{git hash}


  • Version number : What the end user sees publicly in the App store (“3.4.1”)

  • External build number : This number is incremented when submitting to iTunes Connect (“10343”)

  • Travis build number : Incremented for each travis build. Can be accessed using TRAVIS_BUILD_NUMBER environment variable (“502”)

  • Git hash : Short git commit hash git rev-parse --short HEAD (“274a09f”


This gives any internal tester confidence as to what their testing is correct, and if they need additional context there are breadcrumbs to follow.

We can then attach this version to stories in our bug tracking system, making the workflow fully traceable. Again allowing for this traceability helps reviewers to gain context (be it now or in months/years from now) and perform their review with confidence.

So far this worked great for us but we’re always refining it to find the right balance.

One area I’d like to improve is the feedback from our testers in external Test Flight phase. Currently we’re not getting 100% confidence in App store releases, and the soak testing gained from a strong group of Beta testers will help immensely with that.

We'd also love to hear what works for your team and personal workflows! Or get an invite to our beta group by emailing us ios-beta-feedback@hinge.co

 

Continue
Unknown author on

Today we have a talk by Adam Lashinsky, Fortune Magazine Senior Editor and author of Inside Apple. In this talk Adam will explain what key elements of corporate culture and business processes led Apple to market domination. Talk was recorded at the The Silicon Valley iOS Developers' Meetup.


Have a cool project to share? Join us for our first Hack and Tell in San Francisco.


 

Continue
Unknown author on

We've got a new talk today and it's really special. Seven leaders in mobile development gathered at one of the TechXploration events in San Jose to discuss the Future of Mobile Development. Listen to learn how to keep up with important mobile trends now, and also hear the future forecast from these visionaries.


The panelists:

Continue