rozd/user-kit
A provider-neutral “current user” layer for SwiftUI.
Installation
Add the package in Xcode (File ▸ Add Package Dependencies…) with:
https://github.com/rozd/user-kitOr in Package.swift:
.package(url: "https://github.com/rozd/user-kit", branch: "main"),You’ll usually also add a provider adapter, e.g. user-kit-firebase. The adapter is a separate package so apps that don’t use a given provider never pull in its SDK.
Quick start
1. Build one User from an adapter and inject it. Only this file imports the adapter; the rest of your app imports just UserKit.
import SwiftUI
import UserKit
import UserKitFirebase // your chosen adapter
extension User {
@MainActor static let current = User(
service: FirebaseUserService(configuration: .init(
authDomain: "auth.example.com",
bundleID: Bundle.main.bundleIdentifier!
)),
storage: FirebaseUserStorage(),
synchronizer: FirebaseUserSynchronizer()
)
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
RootView().environment(User.current)
}
}
}2. Read the current user anywhere.
import SwiftUI
import UserKit
struct ProfileView: View {
@Environment(User.self) private var user
var body: some View {
if user.isAuthenticated {
Text(user.info?.profile.displayName ?? "Signed in")
if user.isAdmin { AdminPanelLink() } // gate admin-only UI
Button("Sign out") { Task { try? await user.signOut() } }
} else {
Button("Sign in") { Task { try? await user.signIn() } }
}
}
}What you get
Everything below is on User:
| Member | What it does | | --- | --- | | info | Current user snapshot (id, session, profile, role), or nil when signed out. | | isAuthenticated | Whether there’s a valid session. | | isAdmin | Convenience for role == "admin". | | signIn() / signOut() | Sign in / out via the provider. | | authenticate() | Present the provider’s auth UI. | | withAuthentication { … } | Run work with a guaranteed-valid session (refreshes as needed). | | infos | An AsyncSequence of distinct user changes, for observing outside SwiftUI. |
Bring your own provider
An adapter is a small package that import UserKit and provides concrete conformers to six protocols:
- Behavior:
UserService(sign in/out, verify email, present auth UI),UserStorage(persist the last-known user),UserSynchronizer(auth state as anAsyncSequence). - Data:
UserInfo,UserSession,UserProfile.
Then the app builds User(service:storage:synchronizer:). See user-kit-firebase for a complete reference implementation, and Tests/UserKitTests/AdminRoleTests.swift for the smallest possible conformer set.
Requirements
- Swift 6.2+, iOS 18+, macOS 15+
- UserKit is built with Approachable Concurrency (
MainActordefault isolation +NonisolatedNonsendingByDefault). Enable the same on your app target so isolation lines up across the package boundary. - Android / Skip: UserKit is a plain cross-platform Swift package — Skip Fuse (native) apps can depend on it directly, with no Skip-specific setup on either side, and CI verifies the test suite on an Android emulator. Skip's transpiled (Lite) mode is not supported.
License
MIT © Max Rozdobudko
Package Metadata
Repository: rozd/user-kit
Default branch: main
README: README.md