Migrating Fieldwire's Files Tab from Objective-C to Swift
The files tab in the Fieldwire iOS app lets users view, search, and edit the collection of files they’ve uploaded to a project. As part of an ongoing effort to modernize the app, we recently migrated the codebase supporting this feature from a legacy Objective-C implementation to Swift.
Often, rewriting a major feature creates more problems than it solves. It’s easy to miss an important edge case when reimplementing functionality, and shipping such a large change to customers at scale can be risky. As a result, undertaking a rewrite or code migration without strong motivating factors is frequently not worth the effort required to do it well.
There were a few technical factors which informed our decision to go ahead with this project despite the risk. First, migrating this feature to Swift would help eliminate a class of memory safety bugs, improving the overall stability of the app. At the same time, it gave us the opportunity to adopt more modern design patterns. Our priority in this area was to improve our model layer so it could more easily be shared across different parts of the app which need access to the user’s files (e.g. to link them to a plan). In addition to making the user experience more consistent, this also allowed us to more easily audit the app for Core Data concurrency violations, eliminating another class of bugs and crashes. Finally, as part of this effort we also took the time to adopt newer UIKit APIs, resulting in an overall more polished interface.
Once we’d decided to begin rewriting the feature, we needed to decide how to ship the new version to customers. From the beginning, we knew we didn’t want to unconditionally replace the entire implementation in a single release; doing so would risk impacting the stability of a critical feature if we overlooked a serious bug. We considered two other options: shipping the rewrite behind a feature flag, or shipping it incrementally across a series of releases.
Feature flags are an attractive solution for this kind of problem because they minimize risk. If a feature is shipped and problems are encountered in production, it’s easy to roll back the change quickly without needing to ship an app update. However, they also require maintaining both the old and new implementations for some period of time, plus some additional cleanup work to eventually remove the feature flag and old implementation. They’re also dependent on server infrastructure. In our case, this was especially problematic because the Fieldwire app is often used offline for extended periods of time on jobsites with poor or nonexistent internet access. Even if a feature flag was turned off, it might be a long time before that change propagated to all of our customers, impacting their mission-critical workflows.
Instead of using a feature flag, we ultimately chose to ship the code migration incrementally over the course of 3-4 releases. This approach sacrificed some flexibility in terms of how quickly we could respond to issues. However, shipping smaller changes still minimized the risk by limiting the impact of any bugs introduced. It also made code review and manual verification of changes much easier, because they could be tested thoroughly in isolation. Additionally, our existing suite of UI automation tests for the files feature played an important role in catching regressions before they made it into a shipping release.
In some ways, rewriting a feature incrementally is more difficult compared to replacing everything at once. It’s easy to let the existing architecture determine the new architecture when you need to maintain compatibility at each intermediate step, which isn’t always desirable. To avoid falling into this trap, we intentionally migrated the “leaf” dependencies of the feature first, gradually working up the dependency tree. For each release, we also included temporary shims so that the old code could interact with the new code without overly influencing its design. This allowed us to effectively mitigate the downsides of our chosen approach.
The new version of the files feature was released over the course of a couple of months in versions 1.34.0 - 1.36.0 of the Fieldwire iOS app. Thanks to our advance planning, the transition went smoothly, with no negative impact to app stability or the user experience. Now that the feature is entirely written in Swift, it’s also over 2000 lines smaller, making it significantly easier to maintain. Most importantly, we now have a solid, extensible foundation we can leverage to more easily ship great new files-related features for our users in the future.