iOS

Efficient ViewController Transitions

Anyone familiar with RTR’s app knows that there are several different viewControllers that the user is able to navigate between; however, anyone not familiar with the code would most likely assume this navigation is done through simply pushing the relevant viewController via the navigation controller or by presenting the viewController modally (ex below).

Prior to joining the team, I would have made the same assumption. This assumption is wrong! You are probably now asking yourself “what other way is there to navigate between viewControllers?” Good question!

To handle efficient routing, we implemented a unique pattern to navigate between viewControllers. All of the data on the app’s viewControllers are populated by modelControllers. With that in mind, when a viewController is instantiated, the viewController’s initialization requires a modelController as a parameter. You don’t have to do it this way, but for reasons related to a whole different (future) blog post, we gather all the relevant information from the network at an earlier stage and persist this information throughout the app’s lifecycle.

After having created the viewController and established which modelController the viewController “owns,” it is time to create the route that will link to this viewController. RTR’s app has an entire class (AppRouter) dedicated to routing. One thing to note: we use JLRoutes, a cocoa pod that manages routes for URL schemes, to handle routing within the app. The main function we are concerned with inside AppRouter is adding a new route, which is a function provided by JLRoutes.

As you can see from the code above, addRoute requires a path as a parameter. We store all paths required for routing in a class, RTRURL. The enum Route (below) is the path to the specific viewController and the enum Param (also below) holds the paths to any parameters required to instantiate the viewController. In this example, case Experiment is the path to experimentsMC, which is the modelController that is required to initialize RoutingVC.

Great! We now have a way of routing the app to a given viewController based on the viewController’s path provided in the addRoute func. So…how do we present it?

We made a class, Presentation, that subclasses from RTRURL. This means that Presentation has access to all of the properties held in RTRURL. Yes, the code inside Presentation could be done inside RTRURL. However, we made this Presentation class as a way of abstracting implementation details to make the code easier to read. 👏

In order to “tie together” all of the components that contribute to navigating to a given viewController (aka creating a new route and providing the viewController’s route path), we decided to create a “route struct” for every viewController. Specifically, this struct encapsulates all of the information required to instantiate the viewController. It does this by adopting and conforming to the PresentationProtocol.

PresentationProtocol has three requirements: a path, required parameters and valid parameters. What does this mean, exactly? With this particular example, RoutingVC can’t be instantiated without an experimentsMC; therefore, this property must be provided in the RequiredParams. Similarly, a new route can’t be added without a path, thus, a path must be provided.

ValidParams takes in properties that can be displayed by the viewController but aren’t required in order to instantiate the viewController. ValidParams is encapsulated within the PresentationProtocol but can be an empty array. An example of ValidParams could be the way in which the viewController is presented. Is it pushed? Is it modal? You choose!

Screen Shot 2017-03-24 at 10.27.49 AM.png

FINALLY we are ready to show the viewController. The params property includes the required experimentsMC parameter as well as presentationStyle, which is a valid (but not required) parameter. These parameters are passed through to the Experiment “routes struct” held on the Presentation class.

After creating an instance of the “routes struct” (in this example, Experiment), we are now ready to actually present the viewController. openViewConrollerWithPresentation() takes in a conforming type of PresentationProtocol as a parameter. After establishing the path, parameters and URL, routeURL is called. Using JLRoutes, routeURL presents the viewController associated with that url.

This may seem like a complicated solution for a “simple” transition between viewControllers. So, why did we decide to do it this way? For two key reasons:

  1. By creating a route for each viewController, integrating deep linking with universal links is much simpler. The url path already exists!
  2. When testing new features on the app, it is much more efficient to link directly to the viewController that contains the feature in question rather than navigating throughout the app to finally get to the relevant viewController.

The next time you use RTR’s app, you now have a “behind the scenes” understanding of what’s happening when you navigate between viewControllers. Thoughts? Comments? Please don’t hesitate to reach out to us!


iOS Data Binding is Better with Signals

