JchuComponents cover

JchuComponents

JchuComponents is a collection of Kotlin Multiplatform utilities, Android Jetpack Compose components and native SwiftUI components for building Android and Apple applications with less repeated infrastructure.

JchuComponents 3 is under active development on the v3 branch. Pre-release versions follow 3.0.0-alphaNN, 3.0.0-betaNN and 3.0.0-rcNN before the stable 3.0.0 release.

Apple targets are ARM64 only: iosArm64, iosSimulatorArm64 and macosArm64. Intel x64 targets are not supported.

Modules and platforms

Module Android iOS macOS Purpose
jchucomponents-foundation ✅ ✅ ✅ Portable state, dates, text and utilities
jchucomponents-network ✅ ✅ ✅ Ktor client configuration, resources and flow helpers
jchucomponents-prefs ✅ ✅ ✅ Multiplatform DataStore preferences
jchucomponents-pay ✅ ✅ ✅ Shared payment models and Android RevenueCat integration
jchucomponents-pay-kmp ✅ ✅ — Shared RevenueCat purchases for Android and iOS (iOS ARM targets)
jchucomponents-qr ✅ ✅ ✅ QR encoding and decoding utilities
jchucomponents-room ✅ ✅ — Room 3 runtime configuration and SQL migrations
jchucomponents-supabase ✅ ✅ ✅ Supabase Auth, database, realtime, Edge Functions and Koin setup
jchucomponents-core ✅ ✅ ✅ Android architecture and lifecycle utilities with portable core APIs
jchucomponents-ktx ✅ ✅ ✅ Android APIs and portable Kotlin extension compatibility helpers
jchucomponents-ui-kmp ✅ ✅ — Portable Compose components without Android resource dependencies (iOS ARM targets)
jchucomponents-ui ✅ — — Android adapter for resource- and platform-dependent components; re-exports jchucomponents-ui-kmp
jchucomponents-pdf ✅ — — Remote PDF reader with generated Navigation 3 routes
jchucomponents-pdf-kmp ✅ ✅ — Compose Multiplatform PDF reader with Android PdfRenderer and iOS PDFKit (iOS ARM targets)
jchucomponents-navigation3 ✅ ✅ — Compose Multiplatform Navigation 3 annotations, helpers and KSP processor (iOS ARM targets)
jchucomponents-navigation3-di ✅ ✅ — Dependency injection helpers for generated Navigation 3 entries on JVM and iOS ARM targets
jchucomponents-app-updates ✅ — — Google Play update StateFlow and lifecycle controller; apps own the UI
jchucomponents-bom ✅ — — Aligned Android module versions

The Swift package complements the shared Kotlin code with JchuComponentsCore, JchuComponentsExtensions, JchuComponentsSwiftUI, JchuComponentsNavigation and the optional native JchuComponentsPay product. JchuComponentsSwiftUI also includes JchuPdfReaderView for remote PDFs on iOS.

Installation

The examples below use 3.0.0-alpha25. Replace it with the latest available release.

Kotlin Multiplatform

KMP artifacts are published to Maven Central. Add only the modules needed by your shared source set:

kotlin {
sourceSets {
commonMain.dependencies {
implementation(
"io.github.jeluchu:jchucomponents-foundation:3.0.0-alpha25"
)
implementation(
"io.github.jeluchu:jchucomponents-network:3.0.0-alpha25"
)
implementation(
"io.github.jeluchu:jchucomponents-ui-kmp:3.0.0-alpha25"
)
}
}
}

The available Maven Central artifacts are:

io.github.jeluchu:jchucomponents-foundation
io.github.jeluchu:jchucomponents-core
io.github.jeluchu:jchucomponents-ktx
io.github.jeluchu:jchucomponents-network
io.github.jeluchu:jchucomponents-ui-kmp
io.github.jeluchu:jchucomponents-pdf-kmp
io.github.jeluchu:jchucomponents-pay
io.github.jeluchu:jchucomponents-pay-kmp
io.github.jeluchu:jchucomponents-prefs
io.github.jeluchu:jchucomponents-qr
io.github.jeluchu:jchucomponents-room
io.github.jeluchu:jchucomponents-supabase
io.github.jeluchu:jchucomponents-navigation3
io.github.jeluchu:jchucomponents-navigation3-di
io.github.jeluchu:jchucomponents-bom
io.github.jeluchu:jchucomponents-app-updates

