Usage

Once you have Parra installed and configured, you'll want to start incorporating its individual products into your app. The APIs for these products can be accessed via Environment Values from anywhere in your app. Below is a description of the available Environment Values as well as examples of how to use them.

Environment Values

Since Parra is designed to work with SwiftUI, its state and APIs can be easily accessed from Environment Values. The following values are provided, which you can access from your Views.

  • The Parra object: Used for logging events and accessing instances of Parra's submodules, like releases, auth or feedback.
    @Environment(\.parra) var parra
    
  • App Info: Contains information specific to the application that has been configured in the dashboard. This includes information about your tenant, releases, legal documents and more.
    @Environment(\.parraAppInfo) var parraAppInfo
    
  • Authentication State: An enum representing the login state of the current user. To see what states are available, check out the accessing identities doc.
    @Environment(\.parraAuthState) var parraAuthState
    
  • Theme: The current theme being used to render the application. For more information on available fields, check out the themes doc.
    @Environment(\.parraTheme) var parraTheme
    

Feedback Forms

Feedback forms allow you to collect feedback information directly from your users. To add a form to your app, first create one in the Parra dashboard and copy its unique identifier. Then use one of the available presentParraFeedbackForm View modifiers in SwiftUI to present the form modally.

The simplest version only requires a Binding indicating whether the form should be presented. When it becomes true, we'll handle loading the form data and presenting the form sheet automatically.

Presenting a feedback form

import Parra

struct FeedbackButton: View {
    @State private var isFormPresented = false

    var body: some View {
        Button("Leave feedback") {
            isFormPresented = true
        }
        .presentParraFeedbackForm(
            by: "my-feedback-form-id",
            isPresented: $isFormPresented
        )
    }
}

If you prefer, you can also use the fetchFeedbackForm function from Parra's feedback module to load the form data before passing it directly to the presentParraFeedbackForm modifier. This allows you to preload the form data, show a custom loading UI or even transform the form data before presenting the sheet.

Presenting a feedback form with a custom loading UI

import Parra

struct FeedbackButton: View {
    @Environment(\.parra) private var parra
    @State private var formData: ParraFeedbackForm?
    @State private var isLoading = false

    var body: some View {
        Button {
            Task {
                await loadFeedbackForm(with: "my-feedback-form-id")
            }
        } label: {
            Label(
                title: { Text("Leave Feedback") },
                icon: {
                    if isLoading {
                        ProgressView()
                    }
                }
            )
        }
        .disabled(isLoading)
        .presentParraFeedbackForm(with: $formData)
    }

    private func loadFeedbackForm(
        with formId: String
    ) async {
        isLoading = true
        defer { isLoading = false }

        do {
            formData = try await parra.feedback.fetchFeedbackForm(
                formId: formId
            )
        } catch {
            formData = nil

            ParraLogger.error(error)
        }
    }
}

Changelog

With changelogs, users can view a list of your app's past releases. They can drill into this list to view the changes that occurred in each release. Changelogs can be presented in a sheet using the presentParraChangelog modifier, which fetches the latest changelog before presentation.

Presenting a changelog

import Parra

struct ChangelogButton: View {
    @State private var isChangelogPresented = false

    var body: some View {
        Button("Present Changelog") {
            isChangelogPresented = true
        }
        .presentParraChangelog(isPresented: $isChangelogPresented)
    }
}

If you prefer, you can also use the fetchChangelog function from Parra's releases module to load the changelog data before passing it directly to the presentParraChangelog modifier. This allows you to preload the changelog, show a custom loading UI or even transform the changelog's data before presenting the sheet.

Presenting a changelog with a custom loading UI

import Parra

struct ChangelogButton: View {
    @Environment(\.parra) private var parra
    @State private var changelogInfo: ParraChangelogInfo?
    @State private var isLoading = false

    var body: some View {
        Button {
            Task {
                await loadChangelog()
            }
        } label: {
            Label(
                title: { Text("Changelog") },
                icon: {
                    if isLoading {
                        ProgressView()
                    }
                }
            )
        }
        .disabled(isLoading)
        .presentParraChangelog(with: $changelogInfo)
    }

