2 Apps in 1 Workspace — Trendyol Go

Barkın Özakay
Trendyol Tech
Published in
11 min readMar 20, 2024

(Contributors: Aytuğ Sevgi & Barkın Özakay & Ceyda Karabulut)

Hello everyone, we will mention the Trendyol meal and grocery channels’ story from the Trendyol app to the brand new app as local commerce iOS mobile developers.

Today’s Trendyol app contains multiple services we call channels; it is an excellent example of a super app. As expected, handling and maintaining this in a workspace with many teams and team members is not easy. You can find detailed articles in the References section at the bottom, in which we adopted a modular approach and are using Tuist CLI to generate and maintain our project. Thanks to these, we could build our Meal & Grocery channels as separate apps to increase developer experience.

In the Trendyol app, you can access channels from specific banners both from the Homepage and Trendyol Go tabs. Then, the user navigates to one of these channels, as shown below.

Trendyol app Channel Navigation Flow

Recently, there has been a need for a new app for Trendyol Go. Initially, without delving into specifics, we planned with our team how to create and progress with the project in broad strokes. After outlining the app’s main features during this process, we finalized its design details in the last stage.

Pre-Start Topics

Before diving deep into it, let’s briefly outline what has been accomplished to initiate this new project.

  • Creating a new app target with Tuist
  • Integrating 3rd party dependencies (Firebase, Google Cloud etc.)
  • Creating a new app on App Store Connect and setting the initial configurations (App Information, Privacy and Availability, Users and Access)
  • Integrating CI/CD flow to automate the release upload process and necessary new Merge Request pipelines

Creating a new project with Tuist

The idea of introducing a new app within an existing project sounds intimidating. However, the better you manage dependency within your project, the easier your job becomes. As the Trendyol iOS team, we use Tuist, which significantly facilitates our work. You can look at this article. to learn about our modularization evolution and what we’ve done previously.

1- We created a new Project on Tuist.

import ProjectDescription

let project = Project(
name: "TrendyolGoApp",
targets: [
Target(
name: "TrendyolGoApp",
platform: .iOS,
product: .app,
bundleId: "bundleId",
sources: ["Sources/**"],
dependencies: [
mealDependencies,
groceryDependencies
]
)
]
)

2- We added the dependencies we use from 3rd parties to TrendyolGoApp. Afterward, we defined this project under our workspace.

let workspace = Workspace(
name: "Trendyol",
projects: [
"Trendyol",
"TrendyolGoApp"
]
)

Thus, the newly created application will only have dependencies independent of Trendyol. With the modular structure and dependency management, we prevent an unnecessary increase in both the build time and the size of the app.

3- We registered the dependencies in AppDelegate and got the project up and running.

import DependencyEngine
import Meal
import Grocery

@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
willFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
...
TrendyolGoDependencyRegistration.register(to: .shared) // module registeration which only depending to trendyol go
MLDependencyInitializer.execute() // meal dependency registeration
GRCDependencyInitializer.execute() // grocery dependency registeration
...
}
}

Challenges

Keychain — Authentication

We wanted users to do as little as possible on new app transitions, they should have been able to continue on the new app where they left off on the Trendyol app. The first and the most crucial thing was authentication.
However, authorization information is a secret and should be kept safe, so the keychain is the answer to achieve this. Keychain provides iOS apps with an encrypted database to store user information. Naturally, you can access this keychain data only for your app, but we need to transfer it from the Trendyol app to the Trendyol Go app upon users’ consent and contractual obligation. At this point, we researched and learned that keychain services allow you to securely share your keychain among the family of apps, which means that the same team develops it and relies on the same user secret, which is exactly what we are looking for. When you create an app, you should assign a bundle ID, and Xcode generates an app ID with your unique team ID.

[$(teamID).com.example.AppOne]

By this app ID, an application has its private access group. If you want to share keychain items between two apps, add both to the same access group. Adding another app’s bundle ID to keychain sharing enables this property on the application’s Signing & Capabilities settings. Then you can access the app’s, which has been added, keychain items. You can access Apple documentation here for more detailed information, but that was enough for our needs.

Keychain Sharing Setting in Xcode

As a result of this, we became able to share keychain information with the new app. But we usually read keychain values like this:

let bundleIdentifier = Bundle.main.bundleIdentifier