jchucomponents-ktx keeps its Android extensions in androidMain; its shared API provides the existing boolean, list, and serialization package names plus a portable Job helper, backed by jchucomponents-foundation where an equivalent already exists. Dispatcher-based coroutine helpers remain in androidMain.

jchucomponents-pay keeps its existing Android RevenueCat API and shared payment models. For purchases from common Kotlin on Android and iOS, use jchucomponents-pay-kmp; it wraps RevenueCat's official Kotlin Multiplatform SDK and publishes Android, iosArm64, and iosSimulatorArm64 variants. Its RevenueCat SDK dependency is limited to Android and iOS, so this artifact does not currently target macOS.

Most listed artifacts contain Android, iOS ARM64 and macOS ARM64 variants. The exceptions are jchucomponents-room, jchucomponents-navigation3, jchucomponents-navigation3-di, jchucomponents-pdf-kmp, jchucomponents-pay-kmp, and jchucomponents-ui-kmp, which currently contain Android, iosArm64, and iosSimulatorArm64; and jchucomponents-app-updates, which is Android-only and intended for apps distributed through Google Play. Consumers provide their own update interface. JitPack's Linux builds do not produce the Apple KLIB variants, so KMP applications targeting iOS or macOS should use Maven Central.

jchucomponents-foundation also provides JchuLocaleProvider for reading the system language from shared code. getPlatformLanguageCode() maps Spanish in the United States to es-US and returns the base language code for other regional locales; getLanguageTag() exposes the full BCP 47 tag where the platform provides one.

jchucomponents-ui-kmp contains the UI components that compile for Android, iosArm64, and iosSimulatorArm64. The Android jchucomponents-ui artifact keeps the existing Android API and adds the platform-bound parts that use R, Context, View, Bitmap, Android resource APIs or Android Coil requests. KMP applications should depend on jchucomponents-ui-kmp directly; Android applications can continue using jchucomponents-ui as before.

Android

Add JitPack to settings.gradle.kts:

dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven("https://jitpack.io")
}
}

Use the BOM to keep Android modules on the same version:

dependencies {
implementation(
platform("io.github.jeluchu:jchucomponents-bom:3.0.0-alpha25")
)
implementation("com.github.jeluchu.jchucomponents:jchucomponents-ui")
implementation("io.github.jeluchu:jchucomponents-ktx")
}

Alternatively, specify each JitPack artifact version directly:

dependencies {
implementation(
"com.github.jeluchu.jchucomponents:jchucomponents-ui:3.0.0-alpha25"
)
implementation(
"com.github.jeluchu.jchucomponents:jchucomponents-pdf:3.0.0-alpha25"
)
}

The jchucomponents-pdf artifact provides an Android Compose screen for remote PDF URLs and is available through JitPack starting with 3.0.0-alpha23. The library generates its typed PdfReaderRoutes.Reader(url) route and pdfReaderEntries(backStack) provider, so apps register the provider in their Navigation 3 entry provider and navigate to the generated route. The artifact is Android-only and is distributed through JitPack.

For Compose Multiplatform apps, use the Maven Central artifact io.github.jeluchu:jchucomponents-pdf-kmp. It provides the same reader route on Android, iosArm64, and iosSimulatorArm64, using PdfRenderer/FileProvider on Android and PDFKit/native sharing on iOS. Add it to commonMain and register pdfReaderKmpEntries(backStack); then navigate with PdfReaderKmpRoutes.Reader(url = remotePdfUrl). The Android-only artifact remains available unchanged.

Multiplatform payments

For a shared RevenueCat integration on Android and iOS, add the KMP artifact to commonMain:

kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.jeluchu:jchucomponents-pay-kmp:3.0.0-alpha25")
}
}
}

Initialize it once with the API key for the current store, then load the active offering and make purchases from shared Kotlin:

import com.jeluchu.pay.revenuecat.kmp.Payment
import com.jeluchu.pay.revenuecat.kmp.initPayment

