Spinning Up a Feature Request Screen with Supabase
This post is brought to you by Emerge Tools, the best way to build on mobile.
It all started with this Hacker News comment:
As I near the finish line for the M.V.P. of my next app, I’m at the “10%” phase. The settings screen is the sole remaining, seemingly vast, chasm which lay between “developing” and “finished”, and as most of you can likely attest too - these things tend to drag on.
Among them? A feature request screen. Here were my rough requirements:
- I don’t want to just open up Mail, or another email client, and fire off an email.
- I want to store requests somewhere such that later, I can filter and mangle the data into another client app (i.e. an internal feedback tool).
- I want to own the data so that I could also display it inside the app later, if so desired.
- I wanted an incredibly, super super suuuuuper light dependency for cloud storage.
All told, the heart of the matter is this: I need a cloud based database, and an API to access it via iOS. You’d be surprised how barren the landscape is for something that, to me, seems to be an insanely common problem. As a UIKit, SwiftUI, Cocoa-lovin’ fool - I don’t want to write my own API to connect to some bespoke cloud database.
Which brings me back to that Hacker News comment, Supabase seemed perfect for the task. Turns out it works great, and the whole setup to API call was about five or ten minutes.
Here is the end result:
let concatenatedThoughts = """
The art of making an effective feedback form is also an interesting topic to me. There are so many different ways to ask a user about how things are going, and as many U.I. patterns to match. For me? I stole a trick we use at Buffer, and simply asked them what they were trying to do that they could not do. This gets to the heart of the matter, for me at least, as to what feature is missing.
"""
Supabase Setup
So, it turns out that Supabase has an unofficial, official client. It seems that’s the case with three of their five client libraries, with Flutter (what?) and Javascript being their official offerings:
That’s fine - I’m no stranger to open source work, I use a lot of it everyday. Yet, this is a service, so using a community driven solution had previously turned me away. No matter, with the Swift library being emblazoned with the ‘community’ badge on their official website, it seems likely they’re wisely capitalizing on an existing solution.
So long as they’ve given it an unambiguous, certified “If you want Supabase on platform X, then use client library Y” vote of confidence, I’m down to dance. And dance I did.
Supabase setup is a breeze. Simply create an account, and that’s it - there is no hand holding, tool tip laden forced entry through the door. The whole thing took me back to the sign up flows of yore, where I only provided an email - and I was rewarded with getting back to the problem I was trying to solve.
Post signup, I created a new table in the Postgres database you’re given out of the box. Using their WYSIWYG editor, I created a table just for feature requests. After setting up a few basic fields, I mirrored what data I needed in Swift:
import Foundation
typealias FeatureRequests = [FeatureRequest]
struct FeatureRequest: Codable {
var id: Int8?
var createdAt: Date?
var text: String
var email: String
enum CodingKeys: String, CodingKey {
case id, text, email
case createdAt = "created_at"
}
}
From there, the process of installing the Supabase client from Swift Package Manager had me up and going. In fact, this is the entirety of the code - all that’s left to do is remove the hardcoded API keys:
struct Outreach {
private static let url: String = "your_supabase_api_url"
private static let apiKey: String = "api_key"
private let featuresTable: String = "FeatureRequests"
private let client = SupabaseClient(supabaseURL: URL(string: Outreach.url)!, supabaseKey: Outreach.apiKey)
}
extension Outreach {
func sendFeatureRequest(_ text: String, email: String = "") async throws {
let req = FeatureRequest(text: text, email: email)
try await client.database
.from(featuresTable)
.insert(values: req)
.execute()
}
func getFeatureRequests() async throws -> FeatureRequests {
let features: FeatureRequests = try await client.database.from(featuresTable)
.execute().value
return features
}
}
And that’s it, from the SwiftUI view I simply call that with the feedback they’ve left. All landing in my own little cloud database that I am free to tweak any which way:
Perf - now I get the feedback, I can feed it into my custom feedback app, and I can later extend the data model. All of my boxes? Checked.
A Note on Row Level Security
Of course, you may notice the giant yellow “YOU’RE PLAYING WITH FIRE” banner.
That’s expected, as I couldn’t wrangle how to get inserts working with the Supabase client with any sort of RLS enabled. So, I guess, please don’t spam my feature requests. If anyone knows how to get this properly working, give me a shout. The friction I kinda hit was that I have no concept of a logged in user, and a lot of the RLS (as I see it) depends on some sort of notion of one.
No matter, I’m stoked with how easy all of this was. A lightweight library, a no-fuss database with an accessible API - that’s really all I wanted. I’ll be keeping on eye on what the folks at Supabase get up to, and it may become more core to my current project.
Until next time ✌️