DEV Community

loading...
Cover image for Building An iOS "App" With 30 Lines of Javascript

Building An iOS "App" With 30 Lines of Javascript

steventey profile image Steven Tey πŸ‘¨πŸ»β€πŸ’» Originally published at recurrence.app ・6 min read

Apple's iOS 14 update 5 months ago introduced iOS widgets, which allow users to get timely information from their favorite apps at a glance.

This also opened up a whole new realm of possibilites for developers to use widgets as surrogates for native iOS apps, hence saving tons of engineering resources while still providing an app-like experience for their users.

And this is how I was able to create a fully-functional "app" in the form of an iOS widget for Recurrence using Javascript, a RESTful API endpoint, and the Scriptable app.

What is Recurrence

Recurrence is a lightweight affiliate management software that allows you to keep track of all your affiliate sales in one single platform.

I built Recurrence with the objective of helping startup founders launch their affiliate programs in a hassle-free, one-click fashion without breaking the bank.

Abiding by the principle of dogfooding, I've been using Recurrence to track affiliate sales for my other project, One Word Domains, and the results have been super encouraging so far – over 456 clicks tracked and $275.65 in affiliate sales recorded.

However, the only way my affiliates were able to track the progress of their clicks was through the Recurrence web-app (pictured below), which was more of a desktop-first experience.

Affiliate Dashboard - Recurrence

While the web-app was optimized for mobile, I still wanted something that was more mobile-friendly. Something that was native to the iOS experience.

And that's when I came across this tweet by Pieter Levels.

Turns out, all you needed to do to build an iOS widget were 3 simple steps:

  1. Set up an API endpoint that contains the live data
  2. Download the Scriptable app
  3. Design and code the widget using Javascript

Building the API Endpoint

Since I was using the Flask framework, all I needed to do is add an extra route for the API endpoint:

from flask import Flask, jsonify
from flask_cors import cross_origin
import psycopg2

@app.route('/api/v1/<api_key>')
@cross_origin()
def fetch_data_api(api_key):
    db_cursor.execute("SELECT clicks, earnings FROM affiliates WHERE api_key = %s", (api_key,))
    data = db_cursor.fetchall()[0]
    clicks = data[0]
    earnings = data[1]
    data = {
        "stats": [{
            "clicks": clicks,
            "earnings": "{:.2f}".format(earnings)
        }]
    }
    return jsonify(data)
Enter fullscreen mode Exit fullscreen mode

There are a few things to note in this code block:

  • I'm using @cross_origin(), which is a decorator from the flask_cors library that allows cross-origin browser/API requests for flask apps. You might need to install the library with pip install flask_cors for this to work.
  • Since I'm using PostgreSQL as my database, db_cursor is my psycopg2 object, which allows me to connect to the database from a remote origin. You might change this depending on your database schema.
  • This function returns the data in JSON format using flask'sjsonify method. This step is key, since the Scriptable app only reads data in JSON format.

Downloading Scriptable

You can download the Scriptable app on the App Store here. However, for local development, I'd highly recommend using the Scriptable Mac Client – that way you can test your code directly on your laptop instead of copying it from VSCode/Sublime and pasting into your phone just to test it out.

Scriptable MacBook Client

Once you've opened the Scriptable Mac App, all you gotta do is click on the little "+" sign at the top right corner and start coding up your Javascript.

Designing and Coding the Widget

First, let's build the function that will fetch us some live data from the API endpoint that we built earlier:

async function loadData() {
  let url = "https://recurrence.app/api/v1/YOUR_API_KEY"
  let req = new Request(url)
  let json = await req.loadJSON()
  return json.stats
}
Enter fullscreen mode Exit fullscreen mode

Here, we're building an asynchronous function with the Request() constructor from the Fetch API. This function visits our endpoint and fetches the data in JSON format every minute or so (more on this later).

Then, we'll design the function that renders the frontend of our widget:

async function createWidget(data) {

    let widget = new ListWidget()
    widget.backgroundColor = new Color("#fff")
    widget.addSpacer()

    let stats = String(data[0].clicks) // In this case, we're getting the first element of the data object and the "clicks" child element.
    let stats_num = widget.addText(stats)
    stats_num.font = Font.boldSystemFont(50);
    stats_num.textColor = new Color("#4f5ce2")
    stats_num.centerAlignText()

    let subtitle = widget.addText("total clicks")
    subtitle.font = Font.mediumSystemFont(15);
    subtitle.textColor = new Color("#000")
    subtitle.centerAlignText()

    let image = widget.addImage(img)
    image.centerAlignImage()
    image.imageSize = new Size(100,35)

    return widget
}
Enter fullscreen mode Exit fullscreen mode

