Apps with only one login method limit user options and often lead to negative reviews, but with multiple login options comes great complexity. By using Swift enums, David East demonstrates how we can easily abstract it away to keep our view controllers and login logic clean and simple. Join him in this talk to learn about the power of enums as first-class value types in Swift and how to build a trustworthy authentication flow.
My name is David East, and I am a developer advocate at Google working on the Firebase team. At Firebase, we do a lot of work with authentication. Here I’d love to talk about how we can build trust through authentication with our users: why we want to build the user’s trust, and how to do it in code using Swift enums.
1-Star Horror Story (00:54)
A couple of friends of mine built this incredible keyboard app called Etch. It’s the first ever gesture based multi-tasking keyboard that allows users to quickly communicate and access services by drawing simple shapes on a grid. I helped test it and thought it was awesome, and they were featured in the Best New Apps of the App Store for the launch week. I couldn’t wait to see all the great 5-star reviews that would come in.
The reviews started out good: “fun, unique, but needs a little work.” Later on, they started to get rough, focused around the Facebook login required for the app, with 1-star reviews like “that is such a lazy, gimmicky requirement instead of implementing your own login or giving the end user an option”. After considering these, I came to the conclusion that if we want the users to trust us, we need to give them options. Since they’re not familiar with the lack of power developers get through a Facebook login, unless your app is widely known, users will have a hard time trusting you.
An app that provides options very well is Overcast, a podcast app. When you first download it, they do not try to pry any information from you. They give you simple options: “I am new to Overcast, I already have an account, I do not want an account”; they also explain why you may want an account in a skeptics FAQ, and have a human-readable privacy policy. This app has nearly 7,000 ratings, the vast majority being five stars, successfully getting the user’s trust. At Etch, when they had enough time to implement email login, the one-star reviews died down. We learned that we can build trust by providing options.
Enums in Swift (07:17)
Implementing multiple login options with multiple SDKs is difficult, but Swift has a really cool construct for multiple options: enums.
enum LoginProvider {
case Facebook
case Email
case Google
case Twitter
}
We used to consider enums as a bag of glorified integers — a set of common values for type-safety. In Objective-C or C, we would think that these are just mapped to zero, one, two, three. In Swift, it is not that at all: they are not mapped, unless you go and map them yourself. Each one of these enumeration cases is a fully-fledged value in itself; each one of these cases has a type of its enum. In this case, the case type is LoginProvider
. This is because in Swift, enums are first-class types.
4 Stages of Enum Awesomeness (08:12)
Stage 1 - Raw Values (08:23)
Raw values are great; they allows us to store a value with each case type, and they all conform to the same type.
enum LoginProvider: String {
case Facebook = "facebook"
case Email = "email"
case Google = "google"
case Twitter = "twitter"
}
let provider = LoginProvider.init(rawValue: "email")
In this case the raw value is String. We set them all to have unique string values, and now we can initialize an enum from a raw value. For instance, we just provide in the string of “email”, and we get the enumeration case of Email
within the LoginProvider
.
Stage 2 - Associated Types (08:57)
Like with raw values, associate values allow you to store a value with an enum, but you do not need to have all of them be strings or integers. Instead, each one can implement its own type.
enum LoginProvider {
case Facebook
case Email: (String, String)
case Google
case Twitter
}
let provider = LoginProvider.Email("bob@bob.com", "pass")
Therefore, the value associated with the enum is not pre-populated with a default value, as is the case with raw values. Instead, you can change it to whatever you want; it is a variable. In the case of social login, each requires different types of credentials. However, since email requires an email and a password, we can store those two string values and set up our LoginProvider.Email
with them. These associated values can be of any type; they aren’t limited to String, Int, or Char like raw values.
Instead of dealing with two strings, we could use a struct of LoginUser
, set the two properties, and then provide a function that validates the email and password.
struct LoginUser {
let email: String
let password: String
func isValid() -> Bool {
return email != "" && password != ""
}
}
Now, rather than using two strings, I can store the LoginUser
in the enum with case Email: (LoginUser)
. Then, I can associate a login user with the email like so:
let provider = LoginProvider.Email(user)
To extract those values, we use switch
on an enum, and using a case
statement, we can pull that value out with a case let
or case var
. Inside of that case statement, we have access to the user, which is really cool because we can take that user information and log them in, right there.
switch provider {
case let .Email(user)
// login!
break
}
Stage 3 - case where
(10:43)
Armed with the knowledge of associated values, stage 3 using case where
becomes that much sweeter. Before we were using a case let
where we could pull out the email that we had stored. Now with this where
statement we can also check to see if the user is valid by calling that function we created earlier. This allows us to be even more specific with our case statements:
switch provider {
case let .Email(user) where user.isValid():
// login!
break
case let .Email(user) where !user.isValid():
// don’t login!
break
}
We can check to see if the user is valid in one, we can check to see if the user is not valid in the other. This is extremely powerful; before we had to only have case statements that matched to the enum, and now we can be more expressive about our switch matches.
Stage 4 - Functions (11:30)
The fact that enums can have functions proves that they are first-class value types in Swift. With functions, when you use switch
, you can switch off self
, as self
is the current enumeration case.
enum LoginProvider {
func login() {
switch self {
case let .Email(user) where user.isValid():
// login!
break
}
}
}
If you are using .Facebook
, the self
would be .Facebook
but in this case we are using .Email
, and it would match there as well. To use it, you just create an instance of login provider, and just call login:
let provider = LoginProvider.Facebook
provider.login()
// How do we surface the login to the View Controller?
Within two lines of code, you abstracted away multiple types of social login to a certain degree. Unfortunately, we now have to deal with the fact that this model code has no concept of returning the user to the view controller, as login is asynchronous. When you are supporting multiple logins, you are dealing with multiple different asynchronous flows.
Login SDKs (12:37)
Social login SDKs are like snowflakes: they may be beautiful, but no two are the same:
- Facebook - Async call to login, async call for user info
- Google - Requires two delegates (one of them is a UI delegate where you implement nothing on)
- Twitter - Async call to get accounts, async call to login
- Email & Password - Totally custom
Simplifying Asynchronous Data Flows (13:14)
Instead of chasing around other login providers, define your own flow, and make them adhere to you. Delegate, albeit not as Swifty, works really well when you are dealing with view controllers.
We can create a protocol (LoginProviderDelegate
) and use our typical delegate syntax (which is not so concise, but it works). We can create methods that will surface when a user logged in or when there is an error. With social login, there are more delegate methods we should be using, but you could just add those delegate methods as you need them.
protocol LoginProviderDelegate {
func loginProvider(loginProvider: LoginProvider, didSucceed user: User)
func loginProvider(loginProvider: LoginProvider, didError error: NSError)
}
The way this works is, in that login(delegate:)
function in the enum, the delegate is just a parameter. Then we can switch off self
and in the case where we have a valid user, we call login, pass the user object in, and that returns us in an asynchronous call our user from the server.
When it is successful, we call the delegate method and it will surface it to the view controller. It makes our view controllers easy to read and it pulls all that logic out of there.
class ViewController: UIViewController, LoginProviderDelegate {
let provider = LoginProvider.Facebook
@IBAction func loginDidTouch() {
provider.login(self)
}
func loginProvider(loginProvider: LoginProvider, didSucceed: User) {
print(user)
}
We just adhere the view controller to the LoginProviderDelegate
, and then we can default to Facebook or another login provider, and if there is an IB action where you tap a button, you just pass yourself, the view controller, as the delegate. When it is successful, it will call the login provider method. This will work for any provider. We can have it all conform to this flow, rather than worrying about all the other ways it could be implemented.
Code Demo (15:03)
By using delegation to define our own flow through which all of our login provider-specific code will go through, we greatly simplify our login view controller and decouple the complex login code from it. This way, instead of chasing everyone else’s implementation details and callbacks, we unify them to our own clean and easy to handle model.
In conclusion, we want to gain the user’s trust by providing them options, and we can simplify these options with Swift enums. Thank you.
Q&A (18:55)
Q: How does using enums compare to using protocols to achieve multiple login options? What is the tradeoff between those?
David: This was an experiment to be as Swifty as possible. We have done wrappers around logins and we have used protocols in the past. Your view controller will be just as clean as before, but since multiple login options mesh with enums that have defined multiple options, it works well. Protocols are another viable way to accomplish this; I’m not being dogmatic with this, it’s just one cool option that works well.
Q: Since you focused on both, what is the primary message of your talk? Do you want to encourage using enums or encourage this method to build a login flow?
David: Both. My main message was think about gaining the trust of the user by providing multiple forms of authentication in your app like Overcast does. I also really wanted to show how powerful Swift enums are, and how they can accomplish this more easily with their power.
Q: I recently implemented login flow in our app using enums, and encountered enums with many associated variables that made it awkward to construct them. Using a custom type of the object for each enum is a great way to clean up that code!
David: Yes, I actually encountered that when clearing up code for the slides and using our own type to construct logins is indeed a great tip.
Receive news and updates from Realm straight to your inbox