Change variable values instantly without rebuilds
Tags: ios, swift, devrel
Published: 2021-10-25 19:08:28

<!-- 
Big idea: Using runtime tweak improves your productivity by changing stuffs at runtime instead of having to recompile everything again.
 -->

Most of the apps distributed in the App Store requires a back end server. Facebook, Instagram, Twitter, Netflix, Amazon, you name it.

There are at least two kinds of servers involved when building these apps: production server and testing server.
Testing server is used to prevent unstable features from going to production.

This practice impacts our development activities. We often need to switch servers depending on the situation. How do we handle changing these servers?

## The simple way and the complexities it introduced

```swift
let productionURL = "https://api.myapp.com"
let stagingURL = "https://staging-api.myapp.com"

let baseURL = productionURL
```
This is the typical way people do to switch servers, simple and straightforward.
Change the `baseURL` to either `productionURL` or `stagingURL` when you need to, rebuild the app, and see the results. 

While this approach works, there are some problems you need to be aware of:

### 1. It gets slower over time
Your project's build speed will get slower as your codebase grows. 
I have reached a point where I have to wait for 3 minutes just to change servers.
![People slacking off waiting for their code to compile](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkdjc9j0kbh26zs3qiav.png "The infamous xkcd 303")

### 2. Human error eventually hits hard
When using the above technique, you have to modify your code in order to switch servers.
```diff
- let baseURL = productionURL
+ // let baseURL = productionURL
+ let baseURL = stagingURL
```

You have to be very careful to not let the code above get committed and published to the App Store.

This of course can be prevented by code review, but humans eventually make mistakes. It is only a matter of time when you let your guard down and published this to production. I did, at some point in my career.

### 3. Huge inconvenience for testers
Aside from developers, testers are the ones that also need access to both staging and production servers.

If you use CI/CD system to distribute test apps, then you need to provide two builds with each build pointing to a different server.

This is a big waste of time. Builds take twice longer, and also testers need to download the builds again just to switch servers.

So now you're convinced that even though the simple technique works, it has some drawbacks.
Wouldn't it be nice if we can switch servers instantly?

--------

