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
orfeedback
.@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.
Different logic is used to determine if an update is available depending on the current environment. updateAvailable
returns true
:
- DEBUG: Always.
- TestFlight: When a higher version OR build number is available.
- RELEASE: When a higher version is available.
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)
}
}
}
Legal Documents
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
}
}
}
}
}