Embracing Progressive Web Apps: Our Journey to a Unified Platform

In today’s fast-paced tech environment, maintaining multiple platforms—a web app, an Android app, and an iOS app—is an ambitious and resource-intensive endeavor. For our team, a small group focused almost exclusively on web development, this challenge became increasingly clear. With limited mobile app development expertise, we found ourselves investing significant time and resources into maintaining mobile apps. This ultimately led us to adopt a Progressive Web App (PWA)-inspired approach to unify our codebase across platforms, albeit with some key differences from a traditional PWA model.

What is a Progressive Web App (PWA)?

A Progressive Web App (PWA) is a web application designed to deliver an app-like experience directly from the browser. PWAs combine the best of web and mobile apps, leveraging modern web capabilities to provide functionality like:

  • Cross-Platform Compatibility: A single codebase that works seamlessly across web, Android, and iOS.
  • Offline Access: PWAs use service workers to cache resources, enabling offline functionality and faster load times.
  • Installability: PWAs can be installed directly from the browser, eliminating the need for an app store.
  • Push Notifications: Support for native-like notifications to keep users engaged.
  • Responsive Design: Ensuring a consistent experience across devices of all screen sizes.

Let’s dive into an example to illustrate how a PWA can be built using React and TypeScript. For this example, we’ll create a compound interest calculator that works across web, Android, and iOS.

Code Example: Building a PWA with React and TypeScript

Here’s how to set up a simple compound interest calculator:

React and TypeScript Web App

// src/App.tsx
import React, { useState } from 'react';

const App: React.FC = () => {
    const [principal, setPrincipal] = useState<number>(0);
    const [yearlyAddition, setYearlyAddition] = useState<number>(0);
    const [rate, setRate] = useState<number>(0);
    const [years, setYears] = useState<number>(0);
    const [result, setResult] = useState<number | null>(null);

    const calculateCompoundInterest = () => {
        let total = principal;
        for (let i = 0; i < years; i++) {
            total += yearlyAddition;
            total *= 1 + rate / 100;
        }
        setResult(total);
    };

    return (
        <div style={{ padding: '20px', fontFamily: 'Arial' }}>
            <h1>Compound Interest Calculator</h1>
            <label>
                Principal Amount:
                <input
                    type="number"
                    value={principal}
                    onChange={(e) => setPrincipal(Number(e.target.value))}
                />
            </label>
            <br />
            <label>
                Yearly Additions:
                <input
                    type="number"
                    value={yearlyAddition}
                    onChange={(e) => setYearlyAddition(Number(e.target.value))}
                />
            </label>
            <br />
            <label>
                Growth Rate (%):
                <input
                    type="number"
                    value={rate}
                    onChange={(e) => setRate(Number(e.target.value))}
                />
            </label>
            <br />
            <label>
                Years to Grow:
                <input
                    type="number"
                    value={years}
                    onChange={(e) => setYears(Number(e.target.value))}
                />
            </label>
            <br />
            <button onClick={calculateCompoundInterest}>Calculate</button>
            {result !== null && <h2>Future Value: ${result.toFixed(2)}</h2>}
        </div>
    );
};

export default App;

Service Worker for Offline Support To add offline capabilities, register a service worker:

// src/service-worker.ts
self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open('pwa-cache').then((cache) => {
            return cache.addAll(['/index.html', '/static/js/bundle.js']);
        })
    );
});

self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request).then((response) => {
            return response || fetch(event.request);
        })
    );
});

Android and iOS Integration PWAs can be added to home screens on Android and iOS without requiring native development.

For Android: Create a Web App Manifest file (manifest.json):

