Project Unity: Accelerating Delivery by Rebuilding CMT’s Mobile Frameworks

June 21, 2024

At CMT, we are constantly striving to provide the best customer experience across all our applications. As part of this mission, we embarked on a journey called “Project Unity,” which is an initiative to rebuild our mobile frameworks from the ground up. This new framework will not only support our flagship app, DriveWell Go, but also apps developed for our partners. With Project Unity, we aim to ensure consistency, scalability, and a seamless user experience across all platforms.

Why Did We Decide to Rebuild?

The decision to rebuild our mobile frameworks was not taken lightly. Here are the key reasons that drove this initiative:

  • Scalability: Our existing frameworks were started about 9 years ago when our app ecosystem was much smaller. As we expanded, it became clear that our current architecture could not scale efficiently to support the growing number of apps and features.  
  • Codebase Consistency: Our existing frameworks were a mix of Objective-C and Swift on iOS and Java and Kotlin on Android. As we continued to build new features, this mix of different languages and architectures slowed us down and made it challenging to maintain a high-quality codebase. Rebuilding allowed us to standardize our technology stack, making development faster and more efficient.
  • Modernization: Mobile development practices and technologies have evolved significantly in the last 9 years. Rebuilding gave us the opportunity to adopt modern architectures, patterns, and technologies, setting us up for future innovation.

The New Architecture and Tech Stack

With Project Unity, we are building a robust and modern architecture designed to meet the demands of our expanding app ecosystem. Here’s a concise overview of our new tech stack:

Technology Stack

iOS

  • Language: Latest Swift Version – 5.10
  • UI: SwiftUI
  • Dependency Management: Swift Package Manager
  • Dependency Injection: Factory
  • Reactive Programming: Combine & Concurrency 
  • Testing: XCTest & XCUITest & Cuckoo 
  • Other Dependencies: Xcodegen, Swiftlint, SwiftFormat, Swiftgen.

Android

  • Language: Kotlin – 1.9.22
  • UI: Jetpack Compose
  • Dependency Management: Gradle (Kotlin DSL), Gradle Version Catalogs
  • Dependency Injection: Hilt
  • Reactive Programming: Kotlin Flow
  • Testing: Unit & Instrument Test
  • Other Dependencies: Datastore, WorkManager, Coroutines, Ktlint, etc.

High-Level Architecture

With Project Unity, we are building a robust and modern architecture designed to meet the demands of our expanding app ecosystem. Our new architecture follows a modular structure based on CLEAN Architecture principles with the MVVM (Model-View-ViewModel) pattern for the presentation layer.

The core modules contain shared business logic and models that are used by all other feature frameworks. The CMTFoundation module includes interfaces to the CMT DriveWell SDK for functions like authentication, user management, and mobile configuration. The CMT DriveWell SDK is a core component that is used for trip detection and sensor data collection. The CMTFoundation module ensures that the feature modules (described below) can interact consistently with core functionalities.

Feature modules, such as CMTAuth (for authentication and onboarding), CMTRewards, CMTDiscounts, and CMTTrips, encapsulate specific internal entities and use cases relevant to their domains. Each feature module maintains its own presentation logic, with ViewModels receiving use cases and other dependencies via dependency injection, and Views observing ViewModel updates to encapsulate UI components.

The dependency injection module manages the setup and configuration of the DI framework for the entire app, ensuring that dependencies are provided and managed across all feature modules.

Platform-specific implementations are housed in frameworks like CMTUI for reusable UI components and CMTUtility for app analytics, A/B testing, and so on. 

Additionally, we have worked closely with the CMT design team and have embraced the design token concept they use to build mockups in Figma. By matching the design tokens closely in our implementation, we ensure easy customization and reusability of UI components. This collaboration allows us to create a consistent and cohesive user experience across all our apps.

This modular, clean architecture not only improves code maintainability and scalability but also allows for isolated testing of each feature module, facilitating independent development and faster iterations. 

