Multi-brand Clothing eCommerce App
Gap Inc. is a worldwide clothing and accessories retailer with six distinct brands - Gap, Banana Republic, Old Navy, Intermix, Hill City and Athleta.
While at Prolific Interactive I joined an existing team of 7-10 Engineers, 3 Designers and 2 Product Managers to iterate and improve on the existing apps for each of Gap’s brands. During my time (~18 months) as a member of the iOS team, I established a Performance Benchmarking process, investigating a series of performance issues in each app and I implemented an internal Logging Framework used across all of Gap Inc’s internal apps. I refactored and improved the User Experience of the core Product Detail screen, Implemented a comprehensive Image Caching solution shared throughout each app, and implemented a series of Checkout features such as Order Tracking, Reserve in Store, and Store Locator.
Our application was entirely written in Swift 4.0, primarily used the VIPER (View, Interactor, Presenter, Entity and Routing) architecture intended to separate out data flow, routing, views and business logic into seperate modules. We implemented a comprehensive theme system which allowed us to compile a seperate app for each of Gap’s 4 main brands - Gap, Old Navy, Banana Republic and Athleta from the same source code. We wrote unit tests using XCTest, and UI tests using XCUITest. We prioritized Accessibility support actively during our ongoing development workflow, enabling users to navigate through the catalog, add items to the cart and checkout using assistive tools such as Voice Over.
I was tasked with investigating a series of performance issues that users were experiencing using each of Gap’s apps. User reports suggested that after installing the app on a device battery life would degrade, while using the app screens would be slow and scrolling through the product catalog was not smooth. I used Xcode’s Instruments tooling to Profile each of the apps in various Device/Network conditions and documented my results.
As part of my investigation into these issues, I took the initiative to establish a defined structure for performance benchmarking mobile apps at Gap. I created a report template, and instituted controlled performance tests at regular intervals during my time on the team. This meant that as time went on, performance test results could be collated to provide a clear insight into the ongoing performance impact of the app on users. Issues could be found pro-actively, and performance would become a priority amongst Gap’s iOS team.
While performing the first performance tests, I found a series of potential causes for the issues that users were experiencing - Blocking the Main Thread on the PDP (Product Detail Page), Lack of Image Caching and an unreliable third party Shimmer animation library. I determined that the scrolling frame rate on the main CDP (Catalogue Detail Page) was less than Apple’s recommeneded 60 fps. Based upon these results I then refactored these areas of the app and eleviated the performance issues that users had been reporting.
I compiled my results into a blog post, educating other iOS engineers on the process of performance testing, and encouraging others to profile their apps regularly.
As an ecommerce application, Gaps apps tend to be very image heavy, displaying feature images on the Catalog, as well as large UICollectionView’s full of Product Cells with images and Product Detail screens with full bleed high resolution scrolling product images. One of the main performance issues that appeared was related to scrolling through the catalog of product cells. Each cell would trigger a network request to download an image then render the image in the UI while the user was scrolling. I implemented a custom Image Cache solution for the apps that would ensure that images that have already been fetched from the network were not being fetched again within the same session. Images were stored in an in-memory cache using the
imageURL as the key, and the UIImage as the value. This Image Cache persisted across ViewControllers and meant that if an image was fetched from one screen, it would not need to be re-fetched for another.
I also implemented logic to cancel image requests if Product Cells immediately scrolled of screen, as they were likely not going to be needed. This greatly reduced the number of unnecessary network requests being made, while the user casually browses through the app. I improved the concurrency of image loading, ensuring that the main UI thread was freed up to render the scrolling and respond to user touch events.
In order to improve and standardize the collection of debugging breadcrumbs throughout all of Gap’s internal apps, I was tasked with architecting a logging solution which would collect debug logs from projects, batch them and upload them to the Server. It was required that the Logger persist logs to disk, in order to prevent loss of data in the event of an App Crash, or a device being taken offline. The Logger needed to have minimal impact on Battery Life and performance, as well as ensure that the GapLogger could be integrated with third party services such as New Relic to allow logs to be indexed and searched.
After architecting and end to end solution called GapLogger, I implemented the iOS framework and deployed it to all of Gap’s internal mobile applications. GapLogger was designed to ingest logs using a
LogManager and then persist logs to disk using Core Data. GapLogger would batch logs based off a series of configuration settings, either time-based or size-based and send the logs to the network. Upon completion logs would be removed from Core Data, making space available for incoming logs.
Working on a large team, with multiple remote team members, I learned about how to work effectively on a remote team. I learned about working on a product within a large, non-technology focused organisation and about how to navigate the use of data, metrics and OKRs to ensure product decisions have the higher level needs of the business in mind.
I gained solid experience in architecting, implementing and maintaining shared internal frameworks that are consumed by multiple apps (and product teams). I learned about how to manage the expectations of consumers of a framework, managing the process of point releases, backwards compatability and future proofing the framework.