Here at Rent the Runway, we want to make sure that all of our users have a responsive and rewarding iOS mobile app experience. With our backend architected to use many highly performant microservices, there is a whole lot of data and user interactions to keep in sync. We have services for user memberships, promotions, products, search categories, user hearts, and so much more. Without a one-to-one mapping of API service calls to application views, it was critical that we come up with a robust and coordinated approach for data binding and content updates.

One of the main iOS communication patterns is delegation. Delegation creates a one-to-one messaging structure between two objects, where one object can act on behalf of another. Using this pattern, it is typical that a completed API call would inform its delegate object that new data exists and is ready for processing and/or display somewhere else in the app.

On this diagram we can see that we have several UIViewControllers (ProductCollectionsVC, ProductsGridVC, ProductVC) that hold a ModelController (HeartsMC), which is responsible to fetch all the hearts from a user. 
While this approach is useful in many situations, it makes it difficult for communicating data updates from one object to many objects at the same time as we can see on the diagram.
 
As an alternative to delegates, we can use ReactiveCocoa’s framework to use signals for communication. In short ReactiveCocoa is a functional reactive programming framework which represents dataflows by the notion of events. An event is just a concept that represents that something happened, which could represent a button being tapped or the status of a network call being made. ReactiveCocoa uses Signals and SignalProducers to send messages representing these events.  A Signal will only notify an observing object of events that have occurred after the observer has started observing, not any past actions. On the other hand, a SignalProducer allows you to start observing for an event but also have the visibility for past and future events.

ReactiveCocoa explains these concepts by comparing them to a person (the observer) watching television (the signal or signal producer). 

[A signal] is like a live TV feed — you can observe and react to the content, but you cannot have a side effect on the live feed or the TV station. [A signal producer] is like a on-demand streaming service — even though the episode is streamed like a live TV feed, you can choose what you watch, when to start watching and when to interrupt it.
— https://github.com/ReactiveCocoa/ReactiveSwift

Signals work very well for UI events like tapping a button, where an observer would only be interested in reacting to touch events after it has started observing. A Signal Producer would be more useful for a network request, where its on-demand nature would help provide more contextual information about a network request, such as allowing an observer to know if it is in progress, has it previously failed, should we cancel it, etc.

Objects throughout our app can then observe these event-driven signals and take any action they need to bind data to views, process data, or trigger any other actions. This allows multiple views and viewControllers in our app to be notified of data changes at the same time without the need for complex delegation chains. 

In the code snippet above, we are setting up signals for a modelController. When it receives a signal, it can call a method that is defined by our protocol. If we were to configure these methods and interactions with a delegate, other ViewControllers would not receive any message and would not know to update their data.

In the app, we have a TabBarController that allows users to quickly switch between viewControllers that provide different functionality. However, we do have some shared data sources between these controllers, so our signals allow us to update data on every accessible viewController so the user can interact with them immediately after switching tabs.

This approach has been very helpful for broadcasting changes of the datasource on a ModelController throughout the app. While implementing, we paid special attention to weak/strong references with our observers to prevent retain cycles, routinely testing that objects were being deallocated when needed. Additionally, deadlocks can occasionally occur if signals trigger other signals that are dependent on the completion of another signal. However, with well thought out protocols and implementation patterns, we have been able to provide a responsive and stable app experience for our users.


As seen On Apple TV: A HackWeek Memoir

Last week, to celebrate months of hard work, Rent the Runway hosted HackWeek, a week-long hackathon. This was an opportunity to work with new technologies, hardware, languages, as well as coworkers outside of our usual teams.

So on the Friday before HackWeek, we gathered in the kitchen to hear the pitches and decide what projects to work on. That same Friday, Rent the Runway received the AppleTV Developer Kit (Fun fact: we won it in the AppleTV lottery for just one dollar!) Naturally, the mobile team was itching for an opportunity to tinker with our new toy.

On Monday, our team was formed. Its group members? Billy Tobon - Lead iOS Developer, Sandy Woodruff - Product Designer, and Lea Marolt Sonnenschein - iOS Developer.