Challenges and Solutions on iOS

We encountered several challenges along the way. For this post, we will focus on the iOS implementation first. Here’s a glimpse into the hurdles we faced and how we overcame them:

Challenge 1: SwiftUI Discrepancies

We believe that SwiftUI is the future for iOS apps’ UI development. However, discrepancies between iOS 15 and iOS 16 and above posed significant challenges. For instance, the new NavigationStack APIs introduced in WWDC22 are not available on iOS 15, leading to navigation issues. Thankfully, the open-source community provided a solution with the “NavigationBackport” library, which helped us address this issue.

However, other issues persisted, such as the absence or inconsistent behavior of standard UI components like popup sheets across different iOS versions. We also spent extra effort handling HTML with AttributedString due to missing features as compared to NSAttributedString. Additionally, our mobile apps heavily use maps to display driving data, but SwiftUI’s map functionality is still in its early stages. We had to fall back on UIKit maps to provide the full experience. Building the framework in a modular way, we also discovered that SwiftUI previews inside the module package didn’t work properly, adding another layer of complexity.

Challenge 2: Swift Package Manager in CI

At CMT, our mobile apps undergo nearly 100 releases every year, a feat made possible by continuous integration (CI). For our new framework, integrating CI/CD with the new codebase was crucial. In the past, we used Cocoapods for dependency management, but we switched to Swift Package Manager (SPM) for its seamless integration with Xcode and support from Apple. While SPM simplified project setup and the integration of third-party libraries on developers’ local machines, setting up the CI pipeline on Bitrise posed a challenge. We encountered issues integrating Cuckoo with SPM directly. Cuckoo requires that we generate mocks which is usually done using build steps, but it is missing in Swift Packages. We resolved this issue by looping through the packages and manually running the script to generate mocks before running tests. 

Another issue that we face is package versioning. When we update a package in an internal local swift package we need to manually open the package for the lock file to update otherwise the project does not build.

Challenge 3: Combine and Swift Concurrency

We embraced reactive programming, but Apple’s lack of updates to the Combine framework since its 2020 WWDC announcement presented a challenge. Instead, Apple made numerous updates to its Concurrency feature, incorporating features from Combine. We aimed to use the async/await style syntax in most of our interfaces and functions, but we still had to rely on Combine in many areas to achieve the desired functionality. Balancing the use of both required careful planning and implementation to ensure an efficient reactive programming experience.

Results Achieved with the New Framework

The implementation of the new framework has resulted in the following improvements:

  • Code complexity reduction: The previous framework contained over 202,000 lines of code (80% in Swift and 20% in Objective-C). As of June 2024, we have implemented 70% of the features using roughly 20,000 lines of Swift. We estimate that we will reduce the codebase by roughly 75% of the codebase after fully migrating all relevant features.
  • Delivery time and cost reduction: We have observed a 70% reduction in the time required to build new features, allowing CMT to deliver new functionalities to our users more quickly. Additionally, continuous integration (CI) times have been cut by 60%, accelerating our development cycles and lowering CI costs.
  • Increased test coverage: The non-UI code of Project Unity has 100% unit test coverage. The previous framework had only 10% non-UI unit test coverage. This increased coverage will help prevent regressions and improve code reliability and robustness. Looking ahead, we plan to implement UI automation tests, which will enable us to deliver updates faster and more frequently with greater confidence in quality.

Looking Ahead

Project Unity represents a milestone for CMT. By rebuilding our mobile frameworks, we have laid a solid foundation for future growth and innovation. The new framework enhances performance and scalability but also ensures that our apps can evolve with changing user expectations and technological advancements.

We are excited about the possibilities that Project Unity unlocks and look forward to delivering even better experiences to our users. Stay tuned for more updates as we continue this journey!


About The Author

Zongkun Dou, Ankit Singhania, Marinko Radic