A fresh way to build native apps for Apple platforms with Swift

Delighting users means interacting with lots of APIs these days. We have to carefully request permissions, handle in-app purchases, track usage analytics, and deep-link into our apps through URLs, notifications or Siri. With Flint you add a small amount of code to describe the features and actions that make up your app, and it will take care of the rest.

(It also makes debugging and testing easier)

Hero

Thinking differently.

Flint is an application framework written in Swift that helps you build apps out of Features and Actions using ideas from Feature Driven Development. It provides the plumbing behind the scenes to integrate with platform APIs. You continue use your preferred UI libraries and patterns — the framework operates at a lower level in your code to handle action dispatch and the various entry points to modern apps.

By adding information about actual features to apps you gain context about what is happening at a given point in time, as well as the ability to control access to features in a clean and safe way. Features in Flint conform to the Feature protocol and follow some simple conventions:

class DocumentManagementFeature: Feature {
    static let description = "Create, Open and Save documents"

    static let openDocument = action(DocumentOpenAction.self)

    static func prepare(actions: FeatureActionsBuilder) {
        actions.declare(openDocument)
    }
 }

Actions are high level tasks the user can perform with your app such as “Open a document”, “Close a document” or “Share a document”. In Flint these actions are types conforming to Action that are bound to features as in the above example. When performed, an Action receives a context (also containing the input) and a presenter. The type of the input and presenter are determined by you, using type aliases:

final class DocumentOpenAction: Action {
    typealias InputType = DocumentRef
    typealias PresenterType = DocumentPresenter

    static var description = "Open a document"

    static func perform(with context: ActionContext<DocumentRef>,
                        using presenter: DocumentPresenter,
                        completion: @escaping ((ActionPerformOutcome) -> ())) {
        presenter.openDocument(context.input)
        completion(.success(closeActionStack: false))
    }
}

Once you define actions, Flint can observe when your code performs any of these high level tasks. This unlocks many behaviours including automatic NSUserActivity support for Handoff, Spotlight and Siri Suggestions, analytics tracking and improved debug logging.

In addition, because Flint also knows how to invoke your actions for a given input, it can handle all the different app entry points for you too, including app or deep-linking URLs and continued activities including Handoff, Spotlight and Siri Suggestions. Read more in the Features & Actions guide.

What about features that require in-app purchases or certain system permissions? Conditional Features support such constraints, which can include specific platforms, OS versions, system permissions, in-app purchases and more. Thanks to Swift your code can’t perform actions of conditional features unless you also handle the case where the feature is not currently available.

let premiumSubscription = Product(name: "💎 Premium Subscription",
                                  description: "Unlock the Selfietron!",
                                  productID: "SUB0001")

/// The Selfie feature requires an in-app purchase and camera/photos and location permissions.
class SelfieFeature: ConditionalFeature {
    static var description: String = "Selfie Posting"

    static func constraints(requirements: FeatureConstraintsBuilder) {
      requirements.userToggled(defaultValue: true)
      requirements.runtimeEnabled()
      requirements.purchase(PurchaseRequirement(premiumSubscription))
      requirements.permissions(.camera,
                               .photos,
                               .location(usage: .whenInUse))
    }

    static let showSelfieCapture = action(ShowSelfieCaptureAction.self)

    ...
}

// In your view controller somewhere, check we can actually use Selfies
if let request = SelfieFeature.showSelfieCapture.request() {
    request.perform(presenter: self)
} else {
    // Look at why it is not available, e.g. missing permissions
}

Features that require multiple permissions or one of many purchase options are easily accommodated, and Flint will help you build a first class permissions onboarding UI to maximise the number of users that can use your feature without you having to worry about how to manage this yourself.

Find out more

Getting Started

Learn how to add the Flint framework to your apps

Features and Actions

Defining your features and performing actions on them

Conditional Features

Prevent use of features unless constraints are met — system permissions, OS versions, and in-app purchases

URL Routes

Map incoming app or deep-linking URLs to your actions

Activities

Let Flint automaticaly publish NSUserActivity instances for the actions you want to expose

Analytics

Use the framework to tell your analytics backend about the actions your users perform

Popular Articles

Didn't find an answer to your question?

Get help from the contributors and community in our Slack

Go to the Flint Slack