Keychain(service: bundleIdentifier, accessGroup: “\(teamId).\(bundleIdentifier)”)

This bundleIdentifier came from Foundation’s Bundle.main.bundleIdentifier before. But the new app gives you the new app’s bundle identifier, and we need Trendyol applications. So we changed bundleIdentifier as parametric, so we pass Trendyol’s value if we want to read something from there.

We have two layers to do keychain operations: Keychain Wrapper and Keychain Accessible.

Keychain Accessible: Inits the keychain with the bundleIdentifier parameter and does basic operations like read, write, check if something exists in the keychain, etc.

Keychain Wrapper: It takes the bundleIdentifier parameter, inherits from Keychain Accessible, and calls its operation functions.

We created a new structure called KeychainTransferKeys to read Trendyol’s keychain values. Firstly, we added a function that finds Trendyol’s bundle identifier for the current environment. We created KeychainWrapper with this value and read the necessary keychain value with its key. That’s all!

public enum KeychainTransferKeys {
public static let necessaryValue1Key = "necessaryValue1Key"
public static let necessaryValue2Key = "necessaryValue2Key"

public static func readValues() -> (
necessaryValue1: String?,
necessaryValue2: Data?
) {
func read(key: String) -> String? {
KeychainWrapper(bundleId: getSharedAppBundleId(with: Bundle.main.bundleIdentifier ?? .empty))
.data(key: key)
.flatMap { String(data: $0, encoding: .utf8) }
}

func read(key: String) -> Data? {
KeychainWrapper(bundleId: getSharedAppBundleId(with: Bundle.main.bundleIdentifier ?? .empty))
.data(key: key)
}

return (
read(key: necessaryValue1Key),
read(key: necessaryValue2Key)
)
}

static func getSharedAppBundleId(with currentId: String) -> String {
switch currentId {
case "testEnvironmentBundleIdForCurrentApp": return "testEnvironmentBundleIdForSharedApp"
case "prodEnvironmentBundleIdForCurrentApp": return "prodEnvironmentBundleIdForSharedApp"
default: return "prodEnvironmentBundleIdForSharedApp"
}
}
}

And then we can use it like that,

let keychainTransferKeys = KeychainTransferKeys.readValues()
let necessaryValue1 = keychainTransferKeys.necessaryValue1
let necessaryValue2 = keychainTransferKeys.necessaryValue2

As one of the beneficial aspects of being an agile team, we thought about the solution altogether, and the member team developed a new service to regenerate the carried token for the new application. So we called this service with the carried token, and it returned us a new regenerated token which is special for the new application, but both of them belong to the same user.

The next challenge for us was guest users! We also implemented a guest user flow for our new Trendyol Go app.

Fortunately, we already have some structures to benefit from because Trendyol has some screens that do not need login. We manage most of the navigation with the deeplink structure, and deeplinks have properties like shouldCheckAuthentication. If this property is true for a deeplink item, and there is no logged-in user, we show the login page.

Was it enough to change shouldCheckAuthentication as false for all grocery and meal deeplinks? Of course, no. Because the first one was, we still wanted to show the login page on the first channel opening from the new app, but we would show an option to continue. So we did not change the shouldCheckAuthentication for grocery and meal channel deeplink to false and did a specific development to continue from the login page. We just allowed the screens that can show without authentication as business decisions and changed their values to false. The second challenge here was, that some screens could be shown, but some services should not have been called, and some features should not have been executed without authentication in those screens also. We had already examined and decided the services that should have been executed and backend teams made arrangements for those if needed. Then we decided to create a login validator on the mobile side to check the necessary situations. For example, if there is no logged-in user, we do not call favorites restaurants or products. If it is related to a user action, for example, if the user wants to favorite a restaurant or product, we show the login page in the channel again, and if the user logs in, can continue to discover the app. It means we went over the whole channel screens and features from the login validator point. It was a big deal, but also the result is a good experience for the new users.

Navigation

Let’s look at how we handled the navigation from our core Trendyol app to our new Trendyol Go app.

Auto navigation for Trendyol Go App

The primary functionality of the Trendyol iOS app relies significantly on a deep linking structure. Take a look at the current deep link navigation setup by referring to the details outlined in this article.