This function creates a widget instance with new ListWidget() and begins defining various design parameters to style the widget:

  • Set the background color of the widget to white with widget.backgroundColor
  • Add the data that was retrieved from the API endpoint with widget.addText(stats)
  • Style the displayed data with stats_num.font, stats_num.textColor, and stats_num.centerAlignText()
  • Define and style the subtitle (# of clicks, total earnings, etc.) with the subtitle object
  • Add the logo of the app at the bottom with widget.addImage(img).

You can also refer to Scriptable's official documentation for more information about widget styling.

Finally, we'll have to call the functions we created earlier and render it in the Scriptable app:

const i = await new Request("https://i.ibb.co/cNqLLbD/logo.png");
const img = await i.loadImage();
let data = await loadData()
let widget = await createWidget(data)
if (config.runsInWidget) {
  // The script runs inside a widget, so we pass our instance of ListWidget to be shown inside the widget on the Home Screen.
  Script.setWidget(widget)
} else {
  // The script runs inside the app, so we preview the widget.
  widget.presentMedium()
}
Script.complete()
Enter fullscreen mode Exit fullscreen mode

And that's it – you're all set! Go ahead and hit the "Run" button at the bottom right corner of the screen:

scriptable

...and, voilΓ ! Now all you gotta do is transfer this code over to the Scriptable app on your phone the same way you did on the Mac client. Here's the complete code snippet:

async function loadData() {
  let url = "https://recurrence.app/api/v1/YOUR_API_KEY"
  let req = new Request(url)
  let json = await req.loadJSON()
  return json.stats
}

async function createWidget(data) {

    let widget = new ListWidget()
    widget.backgroundColor = new Color("#fff")
    widget.addSpacer()

    let stats = String(data[0].clicks)
    let stats_num = widget.addText(stats)
    stats_num.font = Font.boldSystemFont(50);
    stats_num.textColor = new Color("#4f5ce2")
    stats_num.centerAlignText()

    let subtitle = widget.addText("total clicks")
    subtitle.font = Font.mediumSystemFont(15);
    subtitle.textColor = new Color("#000")
    subtitle.centerAlignText()

    let image = widget.addImage(img)
    image.centerAlignImage()
    image.imageSize = new Size(100,35)

    return widget
}

const i = await new Request("https://i.ibb.co/cNqLLbD/logo.png");
const img = await i.loadImage();
let data = await loadData()
let widget = await createWidget(data)
if (config.runsInWidget) {
  // The script runs inside a widget, so we pass our instance of ListWidget to be shown inside the widget on the Home Screen.
  Script.setWidget(widget)
} else {
  // The script runs inside the app, so we preview the widget.
  widget.presentMedium()
}
Script.complete()
Enter fullscreen mode Exit fullscreen mode

Adding Widget to Home Screen

Lastly, you'll have to add your widget to your iPhone home screen. The fastest way to do that in iOS 14 would be to:

  1. Tap and hold on your home screen until your apps begin to jiggle
  2. Click on the "+" button at the top right corner of the screen
  3. Select the Scriptable widget and your desired widget size
  4. Click on "Add Widget"
  5. Long hold on the widget again and select "Edit Widget"
  6. For the "Script" selection, choose the script you pasted in earlier
  7. For the "When Interacting" selection, choose "Open URL" and key in the URL for your app– in this case that would be https://recurrence.app/dashboard. This will allow you to get redirected to your app when you click on the widget.

Here's a quick GIF showing the steps above:

scriptable mobile

In most cases, the widget usually updates every minute or so, but if you want to receive notifications about the changes, you can set that in the Scriptable app under "Notifications".

If you did everything right, you should end up with the following widgets on your home screen:

scriptable widgets

Congratulations, you've just built your first iOS widget! πŸ₯³

Feel free to comment below or ping me via email or on Twitter if you run into any issues along the way – good luck!

Discussion

pic
Editor guide