We spent the first day reading the Apple tvOS documentation, looking at Apple's human interface guidelines, and learning from their examples. We also spent some time thinking of a clever app name. We ended up with TVGunn (Hint: Project Runway). We realized that building applications for the TV relies heavily on a strong understanding of the user interface to create an application that is:

  1. Simple to use: The user should be able to go through the entire app with just a few taps and flicks of the finger.

  2. Actually usable: Asset placement is important in tvOS. If two assets that can be hovered over are too far from each other, the user won't be able make the transition from one to the other.

  3. Sensible: There's actual value in creating a tvOS app. It complements your existing apps and doesn't just exist for the sake of having your app on all available platforms.

 

With that in mind, we brainstormed, sketched, discussed, failed a lot, and learned from our failures. For example, For example, Apple gives developers two options of developing tvOS apps: native, or using their new Television Markup Language - TVML (along with TVJS and TVMLKit). We spent hours trying to figure out what the best template was for our use case, and trying to work with this new XML and JavaScript. At the end of the day, we decided to scrap the project, and start off fresh with a native implementation the next day.

 

FIRST LESSON LEARNED: If you actually want to build something usable, stick to your guns at a hackathon. If you don't care whether the final product works, feel free to explore.

 

On Tuesday, things got off to a good start. We had a plan. TVGunn would be an app placed in Rent the Runway stores that could help users browse through their shortlists in-store. There would be iBeacons placed in the store to detect the user's shortlists, and automatically pull them up on the screen when the user's credentials were verified. This would help streamline the process of interacting with the stylist.

 

Sandy created fantastic mockups, while Billy and I had a clear division of labor on the tvOS side. I was going to work on the app's frontend, while he would try to make our RTR Foundation Framework work with tvOS, so that TVGunn could easily talk to the backend. In the spirit of HackWeek, we decided to write the app in Swift - the RTR iOS app is completely written in Objective-C. Not only that, but we decided to use Storyboards, something many a mobile team dreads like the plague.

 

SECOND LESSON LEARNED: Betas will be betas - things will break and there's nothing you can do about it.

 

In order to develop for tvOS, we had to download the latest Xcode Beta - 7.1. We quickly learned that the Beta is called a Beta for a reason. For example, using Storyboards resulted in a crash every time I tried to drag a new View Controller on the board. I ended up copying the default View Controller set up in the Storyboard 5 times to achieve the user flow that we wanted - #scrappinessisavirtue.

 

THIRD LESSON LEARNED: Cocoapods don't play nicely with tvOS (yet).

 

Despite the Cocoapods fix for tvOS, released on Wednesday, we couldn't import the frameworks we use in our iOS App as pods, so Billy ended up importing them into our project directly. That resulted in a massive amount of errors that took almost two days to debug. In the meantime, Sandy learned how to create LCR files and make use of Apple's new tools to design parallax icons - ParallaxExporter and ParallaxPreviewer. I, on the other hand, tried to figure out how to best mimic Apple's interface. When the user moves from one element to another, the second element has to expand and cast a shadow to create the feedback needed for the user to know what element they're currently on. While this is done for us in some UI elements, like the UITableViewCells, it's not done in others, like the UICollectionViewCells. In order to achieve the look and feel that the user expects, I had to learn how to use special Focus Engine Controls only available on the tvOS.

 We could play with this icon for hours!

We could play with this icon for hours!

 

FOURTH LESSON LEARNED: Parallax icons look and feel fantastic. We played with that app icon just a little too long when we finally got it on the screen.


FIFTH LESSON LEARNED: Don't count on everything you see in examples to be pre-built. I had to put in some muscle in order to get the interface looking and feeling the way Apple does in their examples.

 

SIXTH LESSON LEARNED: Swift 2.1 has evolved-a lot. Make sure to always read the newest documentation carefully, instead of just assuming the old ways work. Otherwise you will experience unnecessary frustration.

 

Swift offers a severely streamlined process of creating apps. Unfortunately, because Swift is still evolving, many of the language's aspects have changed with each iteration. Because of that evolution, learning from older examples proved to be a little difficult, because they didn't work with this new syntax. For example, background tasks and requests now have to be wrapped inside a try/catch block.

 