Trendyol Go — Deeplink Navigation Flowchart

Programming isn’t about what you know; it’s about what you can figure out.Chris Pine

While we were discussing this feature’s technical requirements, we decided to use our main DeeplinkEngine to handle the navigation.

We agreed upon adding control to check whether the incoming deep link string contains any related channel parameters such as Meal or Grocery.

However, we encountered a certain challenge with this approach.

When we employ the same engine structure in both applications, it becomes crucial to determine whether the current instance is running within the core app or Trendyol Go app.

public extension Bundle {
var isTrendyolGoApp: Bool {
Bundle.main.infoDictionary?["CFBundleName"] as? String == "TrendyolGo"
}
}

The intervention in our engine class was pivotal, and the extension variable played a crucial role in effectively managing the navigation of our new app. We also used this variable when we wanted to develop app-specific features for our channels.

In addition to that, we used a deeplink parameter called isComingFromTrendyol to send the necessary events for data tracking when we navigate to our new app successfully.

Additionally, we had to incorporate these changes into the core app release in advance, ensuring a seamless transition when we successfully launch our new app.

Creating the Home Page with Widget Structure

Our homepage is the page that all users will always see when they first open the application. Since we will keep the pages as simple as possible during the MVP phase of the application and many components will be added in later versions, we decided to use a more dynamic structure, especially on the homepage. We already had a structure called a widget that draws the UI according to the response from the backend.

Differences between pages based on response

Thanks to the widget structure, we can update our homepage via the backend, regardless of the version. Thus, we can change the homepage of even users with version 1.0.0 of the application.

Testing Process

As expected, the testing process was challenging and it was also as important as it was challenging. First, we created a dev-process branch which was created from our main development branch when we started development. And we opened feature merge requests to this branch. During the fast development process, we did not wait for the testing of each feature as in the normal process, since we have an intermediate branch and some features are related to each other for technical reasons and these were not ready to test. After we completed the main features and fixes, our team’s QAs started to test screen by screen and listed the issues on a table. We chose this instead of opening all issues to the board to take quick action and to be able to see issues that can be related to the other. At this stage, we claimed issues and fixed them quickly. Thanks to the whole local commerce iOS team, helped to solve them quickly. In this stage, our product managers also started to test and gave us some feedback. After the team’s test & bug fix processes, we prepared a TestFlight version and added a group of people from Trendyol, who are from related teams and those who volunteered and created another table to pick their feedback. We are also thankful for all the team. This feedback gave us some business ideas and helped us to solve some of the bugs. In the Trendyol app, we have a variety of automated tests and we prepared the infrastructure for the new app and added some snapshot tests. We are planning to improve automated tests in the next period.

Branch Management in Regression Process

We proceed with the regression process as follows.

1- When Trendyol/Release is created, we create the TrendyolGo/Release branch from Trendyol/Release.
2- We are opening TrendyolGo/Release branch as merge request to TrendyolGo/Release. So we get auto-pull.
3- After Trendyol/Release is merged to develop and we publish our tgo package, we close TrendyolGo/Release merge request, open it to develop and merge it.

When merging to Development, we do not experience conflicts due to Trendyol/Release. We are going parallel with Trendyol/Release.

Branch management during the release process

CI/CD

Leveraging the advantages of a modularized project, we had to execute new workflows on our CI system as well. Thanks to our platform team, we integrated our new CI jobs to run the related tests.

Also on the CD part, the implementation to trigger a new TestFlight build upload was fairly straightforward since we created a new run target for our new app. (special thanks to Beyza Ince 🚀)

Learnings

As we mainly copied the core app target to our new Trendyol Go target, we forgot to remove some of the unnecessary keys from the Info.plist file. Our first app review request was rejected by Apple because of these redundant keys such as;

  • Apple Pay Capabilities
  • Camera access

Next Steps

As we successfully released our initial versions, we are currently working on new topics such as App Thinning by reducing the dependencies.

Conclusion

Special thanks to our channel team who tackled some bugs with speed and precision. It’s great to know we have such a highly qualified team ready to roll when things get a bit blurry 🥳.

Thanks for reading, do not hesitate to contact us if you have any questions!

References

Want to work in this team?

Be a part of something great! Trendyol is currently hiring. Visit the pages below for more information and to apply. 🧡

--

--