How to add auto-update feature in macOS app: Step by Step guide to setup Sparkle framework (Part 1)
Tags: macos, sparkle, tutorial, swift
Published: 2019-11-13 22:03:02

In this multi-part tutorial, I'll walk you step by step on how to add and configure the auto-update framework [Sparkle](https://sparkle-project.org) in your macOS app. Sparkle has an excellent [basic setup](https://sparkle-project.org/documentation/) page but in my opinion, it is a bit too advanced and maybe a bit overwhelming to follow along. In this tutorial, my effort will be to make it easy for someone to add and configure Sparkle.

If you are familiar with the macOS app releases and why Sparkle is needed, then you can skip the next section and start [here](#gettingStarted).

#Why Sparkle?#

To answer this, let's rewind a bit. So you had an idea about a macOS app. You researched and found no perfect app (solution) exists that solves the problem and you decide to solve by building the perfect app. You burn the midnight oil and finally come up with a build of an app, which is in a state that you are not *too* [embarrassed](https://www.businessinsider.com/the-iterate-fast-and-release-often-philosophy-of-entrepreneurship-2009-11) to showcase to the world. You ship your first version.

Post shipping is when work truly begins. User downloads your app and provides their feedback. A cycle begins which starts by listening to the user's feedback, incorporating them and shipping the second version. And third and fourth and it continues. In this cycle, a very critical element and the question is **how to provide new versions to users?**

One way to do it is via Apple's Mac app store. While it's great option (no hosting fees, no monthly storage and bandwidth fees especially if your app is free and app is auto-updated with new versions) but it comes with mandatory requirements, for e.g. Sandbox should be enabled etc., that one needs to implement in their app and which may not allow your app to work in a way that you think provide the best experience. So the only option left is hosting it outside of mac app store. 

*How do you then provide new versions of your app to users seamlessly?* 

**This is where [Sparkle](https://sparkle-project.org) comes in**

> "*Sparkle is an easy-to-use software update framework for macOS applications.*"

One should always have an auto-update feature in their app starting with the first release, lest you will be in my shoes 😔. 

*Personal plug:* I released a mac menu bar app [EasyFinder](https://www.easyfinderapp.com) and hosted it outside of mac app store. 

{% twitter 1101118647169417217 %}

To provide peace of mind to users and of course assurance to macOS Gatekeeper ☺️ so that it let EasyFinder run, EasyFinder has been dev signed and [notarized](https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution) by apple. It doesn't have an auto-update framework and so I cannot push new versions to users and alert them. The second version of [EasyFinder](https://www.easyfinderapp.com) will be a new launch on [Product Hunt](https://www.producthunt.com/posts/easyfinder), something one may not want unless it's a mega update. Thankfully the second version of EasyFinder will bring a whole bunch of new experiences and features which warrant a hard launch instead of a soft launch. 

*However, lacking the ability to push new version limits me in a way.* I would have liked to push a few minor updates to get user's feedback on some new features that I am building before doing a version 2 launch. Thus in my experience, I would **strongly** advise adding auto-update feature in your macOS app starting with the first release. This is what this tutorial is about. Adding Sparkle to an app before it's first released or in some cases like me post first release.

# Getting Started <a name="gettingStarted"></a>

Let's get started. I will take you step by step on how to setup Sparkle framework in your macOS app and last but not least, configure it properly so that apple agrees to notarize it 😎. Yes, Sparkle needs to be code signed as well or else your app will not be accepted for notarization. Why should we bother about Apple notarization? More on this later.

We will add and configure Sparkle to an existing macOS project. I have created a starter project. Go ahead and [download](https://github.com/ajaxbuddy/SparkleSetupGuide) the project. After downloading, build and run the project. If everything goes well you should see a Star icon in your menu bar   👇
![Menu bar icon](https://thepracticaldev.s3.amazonaws.com/i/xcr9x1z02c5e1nmw80mh.png)

Click on the star and it will open a pop up displaying a label "First Version"

![First version of the app](https://thepracticaldev.s3.amazonaws.com/i/yolp16lbpcv8vujwbbm0.png)

Clicking on the button labeled "Check for Updates" doesn't do anything. It's just a placeholder for now and shortly we will add functionality to this button to check if there is a newer version of the app available


### Adding Sparkle framework to Xcode project

We will use [cocoapods](https://cocoapods.org) to add Sparkle. So first let's add pod file by opening Xcode project location in Terminal and typing 

```pod init```

*Directory content before initiating pod*
![Directory content before initiating pod](https://thepracticaldev.s3.amazonaws.com/i/0jgjm58e7r60z5nf6sc5.png)


*Directory content after initiating pod*
![Directory content after initiating pod](https://thepracticaldev.s3.amazonaws.com/i/h92ja5hhomvpcc5dm2fc.png)

As you can see in the screenshot above, a new file named **Podfile** has been added to the project directory. In this new file, we will add the Sparkle framework pod name. Open Podfile in your favorite editor and add following in the file

```  pod 'Sparkle'```

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/ju07ck87uzdq3hbb9a6x.png)

Now that we have declared, via Pod, that our project requires Sparkle framework, it's time to install the pod. Close the Xcode project.

Go back to the same location in Terminal and install the Sparkle framework by executing the following command:

```pod install```
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/1nmi3udrvoz6vheqbwfl.png)

This will add Sparkle framework as a dependency in your Xcode project and will also create a new Xcode project file with extension **xcworkspace**. 
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/mo8xv6azwcfppgp6ngnr.png)

From now on we will use this file **SparkleSetupGuide.xcworkspace** to open our project. Open your project using this file. Your Xcode project should have a Pod project like in the screenshot below
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/pmalrb83wmnxk7xrms8k.png)

Build and run your project. The result should be similar to when we built and ran before installing the pod.


### Configuring Sparkle

Open *Main.storyboard* file

Open library window by either clicking "**+**" above right side pane or by using short cut `Command` + `Shift` + `L`. Search for 'Object' and drag it to Application Scene

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/b1fkmrw755ud6lvez0nb.gif)

Update the class for this newly added Object to `SUUpdater` in Identity Inspector

> `SUUpdater` - *This class is used to configure the update parameters as well as manually and automatically schedule and control checks for updates*


Sparkle gives the user an option via an alert, whether they prefer to automatically check for updates or they want to do it manually. This happens usually after the first launch of the app. Since we have added Sparkle updater object, we should see an alert on or after the second run. Run the app, shut it and then re-run it. You should see below alert:

 ![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/j8kpnfdxu0hkuaztfwa5.png)


**Let's recap what we have done so far. We have added the Sparkle framework in our project via cocoapods. Subsequently, we added an Object in our storyboard's Application Scene and updated that Object's class to `SUUpdater`. The object will now be able to manually or automatically check for updates**

Now, since the user can choose to check for updates manually, we should provide that feature in our app. Remember that dumb button "Check for updates" that did nothing? Well, let's make button intelligent and useful so that the user can use it to check if there is a new version of the app available.

*Note: Since checking for updates is not the core functionality of an app, you will usually provide this in the preference section of your app instead of putting it in the main screen as we did*

Open `VersionDisplayViewController.swift` file 

Add the Sparkle module by importing it. Add following import

```swift
import Sparkle
```

Now update `checkForUpdates` function with following code:

```swift
    @IBAction func checkForUpdates(_ sender: Any) {
        let updater = SUUpdater.shared()
        updater?.feedURL = URL(string: "some mystery location")
        updater?.checkForUpdates(self)
    }
```

The above code is getting an instance of class `SUUpdater`, setting *some mystery location* to instance's feedURL property and then asking `SUUpdater` instance to check at that *some mystery location* if there is any new version of the app available.

Clicking on the button does nothing but it will generate following error in Xcode console: <a name="noFeedURLErrorMessage"></a>

```swift
[General] You must specify the URL of the appcast as the SUFeedURL key in either the Info.plist or the user defaults!
```

No one likes seeing errors and nor do we. We will get rid of them shortly but there are a couple of new jargon in this error. *appcast*, *SUFeedURL key*. Further, you might be wondering about this *some mystery location* and why are we hard coding (not a best practice)? and how does Sparkle figures out if and when there is a new version of your app is available?

#####First, let's understand how Sparkle works

>*Sparkle uses appcasts to get information about app updates. An appcast is an RSS feed with some extra information for Sparkle’s purposes.*

In plain words, Sparkle polls "some location" on the internet, a location that we provide to Sparkle either via code (like above) or in app's Info.plist, and determines whether the app version is up to date or if there is a newer version available. If there is a more recent version available, then Sparkle informs the user via an alert. In a moment we will see what this alert looks like.


Now that we know how Sparkle works, let's talk about that *some mystery location*. It is the location of our appcast file for our app and it has info on app's versions. Sparkle expects an XML file in that *some mystery location*. Using info in the XML file, Sparkle determines whether the user has the latest version or not.

You can [download](https://sparkle-project.org/files/sparkletestcast.xml) a sample appcast XML file from Sparkle and go through it.

It is **strongly recommended** that appcast XML is served via HTTPS URL. I will use [Amazon S3](https://aws.amazon.com/s3/) to host the appcast XML file for the purpose of this tutorial. You can choose from a plethora of options available online for e.g. Firebase Storage, your companies own server as long as it is HTTPS, Azure etc.

Following screenshot is from S3 location:
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/g77n61ln7499mnzj0c6a.png)
URL of my appcast XML file is:
```
https://s3.amazonaws.com/com.sparklesetupguide.tutorial/sparkletestcast.xml
```

This is the URL that Sparkle was expecting on clicking of the button earlier and on not finding it gave above [error message](#noFeedURLErrorMessage).

One of the many and simplest (in my opinion best way too) in which we can let Sparkle knows of appcast location is via app's Info.plist.

Upload the sample appcast XML file that you downloaded from Sparkle to your favorite online location and then add the following key-value pair in Info.plist

```
key - SUFeedURL
value - URL of XML file which you uploaded online
```

I have uploaded [Sparkle's sample XML](https://sparkle-project.org/files/sparkletestcast.xml) file to S3. My Info.plist looks like this with:
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/mzemqm29gonj3wg5tutq.png)

Go ahead build and run the app. Click on the "Check for Updates" button now. You see an alert that a new version of your app is available (similar to the screenshot below)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/dq47a6r9a4sanx1qjebw.png)

If you see an alert like above 👆
###then
![Bazinga](https://media.giphy.com/media/13Cage2A9C3UJi/giphy.gif)

Congrats. You have successfully integrated Sparkle in your app.

*PS: Any default configuration like appcast URL location should be configured in Info.plist instead of hard coding in code. I added below line*
```swift
updater?.feedURL = URL(string: "some mystery location")
```
*in the code to explain concept of appcast location. If you notice, even though we are not setting any valid URL `updater?.feedURL`, Sparkle is still able to get info on app version because Sparkle is getting it from Info.plist. Go ahead and delete that line and Sparkle will still work as long as appcast location is present in Info.plist*

>Pro tip: If you don't see an alert, then one of the first things you need to check and make sure is that your XML URL is public. This also goes for future debugging as well. For e.g. In Amazon S3, every time you upload a new version of the file (appcast XML), by default URL goes from being public to non-public and we have to make the file public again manually. Knowing this can save a lot of time later trying to debug why the app is unable to find the new version

Stay tuned for the next part, in which we will create our appcast XML and see some of the ways to customize Sparkle to enhance user experience. We will also go through a full cycle of releasing the first version, in which we will see how to correctly code sign app which has Sparkle framework so that it is accepted for Notarization, adding a new feature in next version and updating our current version with a newer version via Sparkle.

It's always a good idea to take a few mins break when working at a stretch. It helps with focus when we come back to the task at hand. If you want to take a few mins to break before your next task, then let me leave you with an interesting trivia

:bulb: *Ever wondered why US Government* :us: *is also referred to as Uncle Sam?* 🤔 *It's named after a meat packer Samuel Wilson. [Go ahead and take a 2 mins break to read on the history of Uncle Sam](https://www.history.com/this-day-in-history/united-states-nicknamed-uncle-sam)*