DEV Community

Cover image for How to Implement a Cross-Platform Login Screen with Angular within a WKWebView
Teodor Dermendzhiev for ChattyWebviews

Posted on

How to Implement a Cross-Platform Login Screen with Angular within a WKWebView

TLDR;

You can develop cross-platform components for your mobile apps using webviews without necessarily having to rely on hybrid frameworks.

Introduction

In many cases, especially when you're building a native app, you might want to have a cross-platform authentication flow. This could be because you want to maintain the native performance and feel for the rest of your app, but need a quick and efficient way to handle authentication.

You could go for a full-fledged hybrid app framework like Ionic or React Native, but that might be overkill if you're only looking to make a few parts of your app cross-platform. Plus, transitioning to a hybrid app framework comes with its own set of challenges, like learning a new programming language or dealing with the nuances of a different development environment. Or you might just have a web developer to help you with few screens...

This is where webviews come in. By creating a login screen using a webview, you can write the code once with a framework you (or whoever is writing the component) are already familiar with (Angular, React, Vue or whatever) and use it across multiple platforms. It's a lightweight solution that doesn't require a full hybrid app framework, making it perfect for simpler features like a login screen.

And to make things even easier, we're going to use an open-source library I created called ChattyWebviews. It's designed to facilitate communication between webviews and native classes, making it a breeze to integrate webview-based features into your native apps. If you find the article useful, consider giving it a star, so I can continue working on it - https://github.com/ChattyWebviews/ChattyWebviews.

Creating the Webview Login Screen

For the purpose of this tutorial, we'll use Ionic to create our login screen. Ionic is a popular framework for building cross-platform applications using web technologies. It's a great choice for our login screen because it allows us to write our code with javascript and have the native iOS or Android look.

First, let's install Ionic if you haven't done so already:

npm install -g @ionic/cli
Enter fullscreen mode Exit fullscreen mode

Next, create a new Ionic project:

ionic start LoginDemo blank --type=angular
Enter fullscreen mode Exit fullscreen mode

Navigate to the new project directory:

cd LoginDemo
Enter fullscreen mode Exit fullscreen mode

Now, let's create a simple login form. Open src/app/home/home.page.html and replace its content with the following:

<ion-header>
  <ion-toolbar>
    <ion-title>
      Login
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
  <form (ngSubmit)="onSubmit()" #loginForm="ngForm">
    <ion-item>
      <ion-label position="floating">Username</ion-label>
      <ion-input type="text" ngModel name="username" required></ion-input>
    </ion-item>
    <ion-item>
      <ion-label position="floating">Password</ion-label>
      <ion-input type="password" ngModel name="password" required></ion-input>
    </ion-item>
    <ion-button expand="full" type="submit" [disabled]="!loginForm.valid">Login</ion-button>
  </form>
</ion-content>
Enter fullscreen mode Exit fullscreen mode

In src/app/home/home.page.ts add the onSubmit() method:

import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {

  constructor() {}

  onSubmit() {
      //here we will send the message to the native class
  }

}
Enter fullscreen mode Exit fullscreen mode

This creates a simple login form with fields for username and password. The (ngSubmit) directive is an Angular feature that allows us to run a function when the form is submitted.

In the next section, we'll look at how to handle the form submission and communicate with our native app using ChattyWebviews.

Handling Form Submission with ChattyWebviews

Now that we have our login form, we need to handle the form submission. When the user submits the form, we want to send the username and password to our native app. This is where ChattyWebviews comes in.

First, install ChattyWebviews in your Ionic project:

npm install @chatty-webviews/js
Enter fullscreen mode Exit fullscreen mode