initPayment(
apiKey = revenueCatApiKey,
isDebug = isDebug,
subscriptionName = "premium"
)

val billingInfo = Payment.loadBillingInfo()
val purchaseResult = Payment.purchaseResult(Payment.ProductsType.MONTHLY)

The artifact uses RevenueCat's Kotlin Multiplatform SDK 3.6.0 and currently targets Android, iosArm64, and iosSimulatorArm64. macOS is not included because the upstream KMP SDK does not currently publish a macOS target. See the jchucomponents-pay-kmp/README.md for callbacks, restore, subscription status, and plan switching.

Swift Package Manager

Add the following package URL in Xcode:

https://github.com/Jeluchu/jchucomponents-spm

Select the JchuComponents product and import the modules needed by the application:

import JchuComponentsCore
import JchuComponentsExtensions
import JchuComponentsSwiftUI

For the native RevenueCat integration, also select and import the optional product:

import JchuComponentsPay

The Swift package currently requires iOS 26.0 or newer. Releases live in the dedicated jchucomponents-spm repository so consumers do not clone the full Kotlin and Android monorepo.

Network quick start

Create a platform-specific Ktor engine from shared Kotlin:

val client = createHttpClient(
HttpClientConfiguration(
baseUrl = "https://example.com/api/",
enableLogging = isDebug,
defaultHeaders = mapOf("X-Client" to "my-app"),
)
)

createHttpClient uses Ktor's Android engine on Android and Darwin engine on iOS and macOS. HttpClientConfiguration controls JSON, caching, timeouts, response validation, logging and sensitive headers.

The v3 network result models and flow helpers belong to jchucomponents-network:

import com.jeluchu.jchucomponents.network.extensions.flow.flowCollector
import com.jeluchu.jchucomponents.network.extensions.flow.flowResourceCollector
import com.jeluchu.jchucomponents.network.extensions.handleFailure
import com.jeluchu.jchucomponents.network.models.Failure
import com.jeluchu.jchucomponents.network.models.Resource

Their previous jchucomponents-core packages were removed during the v3 alpha cycle.

Room quick start

Apply the shared runtime defaults to a Room 3 builder while retaining access to all application-specific configuration:

val database = Room.databaseBuilder<AppDatabase>(context, "app.db")
.configureJchuRoom {
addMigrations(
sqlMigration(
startVersion = 1,
endVersion = 2,
"ALTER TABLE task ADD COLUMN archived INTEGER NOT NULL DEFAULT 0"
)
)
}
.build()

The consuming module must apply KSP and add the androidx.room3:room3-compiler dependency itself. The runtime module does not enable destructive migrations or choose a database path.

Navigation 3 code generation

jchucomponents-navigation3 provides Navigation 3 annotations and back-stack helpers in commonMain. Its KSP processor can generate typed route keys and entry providers from shared @Screen composables for Android and iOS ARM targets (iosArm64 and iosSimulatorArm64). Configure KSP with kspCommonMainMetadata in the module containing those screens. The host still supplies its Navigation 3 UI dependency and root NavDisplay.

The optional jchucomponents-navigation3-di artifact provides Koin inject()/injectByParams() helpers on Android, JVM, iosArm64 and iosSimulatorArm64. It uses Koin's multiplatform Compose ViewModel APIs and shares the runtime module's target restrictions: Navigation 3 1.2.0 has no iosX64 or macOS variants. The runtime module is independent of jchucomponents-ui. The processor groups screens by graph, uses each composable name as its default route, and wires typed arguments, back callbacks, cross-graph navigation and app-owned custom actions. See the jchucomponents-navigation3/README.md for KMP and Android setup, and the Navigation code generation example in the Android catalog.

The Swift package also provides JchuComponentsNavigation, a SwiftUI @Screen macro and graph plugin. Annotated screens generate a typed route enum per graph, including associated arguments and a switch to each destination view. For KMP ViewModels, @ScreenKoinViewModel can infer the factory from the property type; for example, UserViewModel uses KoinWrapper.userForScreen():

@Screen(graph: "Wallet")
struct WalletScreen: View {
@ScreenKoinViewModel
@StateViewModel private var viewModel: UserViewModel

var body: some View { WalletContent(viewModel: viewModel) }
}

