Building An iOS "App" With 30 Lines of Javascript
Tags: javascript, ios, python, showdev
Published: 2020-11-30 17:09:53

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](https://recurrence.app/) using Javascript, a RESTful API endpoint, and the [Scriptable](https://scriptable.app/) app.

# What is Recurrence

[Recurrence](https://recurrence.app/) 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](https://www.oneword.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](https://recurrence.app/static/assets/blog-images/recurrence-dashboard.png)

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](https://twitter.com/levelsio/status/1330573624827392003?s=20) 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:

```python
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)
```

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's` jsonify` 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](https://apps.apple.com/us/app/scriptable/id1405459188). However, for local development, I'd highly recommend using the [Scriptable Mac Client](https://scriptable.app/mac-beta/) – 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](https://recurrence.app/static/assets/blog-images/scriptable-mac.png)

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:

```javascript
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
}
```

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:

```javascript
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
}
```

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](https://docs.scriptable.app/) for more information about widget styling.

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

```javascript
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()
```

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

![scriptable](https://recurrence.app/static/assets/blog-images/scriptable.gif)

...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:

```javascript
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()
```

# 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](https://recurrence.app/static/assets/blog-images/scriptable-mobile.gif)

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](https://recurrence.app/static/assets/blog-images/scriptable-widgets.png)

Congratulations, you've just built your first iOS widget! 🥳

Feel free to comment below or ping me via [email](mailto:steven@recurrence.app) or on [Twitter](https://twitter.com/FNTey) if you run into any issues along the way – good luck!