The Making of Bloop – Converting some code to Flint Actions

Making of Bloop

One of a series of posts about our next app as we make it. It’ll include some details about our technology and design choices and challenges.

Bloop has been in the works on and off since 2017. Late in 2017 I switched my focus to building the Flint framework because I had an epiphany about how I think things should be built, and Bloop would need all this to be a great Apple platforms citizen. TL:DR; Flint lets you design and define the features of your app as small chunks of code, and does cool things mostly automatically like NSUserActivity, URL handling, Siri, Analytics, In-App Purchase feature gating etc.

Now we’re back on Bloop, I have to take this pre-Flint code and turn it into Features and Actions. This is a useful exercise to discuss here as it can help demystify usage of Flint.

My starting point was for the on-screen controls we have, and the app’s default behaviour which is to automatically start playing the last content you were watching.

In order to do this I had to break this down into conceptual features and actions, and then they can be represented in code. This brings clarity to what your app is and represents to users. We’ll just look at the player part of it as an example.

So we have a player feature that lets users play content. Right now, it can show the player UI, and it can be told to play the last content it was playing, so those are two actions: “show” and “play the previous content”.

In code this becomes:

public class PlayerFeature: Feature {
    public static var description: String = "Player"

    public static let show = action(ShowPlayerAction.self)
    public static let playPreviousContent = action(PlayPreviousContentAction.self)

    public static func prepare(actions: FeatureActionsBuilder) {

The action types mentioned there deal with the internal plumbing of how we show and play the content, calling into other dependencies in the app to do the actual work using a presenter passed in when performing the action.

If we look at the PlayPreviousContentAction, we see something cool:

public final class PlayPreviousContentAction: FlintUIAction {
    public typealias InputType = NoInput
    public typealias PresenterType = UIViewController

    public static let activityEligibility: Set<ActivityEligibility> = [.handoff, .prediction]
    public static func prepareActivity(_ activity: ActivityBuilder<PlayPreviousContentAction>) {
        activity.title = "Play the previous content"

I’ve not included the details of the perform implementation of that action. However with the above coded and wired up to load the previous content and play it, and the AppDelegate set up to use these actions to start the player when the app launches, we can run the app and we get:

  • Free logging of when the player is shown or previous content played (including Flint’s timeline/activities debugging tools)
  • Siri prediction for the playback of previous content.

This latter part is great. Though I’m cheating a bit because without implementing anything else new the app starts and shows previous content anyway — Siri already starts suggesting I run Bloop to show the previous content when I’m at work. Flint deals with all of this because of the activityEligibility setting in the action. The app has no explicit code to register activities.

Screenshot of Siri suggestion Bloop activity

It already feels like we’re getting things for free with Flint. I just need to hook up the App delegate activity continuation code to call into Flint and we’ll have something that can properly handle this when there are multiple actions or URLs that could launch the app.

The Author

Marc Palmer (Twitter, Mastodon) is a consultant and software engineer specialising in Apple platforms. He currently works on the iOS team of Concepts sketching app, as well as his own apps like video subtitle app Captionista. He created the Flint open source framework. He can also do a pretty good job of designing app products. Don't ask him to draw anything, because that's really embarrassing. You can find out more here.