Common Flows
Submit feedback, surface the roadmap, build a custom UI on top of the async API, and handle errors. Covers RequestComposeView, RequestListView, programmatic submit / fetch / vote / comment, and FeddyError patterns.
Once the SDK is installed and configured, this page covers the four patterns most apps need: collecting feedback, displaying the roadmap, building custom UI on top of the async API, and handling errors.
Submit feedback
Default — RequestComposeView
The bundled feedback sheet is fully localized in 7 languages (en / zh-Hans / zh-Hant / es / ja / de / fr, auto-picked from the device locale) and handles validation, board picking, async submit, and the success state itself.
import SwiftUI
import Feddy
struct ProfileView: View {
@State private var showFeedback = false
var body: some View {
Button("Send Feedback") {
showFeedback = true
}
.sheet(isPresented: $showFeedback) {
RequestComposeView()
}
}
}By default the sheet shows the workspace's two system boards — Feature and Bug.
Custom workspace boards
Pass boards explicitly to surface ones you've added under
dashboard.feddy.app/w/<ws>/boards. You're responsible for the display
name's localization — the SDK does not know about your custom boards:
.sheet(isPresented: $showFeedback) {
RequestComposeView(boards: [
.featureRequest, // SDK-localized "Feature"
.bugReport, // SDK-localized "Bug"
.init(
key: "discussions", // matches dashboard board.key
name: NSLocalizedString("Discussions", comment: "")
),
])
}Programmatic submit
If you have your own UI, call Feddy.submitRequest(...) directly. Like
all writes, it's synchronous, fire-and-forget, and never throws.
Feddy.submitRequest(title: "Add dark mode")Feddy.submitRequest(
title: "Crash on launch",
description: "Happens after entering passcode on iPhone 15 Pro / iOS 17.4"
)Feddy.submitRequest(
title: "Confusing onboarding step 3",
boardKey: "ux-research"
)The two system boards every workspace ships with are "features" and
"bugs". Omit boardKey to land in the workspace's primary board.
Surface the roadmap
Default — RequestListView
Drop-in roadmap viewer with paginated list, board picker, pull-to-refresh, inline upvote, and tap-to-detail navigation.
.sheet(isPresented: $showRoadmap) {
RequestListView()
}Restricting boards
Restrict to specific boards if you want a subset:
RequestListView(boards: [
.featureRequest,
.init(
key: "discussions",
name: NSLocalizedString("Discussions", comment: "")
),
])The board picker only shows the boards you pass.
Custom UI — async fetch
When RequestListView doesn't fit (custom theming, embedded list, custom
empty state), build your own UI on top of the async read methods.
let page = try await Feddy.fetchRequests(boardKey: "features", limit: 20)
for item in page.items {
print(item.title, item.voteCount, item.attachments.count)
}
// Single request detail (with attachments + official reply)
let detail = try await Feddy.fetchRequest(id: "req_xyz")Vote and comment
All vote / comment methods are async throws.
Toggle a vote
The server is the source of truth — upvote returns the post-toggle
state so you can reconcile against an optimistic UI update:
let state = try await Feddy.upvote(requestId: "req_xyz")
print("voted=\(state.voted) total=\(state.voteCount)")Read comments
Oldest-first, paginated:
let thread = try await Feddy.fetchComments(requestId: "req_xyz", limit: 50)Append a comment
let posted = try await Feddy.addComment(
requestId: "req_xyz",
body: "Looking forward to this!"
)The returned posted is the new comment, so you can append to your local
thread without re-fetching.
Handle errors
Read methods throw FeddyError. Switch on the cases for typed handling:
public enum FeddyError: Error {
case network
case http(status: Int, code: String, message: String)
}| Case | When | What to do |
|---|---|---|
.network | DNS / TLS / offline / timeout | Show retry UI; the user's network is unhappy. |
.http(status:code:message:) | Server responded with 4xx / 5xx | Inspect code for typed handling. |
The code field is a stable string contract — values like
rate_limited, invalid_request, not_found. Match on it rather than
parsing message:
do {
let page = try await Feddy.fetchRequests(boardKey: "features")
// ...
} catch FeddyError.network {
showOfflineToast()
} catch FeddyError.http(_, let code, let message) {
switch code {
case "rate_limited":
scheduleRetry(after: .seconds(30))
case "invalid_request":
log.error("invalid request: \(message)")
default:
showGenericError(message)
}
}Offline behavior (writes)
Writes — submitRequest(...) — never throw. On network failure or 5xx
the payload is persisted to a local FIFO queue and replayed on the next
Feddy.configure(...) call. 4xx responses are dropped with a console log
(replaying bad payloads would loop forever).
The queue is bounded at 100 entries and survives app restarts. The host app does not need to manage it.
When to choose programmatic over the bundled views
| Use case | Pick |
|---|---|
| Roadmap fits a sheet with default theming | RequestListView |
| Embedded inside another screen / custom theme | Programmatic fetchRequests |
| Different empty state, hero, or onboarding | Programmatic |
| Showing votes / comments inline elsewhere | Programmatic |
| Minimal code, no design work | Bundled views |
Authentication
Identify signed-in users so feedback, votes, and comments attribute to a real account instead of a per-install anonymous token.
Overview
Start here to understand the Feddy React Native SDK surface area — what ships in the box, the optional Expo modules that add capability, and where each piece fits.