## SwiftTweaks to the rescue
[SwiftTweaks](https://github.com/khan/SwiftTweaks) is a library to change things instantly without having to recompile, and written in ... Swift. _I thought it was PHP_ 😮.

### How does it work?
SwiftTweaks works by showing a pop up when you shake your phone. Choose whatever settings you want to tweak and that's it.

In our case, SwiftTweaks will show a menu so we can switch between servers instantly. No rebuilds needed.
![A video showing how to use SwiftTweaks from the app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ugu5nju0i1t1xoqaq5k.gif)

If this looks appealing to you, then let me show you how it's done.

### Let's build our own tweaks
Create a new project and add the library as instructed [here](https://github.com/khan/SwiftTweaks#installation). 
I won't go into details here since this is not a beginner article.
After you're done, we can start the next steps.

#### 1. Create a tweak store
```swift
// AppTweaks.swift
import SwiftTweaks

struct AppTweaks: TweakLibraryType {
    // A
    static let serverURL = Tweak<StringOption>(
        "General",
        "Networking",
        "Server URL",
        options: [
            "https://api.myapp.com", 
            "https://api-staging.myapp.com"
        ]
    )
    
    static var defaultStore: TweakStore {
        return TweakStore(
            tweaks: [serverURL], // B
            enabled: TweakDebug.isActive
        )
    }
}
```
This seems like a lot of code but trust me it is simpler than what it looks like.

There are two things that we do here, as marked in comments A and B.

In comment A, we are creating a tweakable `serverURL` with two available options: `https://api.myapp.com`, `https://api-staging.myapp.com`. 
The first option will be chosen as the default.
This option will show up under the _General_ > _Networking_ > _Server URL_ option. 

In comment B, we are putting all the available options in the `tweaks` argument so they can show up in the UI.

#### 2. Change the default UIWindow
These options will show up when you shake the phone while the app is active.
In order to do that, we need to use the `TweakWindow` class provided by SwiftTweaks instead of `UIWindow`.

```swift
window = TweakWindow(
    frame: UIScreen.main.bounds, 
    tweakStore: AppTweaks.defaultStore
)

// Create the root view controller as usual
window?.rootViewController = MyViewController()
window?.makeKeyAndVisible()
```

#### 3. Use the chosen option from the code
Now we need to change how the `baseURL` gets its value.

```diff
- let baseURL = productionURL
+ let baseURL = AppTweaks.assign(AppTweaks.serverURL).value
```
Instead of hardcoding the server URLs, we can now just let SwiftTweaks decide which server URL that we want to use.

#### 4. Restart the app and see the changes
We'll now see how easy it is to change configurations using SwiftTweaks:
- Run your app as usual
- Shake your device (`ctrl + cmd + z` from the simulator), a view controller will pop up showing all your configurations.
- Change your selected server from production to staging.
- Restart your app to see the changes.

![A video showing how to use SwiftTweaks from the app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ugu5nju0i1t1xoqaq5k.gif)

That's it! No more code changes needed when you want to switch servers. You can switch servers whenever you like.

### I don't want my users to use this tool
Me too. God knows what weird things your users will do with this.

So how do you prevent this tool from leaking to app store builds? **nothing**. 

Yes, you don't have to do anything. `SwiftTweaks` is smart enough to know that this will only show up in debug builds.

------

## Other useful SwiftTweaks usages
Now you have seen how powerful this tool is. You might be wondering, what else can you use this for? I have some ideas for you.

### Toggle onboardings or tutorials
Onboardings or tutorials teach users on how to use your app. It is a very common pattern nowadays.

The thing with onboardings is they only show up once when the users open the app for the first time.
```swift
let showOnboarding = !UserDefaults
    .standard
    .bool(forKey: "has_shown_onboarding")

if showOnboarding {
    UserDefaults
        .standard.set(true, forKey: "has_shown_onboarding")

    window?.rootViewController = OnboardingViewController()
} else {
    window?.rootViewController = MainViewController()
}
```
 
Ordinary humans like me can't develop onboardings perfectly on the first try. We have to run the app multiple times until we got it right. 
I usually hard coded `showOnboarding` to true until I've finished with the development. 

Thanks to `SwiftTweaks`, I don't have to do any hard coding anymore. All I have to do is create a new tweak.

```swift
static let showOnboarding = Tweak(
    "General",
    "Onboarding",
    "Show onboarding",
    !UserDefaults.standard.bool(forKey: "has_shown_onboarding")
)
```

And then I just need to change the logic for a bit.
```diff
- let showOnboarding = !UserDefaults
-     .standard
-     .bool(forKey: "has_shown_onboarding")
+ let showOnboarding = 
+    AppTweaks.assign(AppTweaks.alwaysShowOnboarding)

```

### Toggle SDK loggings
Third party SDKs such as Mixpanel have logging capability to ease debugging. 

```swift
Mixpanel.mainInstance().loggingEnabled = true
```

You mostly want this option to be turned off unless for some occasions. The reason is the logs can pollute your debugging console and make important information harder to find.

SwiftTweaks lets you toggle this on/off anytime you want.

```swift 
static let enableMixpanelLogging = Tweak(
    "General",
    "Logging",
    "Enable Mixpanel logging",
    false
)

Mixpanel.mainInstance().loggingEnabled = 
    AppTweaks.assign(AppTweaks.enableMixpanelLogging)
```

-------

## Conclusion
We do some small tweaks here and there in our coding activity. Tweaking those configurations doesn't have to be time consuming. SwiftTweaks lets you do small tweaks instantly. This will save you a lot of time.

There are so many things that you can do with this. You can change colors, fonts, animations on the fly with out having to recompile your code. 

Start using it now see how your quality of life gets better because of it.