SEVENTH LESSON LEARNED: Persisting data on tvOS is tricky. We knew from the documentation that there was no local store in the appleTV, and we planned not to persist anything in the device, but when we tried to use a SaaS solution (Parse) as a bridge between iOS and tvOS the app wouldn’t even load, because most of these types of frameworks use local store for caching.


 

In the spirit of HackWeek, we ended up using Parse via REST, and storing user credentials to Parse in plaintext (a definite no, no in production apps) based on data received from the iBeacon. Inside the app, we used a timing function that periodically checked if a new user had appeared on Parse. If there was no user on Parse, the app would play the official RTR promotional video. If the user appeared, however, the app immediately redirected to their shortlists.

 

By Friday, we ended up with a pretty solid working demo, and presented it to the whole company. Billy presented the hack and explained how it works, while Sandy and I did a small roleplaying sketch to make sure everyone understood our use case. Everyone seemed to enjoy it, and we're excited to explore its use. Even though, the demo was a success, we all know that the app has several problems, and is therefore not ready for production. Because this was HackWeek, though, we allowed ourselves to use not-so-stellar coding practices, pass data that shouldn't be passed around willy-nilly, and all in all, hack our ways until we made it work.

 

EIGHTH LESSON LEARNED: Never use the "F*** it, ship it!" method. If you decide to make a hackathon project a production project, scrap the whole thing and start from scratch. This will allow you to follow good development practices, and create a well-maintainable product, instead of having a codebase with so many leaks, you spend more time working on covering up the problems, rather than fixing them and focusing on moving the product forward.

 

All in all, HackWeek was a fantastic experience and a great success. Our team collaborated well and iterated quickly. We learned a lot of new things, and in the end produced a beautiful and exciting app using completely new hardware. I'm extremely proud of my team, and couldn't be happier I worked with them this entire week. We all can't wait to actually create a live Apple TV app, because we believe that the Apple TV has tremendous potential for how our customers can experience Rent the Runway!

Yours in Fashion,

Lea, Sandy & Billy


Experience with Appium So far

I got a chance to go to Selenium Conference in Boston this year. It is always interesting to know how big shops like Facebook, Google, etc. manage their QA architecture, what new frameworks / technologies they are working on, and what can we learn from others. One automation testing framework for mobile which was talked about a lot was Appium.  Appium uses Webdriver Json wire  protocol to drive iOS and Android apps. Since our own iOS app at Rent The Runway is under development, I thought this would be the best time to try my hands on Appium and have some cool automation test scripts ready for our app.  And now at this time when I have some pretty scripts ready for our app, I thought why not write a blog to share my experiences with Appium so far. When I initially started setting up Appium, I had a very little idea how to read the page source for the app to interact with it. After a little bit of searching I found something that they call an Inspector. The way Inspector works is it makes Page source calls to you app and displays the element hierarchy for you, making it possible to find the name, value etc of the objects on the app.

I use the page objects framework to write automation tests for the mobile app.The way I have set my framework is I have rake tasks in the Rakefile to run regression/ smoke iOS tests. Since eventually we will have a Jenkins job to run these tests I have not hard coded  Appium serverURL and app path parameters in the code. Instead, I pass them in using the command prompt.

So if I have to run my regression suite for iOS, it is as simple as

rake ios:default  IOS_TARGET="appium server url  "" APP_PATH='location of the app path’

where ios:default is a task defined in the Rakefile.

We use watir webdriver to write scripts and by passing the correct capabilities object in the script, you are good to go.  I would say I am pretty satisfied by how Appium has come up so far and I am very happy with the progress I have made with it but there are some challenges which I am facing.

Current Challenges and things to Expect:

I have often found that once my tests fail, the Appium server goes to state saying “clearing out any previous sessions” and it never comes out after that. So if I have to run my tests again I have to restart the server in order for the tests to succeed. In my local testing that's ok but when I add this job to Jenkins this causes major problems upon test failure.

I would also love to have these mobile tests run in parallel. Unfortunately, there is not much documentation out there on how to register a node to the Grid and run parallel tests using Appium. So hopefully my next blog will cover how I managed to solve my current challenges and run Appium tests in parallel. :)

Till then Keep Using Appium!