Then open the AppComponent and attach the messaging service:

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

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

    constructor() {
        attach();  
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, open src/app/home/home.page.ts and replace its content with the following:

import { Component, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { sendMessage } from '@chatty-webviews/js';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  @ViewChild('loginForm') loginForm!: NgForm;

  constructor() {}

  onSubmit() {
    if (this.loginForm.valid) {
      const { username, password } = this.loginForm.value;
      sendMessage('login', { username, password });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In this code, we're importing sendMessage from @chatty-webviews/js. We're also defining an onSubmit method that gets called when the form is submitted. In this method, we're sending a message to our native app with the username and password.

Setting Up the Native App

We need to set up our native app to display the login screen and handle messages from the webview. We'll be using SwiftUI for this.

Before writing the code, we need to do few things. First, add the ChattyWebviews' SPM package by going to File->Add Packages and pasting the repository url:

https://github.com/ChattyWebviews/ChattyWebviews
Enter fullscreen mode Exit fullscreen mode

You will also need to build the Ionic web app package by running ionic build. When the build is done, drag the www folder inside the Xcode project:

Drag the www folder in the Xcode project tree

Note: It's important to select Create folder references. If you want the package to be automatically updated after every build, leave Copy items if needed unchecked.

Select Create folder references

Here's the code for our main view, ContentView:

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.setupModules()
    }

    func setupModules() {

        let module = CWModule(name: "www", location: .Resources)
        WebViewFactory.initModules(modules: [module])
    }
}
Enter fullscreen mode Exit fullscreen mode

In this code, we're creating a SwiftUI view with a button that displays a login screen when clicked. The login screen is a SheetView, which we'll define next.

Creating the Webview Login Screen

Next, we'll create the SheetView that displays the login screen. This view is a UIViewControllerRepresentable, which means it's a wrapper for a UIKit view controller that can be used in SwiftUI.

Here's the code for SheetView:

struct SheetView: UIViewControllerRepresentable, CWMessageDelegate {
    typealias UIViewControllerType = CWViewController
    @Environment(\.dismiss) var dismiss

    func controller(_ controller: ChattyWebviews.CWViewController, didReceive message: ChattyWebviews.CWMessage) {
        print(message)
        dismiss()

    }

    func makeUIViewController(context: Context) -> ChattyWebviews.CWViewController {
        let module = WebViewFactory.getModules()[0]
        let scheduleVC = WebViewFactory.createWebview(from: module, path: nil)
        scheduleVC?.messageDelegate = self
        return scheduleVC!
    }
}
Enter fullscreen mode Exit fullscreen mode

In this code, we're creating a CWViewController that displays the login screen. We're also setting up a message delegate to handle messages from the webview. When a message is received, we print it and dismiss the login screen.

Handling Messages in the Native App

Finally, we need to handle the messages that we receive from the webview. This involves setting up a CWMessageDelegate in our native app.

Here's the code for the message delegate:

extension SheetView: CWMessageDelegate {

    func controller(_ controller: ChattyWebviews.CWViewController, didReceive message: ChattyWebviews.CWMessage) {
        print(message)
        dismiss()

    }
}
Enter fullscreen mode Exit fullscreen mode

In this code, we're defining a method that gets called when a message is received from the webview. We're printing the message and dismissing the login screen.

Fetching Login Credentials in Native Code

In our SheetView class, we have already set up a CWMessageDelegate to handle messages from the webview. When a message is received, the controller(_:didReceive:) method is called. This is where we can fetch the login credentials.

Here's how we can modify this method to fetch the login credentials:

func controller(_ controller: ChattyWebviews.CWViewController, didReceive message: ChattyWebviews.CWMessage) {
    if message.topic == "login" {
        let credentials = message.body as? [String: String]
        let username = credentials?["username"]
        let password = credentials?["password"]
        print("Username: \(username ?? ""), Password: \(password ?? "")")
    }
    dismiss()
}
Enter fullscreen mode Exit fullscreen mode

In this code, we're checking if the message topic is "login". If it is, we're fetching the username and password from the message body and printing them, then dismissing the modal:

Demo gif

Handling Login in Native Code

Now that we have the login credentials, we can handle the login in our native code. This could involve validating the credentials, fetching user data from a server, etc.

Here's an example of how you might handle the login:

func handleLogin(username: String, password: String) {
    // Validate the credentials
    guard validateCredentials(username: username, password: password) else {
        print("Invalid credentials")
        return
    }

    // Fetch user data from server
    fetchUserData(username: username) { userData in
        // Handle user data
    }
}
Enter fullscreen mode Exit fullscreen mode

In this code, we're validating the credentials and fetching user data from a server. The validateCredentials(username:password:) and fetchUserData(username:completion:) methods are not defined here, as their implementation would depend on your specific use case.

Conclusion

With this setup, you can create a simple login screen using a webview and handle the login in your native code. This approach is quick and easy, and it doesn't require a full hybrid app framework like Capacitor. It also allows you to reuse your web code across different platforms, which can save you a lot of time and effort.

Top comments (0)