    private func loadChangelog() async {
        isLoading = true
        defer { isLoading = false }

        do {
            changelogInfo = try await parra.releases.fetchChangelog()
        } catch {
            changelogInfo = nil

            ParraLogger.error(error)
        }
    }
}

Displaying a Release

The latest release API is useful in cases where you want to build a "What's new" UI without using Parra's automatic What's new modal, or if you'd like to display information about a previous release, possibly obtained from the changelog response object. If you want to let us handle it, learn how to configure your what's new settings.

Checking if an update is available

When your app launches, Parra will automatically check if there is a new release available. You can use the updateAvailable function from the releases module to obtain this information.

Displaying the latest release

The presentParraRelease can be used to present a sheet for a specific release. You can obtain the release using the fetchLatestRelease function from Parra's releases module.

Displaying the latest release in a sheet if available

import Parra

struct LatestReleaseButton: View {
    @Environment(\.parra) private var parra

    @State private var isLoading = false
    @State private var appVersionInfo: ParraNewInstalledVersionInfo?

    var body: some View {
        if parra.releases.updateAvailable() {
            Button {
                Task {
                    await loadLatestRelease()
                }
            } label: {
                Label(
                    title: {
                        Text("Update Available")
                            .foregroundStyle(Color.primary)
                    },
                    icon: {
                        if isLoading {
                            ProgressView()
                        }
                    }
                )
            }
            .disabled(isLoading)
            .presentParraRelease(with: $appVersionInfo)
        }
    }

    private func loadLatestRelease() async {
        isLoading = true
        defer { isLoading = false }

        do {
            if let appVersionInfo = try await parra.releases.fetchLatestRelease() {
                self.appVersionInfo = appVersionInfo
            } else {
                ParraLogger.info("No new release is available")
            }
        } catch {
            ParraLogger.error("Error fetching latest release", error)
        }
    }
}

Roadmap

Displaying a roadmap works similarly to other Parra sheets. You can use the presentParraRoadmap modifier with a isPresented binding indicating when the sheet should be shown. When using this variant, we'll handle fetching the roadmap before displaying it.

Presenting a roadmap

import Parra

struct RoadmapButton: View {
    @State private var isRoadmapPresented = false

    var body: some View {
        Button("View roadmap") {
            isRoadmapPresented = true
        }
        .presentParraRoadmap(isPresented: $isRoadmapPresented)
    }
}

If you prefer, you can also use the fetchRoadmap function from Parra's releases module to load the roadmap data before passing it directly to the presentParraRoadmap modifier. This allows you to preload the roadmap, show a custom loading UI or even transform the roadmap's data before presenting the sheet.

Presenting a roadmap with a custom loading UI

import Parra

struct RoadmapButton: View {
    @Environment(\.parra) private var parra

    @State private var isLoading = false
    @State private var roadmapInfo: ParraRoadmapInfo?

    var body: some View {
        Button {
            Task {
                await loadRoadmap()
            }
        } label: {
            Label(
                title: { Text("Roadmap") },
                icon: {
                    if isLoading {
                        ProgressView()
                    }
                }
            )
        }
        .disabled(isLoading)
        .presentParraRoadmap(with: $roadmapInfo)
    }

    private func loadRoadmap() async {
        isLoading = true
        defer { isLoading = false }

        do {
            roadmapInfo = try await parra.releases.fetchRoadmap()
        } catch {
            roadmapInfo = nil

            ParraLogger.error(error)
        }
    }
}

When you host legal documents in the Parra dashboard, they are accessible in the ParraAppInfo object under the legal key. You can use the contents of these documents to render them in your app as you see fit. You can also use the ParraLegalDocumentView to handle rendering them automatically. Below is an example of how you can create a list of all your legal documents.

import Parra

struct MyLegalDocuments: View {
    @Environment(\.parraAppInfo) private var parraAppInfo

    var documents: some View {
        ForEach(parraAppInfo.legal.allDocuments) { document in
            NavigationLink {
                ParraLegalDocumentView(legalDocument: document)
            } label: {
                Text(document.title)
            }
            .id(document.id)
        }
    }

    var body: some View {
        NavigationStack {
            List {
                if parraAppInfo.legal.hasDocuments {
                    documents
                }
            }
        }
    }
}

Was this page helpful?