Make better apps in less time

Use the power of Swift and declarative code conventions to accelerate your app development. All you need to do is define Features and Actions in your code and let the magic happen. Less boilerplate. Fewer mistakes. Delightfully integrated.

import FlintCore

final class PhotoAttachmentsFeature: ConditionalFeature {
    static var description: String = "Attach a photo to a document"

    static func constraints(requirements: FeatureConstraintsBuilder) {
        requirements.iOSOnly = 10
        requirements.permissions(.photos, .camera)
        requirements.purchase(InAppPurchases.unlockAttachments)
    }

    static let showPhotoSelection = action(ShowPhotoSelectionAction.self)
    static let addSelectedPhoto = action(AddSelectedPhotoAction.self)

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

What does the framework do?

Once your code is using Features and Actions, Flint makes a bunch of platform integration and app infrastructure really easy; URL Mappings, Analytics, NSUserActivity for Handoff, Search and Siri Prediction, requesting system permissions, requiring in-app purchases and Siri Shortcuts are just some of the wonderful things you get for little effort. Often all you have to do is define a property on your Action type.

If that wasn’t enough, the coding patterns make your code cleaner, reduce coupling, and shift your thinking to a product-centric approach.

Define your Features

Features in Flint conform to the Feature protocol and follow some simple conventions:

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

    // Bind an action to this feature
    static let openDocument = action(DocumentOpenAction.self)

    // Declare the action bindings
    static func prepare(actions: FeatureActionsBuilder) {
        actions.declare(openDocument)
    }
 }

Wire-up the Actions

Actions are the things that users can do with your features such as “Open a document”, “Close a document” or “Share a document”:

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

    static var description = "Open a document"

    static func perform(context: ActionContext<DocumentRef>,
                        presenter: DocumentPresenter,
                        completion: Completion) -> Completion.Status {
        // Do the work
        presenter.openDocument(context.input)

        // Tell Flint we're done
        return completion.completedSync(.success)
    }
}

// Performing the action in your app looks like this...
DocumentManagementFeature.openDocument.perform(withInput: myDocument,
                                               presenter: self)

Flint observes when your code performs any of these high level tasks, and will automatically trigger logic based on the value of the convention properties, such as logging an analytics event or registering an NSUserActivity for the action so the user’s device can suggest the same action again in future.

Constraining when Features are available

Conditional Features support constraints, which can include specific platforms, OS versions, system permissions, in-app purchases and more:

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

class SelfieFeature: ConditionalFeature {
    static let description: String = "Selfie Posting"

    static func constraints(requirements: FeatureConstraintsBuilder) {

      // Require a purchase
      requirements.purchase(premiumSubscription)

      // All these permissions are required
      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(withPresenter: self)
} else {
    // Look at why it is not available, e.g. missing permissions (see docs)
}

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.

Next steps

Flint has a wealth of other capabilities, including a range of debug tools that allow you to browse all the features in your app, the current status of their constraints and even fake the status of in-app purchases, and even add bug report export functionality to your own apps that captures all the rich information the framework an gather to help you root cause problems quickly.

Head over to the Getting Started guide to learn how to install Flint into your app project and start adding features to your existing projects, or experiment with the FlintDemo iOS sample project.

Subscribe for Flint articles & news

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