Factories follow <lowerCamel ViewModel name without ViewModel>ForScreen; the macro forwards @RouteArgument values by name. Use arguments: [] when the ViewModel needs no route values, or pass factory: explicitly for a custom factory name. The iOS catalog registers generated routes on its root NavigationStack.

Supabase quick start

Register the shared Supabase client from the same Koin graph used by the app repositories:

val supabaseModule = jchuSupabaseModule(
JchuSupabaseConfig(
url = BuildKonfig.SUPABASE_URL,
publishableKey = BuildKonfig.SUPABASE_PUBLISHABLE_KEY,
auth = JchuSupabaseAuthConfig(
flowType = JchuSupabaseAuthFlowType.PKCE,
alwaysAutoRefresh = true,
autoLoadFromStorage = true,
autoSaveToStorage = true,
defaultRedirectUrl = "com.example.app://auth-callback",
scheme = "com.example.app",
host = "auth-callback"
)
)
)

Repositories can depend on JchuSupabaseDatabase, JchuSupabaseAuth, JchuSupabaseFunctions or JchuSupabaseRealtime. Database and Function operations return Flow<Resource<Failure, T>>; Auth returns a typed JchuSupabaseAuthFailure so login screens can distinguish invalid credentials, unconfirmed email, weak password, rate limiting, session expiry and network failures.

Registration reports whether Supabase created a session or is waiting for the email confirmation link:

auth.signUpWithEmail(
email = email,
password = password,
redirectUrl = "com.example.app://auth-callback",
metadata = buildJsonObject {
put("display_name", displayName)
put("requested_account_type", "shop")
}
).collect { resource ->
if (resource is Resource.Success) {
val result = resource.data ?: return@collect
if (result.requiresEmailConfirmation) {
showCheckYourEmail()
} else {
openAuthenticatedArea()
}
}
}

Forward the Android or Apple deep-link URL to auth.handleAuthCallback(url). Observe auth.sessionState during app startup; it differentiates initialization, restored/authenticated sessions, sign-out and refresh failures. Password recovery, confirmation resend, password update, manual refresh, local session clearing and local/global sign-out are also available on JchuSupabaseAuth.

The default Supabase session and PKCE caches are used unless custom SessionManager and CodeVerifierCache implementations are supplied. Inject platform-secure implementations when the application requires explicit token encryption or retention policies. Never use user_metadata as an authorization source: roles, shop ownership and administrator approval must be enforced by database data, RLS and server-side functions.

PostgREST RPCs are available through database.rpc(...). Edge Functions accept idempotency and correlation identifiers without allowing callers to override the Supabase authentication headers:

functions.invoke<AwardPointsRequest, AwardPointsResponse>(
name = "award-points",
body = request,
options = JchuSupabaseFunctionOptions(
idempotencyKey = request.operationId,
correlationId = request.correlationId
)
)

Basic table access remains available for ordinary RLS-protected data:

@Serializable
data class Profile(
val id: String,
val name: String
)

object ProfileTable : JchuSupabaseTable<Profile> {
override val name: String = "profiles"
}

class ProfileRepository(
database: JchuSupabaseDatabase
) {
private val profiles = database.table(ProfileTable)

fun profile(id: String): Flow<Resource<Failure, Profile>> =
profiles.findById(id)

fun updateProfile(profile: Profile): Flow<Resource<Failure, Profile>> =
profiles.updateById(
id = profile.id,
value = profile
)
}

The first Supabase module intentionally excludes Storage. It focuses on Auth, PostgREST database access, Realtime wiring, Edge Functions, Koin and resource flow helpers for Android, iOS and macOS KMP apps.

API stability

  • Alpha releases may introduce source and binary-breaking changes.

  • Beta releases stabilize the public API; breaking changes should be rare and documented.

  • Release candidates accept fixes and documentation changes unless a critical API issue is found.

  • Stable releases follow semantic versioning.

  • Public Kotlin API changes are checked by the binary compatibility validator.

  • Removed or renamed APIs are recorded in CHANGELOG.md.