{
    "name": "Compound Interest Calculator",
    "short_name": "InterestCalc",
    "start_url": "/",
    "display": "standalone",
    "background_color": "#ffffff",
    "theme_color": "#000000",
    "icons": [
        {
            "src": "icon-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "icon-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ]
}

For iOS: Add meta tags to the index.html file:

<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="InterestCalc">
<link rel="apple-touch-icon" href="icon-192x192.png">

This example showcases how a React and TypeScript-based PWA can serve as a cross-platform solution, reducing the need for platform-specific code while still delivering a seamless user experience.

Why PWAs? One Codebase for All Platforms

For teams like ours, PWAs are appealing because they simplify the development process. By focusing on a single web-based codebase, developers can create an app that:

  • Runs on desktops, tablets, and smartphones without platform-specific code.
  • Reduces the time, cost, and effort required for updates and maintenance.
  • Allows rapid iteration and deployment without going through app store approval processes.

Our Approach: A PWA-Inspired Solution with Key Differences

While the PWA framework offers a lot of potential, our team decided to deviate from the traditional model for a few strategic reasons:

  • No Offline Mode (for Now) Unlike a standard PWA, our application does not support offline mode in its first iteration. This decision was based on usage patterns among our customers, where the vast majority rely on our app while connected to the internet. Offline mode remains on our roadmap as a potential future enhancement, but it was deprioritized to focus on immediate needs. As we consider offline mode in the future, the following two options stand as the most likely candidates:
    • Service Workers: Cache key assets and APIs to enable offline access.
      • Pros: Improves user experience and reliability.
      • Cons: Can add complexity in managing cached data and syncing when users come back online.
    • IndexedDB or LocalStorage: Store user data locally for offline access.
      • Pros: Useful for apps requiring data entry or retrieval without a connection.
      • Cons: Requires careful data synchronization strategies.
  • No Direct Install from the Browser A hallmark feature of PWAs is the ability to install the app directly from the browser. However, we opted not to enable this functionality at this stage. Instead, we’re leveraging our partnership with another team in our organization, whose mobile app includes a link to our web app. This integration allows users to log in and use our app within a web view on their mobile devices.

    By embedding our web app within the partner’s mobile app, we’ve effectively eliminated the need to maintain standalone mobile apps, significantly reducing development overhead. This approach also aligns with a larger organizational strategy to create more integrated solutions across teams.
  • Focus on Mobile-Friendly Design To ensure our web app provides a seamless experience on mobile devices, we prioritized the following updates:
    • Responsive Design Enhancements: Ensuring every element scales properly across devices.
    • Touch-Friendly UI Elements: Redesigning buttons, menus, and inputs for ease of use on touchscreens.
    • Smoother Navigation: Adding new UI elements to facilitate intuitive navigation on both web and mobile platforms.

How Our Solution Works

When users open the partner’s mobile app, they are directed to log into our app. Behind the scenes, this link points to our responsive web app, which is rendered in a web view within the mobile app. This approach allows us to:

  • Maintain a single web-based codebase.
  • Deliver a consistent experience across devices without duplicating development efforts.
  • Leverage the mobile app’s existing infrastructure for features like authentication and navigation.

Next Steps: Testing and Feedback

Our current implementation serves as a stepping stone to evaluate the viability of this approach. By directing users to the partner’s mobile app instead of standalone apps, we can assess:

  • Customer reception: Does this approach meet their needs, or do they prefer native apps?
  • Performance: How does the web app perform in a mobile web view?
  • Feature gaps: What additional functionality, such as offline mode or push notifications, would enhance the experience?

Final Thoughts

For our team, adopting a PWA-inspired strategy has been a practical solution to address the challenges of maintaining multiple platforms. While we’ve deviated from the traditional PWA model by skipping offline functionality and direct installability, we’ve leveraged the strengths of responsive web design and organizational partnerships to create a unified, scalable solution. As we gather feedback from users and iterate on this approach, we’ll continue to explore how to enhance the experience, potentially reintroducing offline mode or other PWA features in future updates.

Ultimately, this journey highlights the flexibility of modern web technologies and the importance of tailoring solutions to meet both organizational goals and user needs.

Further Reading:

Leave a Reply

Your email address will not be published. Required fields are marked *