Over-the-air updates for WKWebView content
Tags: webdev, javascript, ios, mobile
Published: 2023-07-18 12:03:44

## ChattyWebviews is a simple tool based on Firebase to help you get more from your webviews
Webviews enable developers to reuse code across different platforms, leading to significant time and resource savings. Major companies like Netflix have successfully leveraged webviews in their apps, using web technologies to deliver consistent user experiences across iOS and Android.

Netflix, for instance, initially used webviews to rapidly experiment with and iterate on their user interface. As Jordanna Kwok, Senior Software Engineer at Netflix, explains in an interview with Hacking with Swift:
**"Webviews allowed us to A/B test large changes and get almost immediate feedback."**

This ability to move fast and adapt quickly is especially crucial for startups and new product development, where the landscape is constantly changing and speed to market can be a key differentiator.

One of the key advantages of webviews is the ability to update the JavaScript code remotely, allowing for real-time updates to the webview content. This is where Over-the-Air (OTA) updates come into play, enabling developers to push updates directly to the webview components of an app, bypassing the traditional app store approval process.

In this blog post, I'll introduce you to [ChattyWebviews](https://chattywebviews.com/), a simple framework that bridges the gap between web and native environments. ChattyWebviews facilitates seamless communication between webviews and native classes, making it an good solution for developers aiming to maintain agility in their app updates and promote UI reusability across platforms.

ChattyWebviews is not an alternative to native development or any cross-platform framework. It can be used alsongside any of them.

I'll guide you through the process of implementing OTA updates for the webview components of a standard iOS app using ChattyWebviews. We'll create a simple app with a login screen living in a webview. This login screen will not only be reusable between iOS and Android but also updatable using ChattyWebviews' OTA update feature.

So, let's dive in and explore how you can enhance your cross-platform app development process with Chatty Webviews and OTA updates.

We'll be using a simple app called **CWLoginDemo** for this purpose. You can create this app from scratch, or you can clone the project directly from [here](https://github.com/ChattyWebviews/CWLoginDemo).

The CWLoginDemo app consists of a home screen with a login button. When the login button is tapped, a modal screen is presented, displaying the login page. This login page is not a typical iOS view, but an Angular app running inside a webview.

The login page we're using is a modified version of a [project](https://jasonwatmore.com/post/2015/03/10/angularjs-user-registration-and-login-example-tutorial) by Jason Watmore. When the user enters their username and password into the fields on the login page, the native class will receive the message, process the login, and close the modal.
Let's get into the details.

## Setting up the CWLoginDemo App

Start by creating a new iOS app project and name it **CWLoginDemo**. Alternatively, you can clone the project directly from [here](https://github.com/ChattyWebviews/CWLoginDemo). The app should consist of a home screen with a login button.

```swift
import SwiftUI
import ChattyWebviews

struct ContentView: View {
    @State private var showingSheet = false
    
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Button("Login") {
                        showingSheet.toggle()
                    }
                    .sheet(isPresented: $showingSheet) {
                        SheetView()
                    }
        }
        .padding()
    }
    
    init(showingSheet: Bool = false) {
        self.showingSheet = showingSheet
        self.setupXPModules()
    }
    
    //1
    func setupXPModules() {
        let module = CWModule(name: "demo", location: .Resources)
        WebViewFactory.initModules(modules: [module])
    }
}

struct SheetView: UIViewControllerRepresentable, CWMessageDelegate {

    typealias UIViewControllerType = CWViewController

    @Environment(\.dismiss) var dismiss
    
    //3
    func controller(_ controller: ChattyWebviews.CWViewController, didReceive message: ChattyWebviews.CWMessage) {
        print(message)
        dismiss()
        
    }
    
    //2
    func makeUIViewController(context: Context) -> ChattyWebviews.CWViewController {
        let module = WebViewFactory.getModules()[0]
        let scheduleVC = WebViewFactory.createWebview(from: module, path: nil)
        scheduleVC?.messageDelegate = self
        return scheduleVC! 
    }
    
    func updateUIViewController(_ uiViewController: ChattyWebviews.CWViewController, context: Context) {
        
    }
    
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

1. First we need to initialize the web module with the name of our angular project contents (inside it's dist folder).

2. The makeUIViewController method returns a UIViewController, so we create the container of our webview app using WebViewFactory.createWebview. Remember to be careful with force unwrapping.

3. Above we set the messageDelegate property to self so the SheetView struct receives messages from the login webview. Here we just print the message and dismiss it.

Of course, for this to work you will have to install [ChattyWebviews SDK](https://github.com/ChattyWebviews/ChattyWebviews). For the sake of this tutorial I used SPM, but you can also use Cocoapods or copy-paste the source code.


## Integrating the Webview Login Page
The login page is an Angular app that we'll run inside a webview. When the login button on the home screen is tapped, a modal screen with the webview login page should be presented.

First, install the [chatty-webviews-js](https://github.com/ChattyWebviews/chatty-webviews-js) package:

```bash
npm install @chatty-webviews/js --save
```

When you have it installed, don't forget to call _attach()_ method:

```typescript
import { Component } from '@angular/core';
import { attach } from '@chatty-webviews/js';

@Component({ selector: 'app-root', templateUrl: 'app.component.html' })
export class AppComponent {

    constructor() {
        attach();  
    }
}
```

Then in **login.component.ts** I replaced the **this.accountService.login** call with _sendMessage()_. Here is the entire component:

```typescript
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { sendMessage } from '@chatty-webviews/js';

import { AlertService } from '../_services';

@Component({ templateUrl: 'login.component.html' })
export class LoginComponent implements OnInit {
    form!: FormGroup;
    loading = false;
    submitted = false;

    constructor(
        private formBuilder: FormBuilder,
        private alertService: AlertService
    ) { }

    ngOnInit() {
        this.form = this.formBuilder.group({
            username: ['', Validators.required],
            password: ['', Validators.required]
        });
    }

    // convenience getter for easy access to form fields
    get f() { return this.form.controls; }

    onSubmit() {
        this.submitted = true;

        // reset alerts on submit
        this.alertService.clear();

        // stop here if form is invalid
        if (this.form.invalid) {
            return;
        }

        this.loading = true;
        sendMessage("login-successful", {username: "Teodor"});
    }

}
```

When the user enters their username and password into the fields on the login page, the native class will receive the message. This message should trigger the processing of the login and the closing of the modal.

Now build the project:

```bash
npm run build
```

When the build completes drag the project folder from inside the dist directory to your Xcode project. It's important to select **"Create folder references"**. If you leave **"Copy items if needed"** unchecked you won't need to drag the package folder after every build, but it won't be in the Xcode project repository, so decide based on your project structure.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xc9qio47qtoyppcztmcl.png)

Now, if you run the app and tap on the Login button you should see this:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qb7sp54f7he26xdiobra.png)

## Setting Up Your Environment for Over-the-Air Updates

Before we dive into the process of implementing over-the-air updates, we need to set up our environment. This involves creating a Firebase project and deploying the backend function to Firebase. Here's how to do it:

## Step 1: Create a Firebase Project
First, you'll need to create a Firebase project.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zdcjd0h85bmebayg0ev3.png)

As part of this process, make sure to enable authentication with email/password, storage, and the Firestore database. Please note that you'll need to have billing enabled to deploy the functions.

## Step 2: Clone the Backend Repository
Next, clone the backend repository from here. Once you've cloned the repository, run `npm install` to install the necessary dependencies.

## Step 3: Install Firebase Tools
If you haven't installed Firebase tools yet, you can do so with the following command:

```bash
npm install -g firebase-tools
```

Then, log in to Firebase using the command:

```bash
firebase login
```

## Step 4: Deploy Functions to Firebase
Now, it's time to deploy the functions to Firebase. You can do this with the following command:

```bash
firebase deploy - only functions - project <your-project-id>
```

You can find your project ID by running:

```bash
firebase projects:list
```

_If you encounter an error that says "Error: There was an error deploying functions" it's likely related to your Node.js version. Ensure you're using Node.js 16 (or the same version as specified in the functions' package.json):_

```json
"engines": {
 "node": "16"
}
```

After you've deployed the function successfully, copy it's url from the function's dashboard:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9fq3wpkulgzddsw7lakv.png)

With these steps, you've successfully set up your environment for over-the-air updates. In the next section, we'll dive into how to implement these updates in your iOS app.

## Pushing the updates

Now that we have our environment set up, we can proceed to implement over-the-air updates. This process involves using the ChattyWebviews CLI and setting up some environment variables on your machine.

## Step 1: Install ChattyWebviews CLI
The [ChattyWebviews CLI](https://github.com/ChattyWebviews/chatty-webviews-cli) is a command-line interface that simplifies the process of managing your ChattyWebviews projects. To install it, run the following command:

```bash
npm install -g @chatty-webviews/cli
```

## Step 2: Set Up Environment Variables
Next, you need to set up some environment variables on your machine. These variables will store the configuration details of your Firebase project. Here's how to do it:

On a Unix-based system like Linux or macOS, you can set environment variables using the `export` command:

```bash
export FIREBASE_API_KEY="<your_api_key>"
export FIREBASE_AUTH_DOMAIN="<your_auth_domain>"
export FIREBASE_PROJECT_ID="<your_project_id>"
export FIREBASE_STORAGE_BUCKET="<your_storage_bucket>"
export FIREBASE_APP_ID="<your_app_id>"
```

On Windows, you can use the `set` command:

```bash
set FIREBASE_API_KEY="<your_api_key>"
set FIREBASE_AUTH_DOMAIN="<your_auth_domain>"
set FIREBASE_PROJECT_ID="<your_project_id>"
set FIREBASE_STORAGE_BUCKET="<your_storage_bucket>"
set FIREBASE_APP_ID="<your_app_id>"
```

These commands set the environment variables for the current session only. If you want to set them permanently, you'll need to add these lines to your shell's startup file (`.bashrc`, `.bash_profile`, or `.zshrc` for Unix-based systems, or Environment Variables on Windows).

Replace `<your_api_key>`, `<your_auth_domain>`, `<your_project_id>`, `<your_storage_bucket>`, and `<your_app_id>` with your actual Firebase project details. You can get them by creating a web project from the Firebase console:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5glldachyny5uk7get6.png)

With the ChattyWebviews CLI installed and your environment variables set, you're now ready to push over-the-air updates to your app. First, tell ChattyWebviews more about your project by calling this command:

```bash
chatty init
```

It will ask you for email, password and the path to your package directory - in our case I pasted the absolute path to _dist/demo_.

Now go to **login.component.ts** and update something in the UI - e.g. replace the login button title with capital letters.

```html
<button [disabled]="loading" class="btn btn-primary">
   <span *ngIf="loading" class="spinner-border spinner-border-sm me-1"></span>
   LOGIN
</button>
```

Build the package:

```
npm run build
```

And push the update to Firebase:

```
chatty ci release --version 1.0.1
```

Before testing the update you need to add few things to the setupXPModules:

```swift
func setupXPModules() {
        ModuleSynchronizer.updateCheckUrl = "<your-checkForUpdate-url>"
      
        let module = CWModule(name: "demo", currentHash: "", location: .Resources)
        WebViewFactory.initModules(modules: [module])
        ModuleSynchronizer.shared.updateIfNeeded(module: module, email: "tester@test.com", appId: "cw-login-demo") { success in
            print("Module \(module.name) updated! Please restart the app.")
        }
    }
```

When you run the app the login screen will be updated.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/owod2bt3v1kuty5gunmw.png)