Experimental APIs should be explicitly marked with @RequiresOptIn. Once an API becomes stable, incompatible changes require a deprecation cycle whenever practical.

Development

The repository includes an Android catalog in app and an iOS catalog in samples/iosApp.

Run the repository-wide Android and Kotlin checks:

./gradlew ciCheck

This validates binary APIs, tests, debug and release assemblies, and all local Maven publications. See the docs/getting-started.md for focused setup and examples for every module.

Inspect Kotlin formatting without modifying source files:

./gradlew ktlintCheck

HTML and plain-text reports are generated under each module's build/reports/ktlint directory. The check is currently advisory and does not fail CI. Apply safe automatic fixes to one module at a time:

./gradlew :jchucomponents-foundation:ktlintFormat

Review the resulting diff before continuing with another module because some rules, such as file naming, require a manual decision.

Build the Android catalog:

./gradlew :app:assembleDebug

Verify the artifacts as an external KMP consumer after publishing them locally:

./gradlew \
:jchucomponents-foundation:publishToMavenLocal \
:jchucomponents-network:publishToMavenLocal \
:jchucomponents-pay:publishToMavenLocal \
:jchucomponents-prefs:publishToMavenLocal \
:jchucomponents-qr:publishToMavenLocal \
:jchucomponents-room:publishToMavenLocal \
:jchucomponents-supabase:publishToMavenLocal

./gradlew \
-p smoke-tests/kmp-consumer \
compileAndroidMain \
compileKotlinIosSimulatorArm64 \
compileKotlinMacosArm64

The smoke project reserves io.github.jeluchu for Maven Local, ensuring the check cannot silently consume an already published release from Maven Central.

Build the Swift package after assembling the XCFramework:

./gradlew \
:jchucomponents-foundation:assembleJchuComponentsCoreReleaseXCFramework

xcodebuild \
-scheme JchuComponents-Package \
-destination 'generic/platform=iOS Simulator' \
build

For contributions:

  1. Open a bug report or feature request.

  2. Discuss the proposed public API for larger changes.

  3. Add implementation, tests and documentation.

  4. Open a pull request.

Synchronizing localizations

scripts/sync_strings.py synchronizes Android strings.xml resources into an Apple Localizable.xcstrings catalog. It adds new keys and locales, updates translations, converts placeholders such as %1$s to %1$@ and ignores non-translatable strings.

Register it once as a Gradle task in the Android application module:

tasks.register<Exec>("syncIosStrings") {
group = "localization"
description = "Syncs Android strings into the iOS String Catalog"
commandLine(
"python3",
rootProject.file("scripts/sync_strings.py"),
"--xcstrings",
rootProject.file(
"iosApp/iosApp/resources/strings/Localizable.xcstrings"
),
"--android-res",
project.file("src/main/res"),
"--delete-missing",
)
}

From then on, synchronization only requires running the Gradle task:

./gradlew :androidApp:syncIosStrings

It can also be saved as an Android Studio Gradle Run Configuration:

  • Name: Sync localizables

  • Run: :androidApp:syncIosStrings

  • Gradle project: the root KMP project

This exposes synchronization as a one-click action from the IDE. Enable Store as project file if the run configuration should be shared with the rest of the team.

Remove --delete-missing when Apple-only keys must be preserved. Add "--dry-run" to commandLine when the task should only preview changes.

Release process

Tagged 3.* versions trigger the release workflow. It validates the Gradle version, API compatibility, tests, Android publications and the Swift package; then it publishes the signed Android and KMP artifacts, including Navigation 3, Play Store updates, and the BOM to Maven Central and prepares the dedicated SwiftPM repository. JitPack builds the same Git tag on demand when a consumer requests that version; it does not need a separate manual upload.

Before tagging, update jchucomponents in gradle/libs.versions.toml and make sure CI passes on v3:

git tag -a 3.0.0-alpha11 -m "3.0.0-alpha11"
git push origin v3
git push origin 3.0.0-alpha11

SwiftPM publication additionally requires the repository variable SPM_REPOSITORY_ENABLED=true and a SPM_REPOSITORY_TOKEN secret with write access to Jeluchu/jchucomponents-spm.

License

JchuComponents is available under the LICENSE.

All modules:

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard