From 5323e0abf7f325207f0e49eab7dc80fedbdd8677 Mon Sep 17 00:00:00 2001 From: Reza Taghizadeh Date: Mon, 17 Aug 2026 22:32:52 +0200 Subject: [PATCH] release: prepare Kotlin 1.0 sample and package publishing --- .github/workflows/release.yml | 29 ++++++ CHANGELOG.md | 7 +- README.md | 93 +++++++++++++++++-- flextrack/build.gradle.kts | 16 +++- .../dev/taghizadeh/flextrack/FlexTrack.kt | 4 +- .../dev/taghizadeh/flextrack/FlexTrackTest.kt | 2 +- gradle/libs.versions.toml | 1 + sample/build.gradle.kts | 3 + sample/src/main/AndroidManifest.xml | 13 ++- .../flextrack/sample/MainActivity.kt | 89 ++++++++++++++++++ sample/src/main/res/values/strings.xml | 5 +- 11 files changed, 246 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 sample/src/main/kotlin/dev/taghizadeh/flextrack/sample/MainActivity.kt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..03e72dc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,29 @@ +name: Release + +on: + push: + tags: ["v*"] + +permissions: + contents: read + packages: write + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + cache: gradle + - uses: gradle/actions/setup-gradle@v4 + - name: Verify tag matches SDK version + run: test "${GITHUB_REF_NAME#v}" = "$(sed -n 's/version = "\(.*\)"/\1/p' flextrack/build.gradle.kts | head -1)" + - name: Verify release + run: ./gradlew clean :flextrack:test :flextrack:lint :flextrack:assembleRelease + - name: Publish to GitHub Packages + run: ./gradlew :flextrack:publishReleasePublicationToGitHubPackagesRepository + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index d35f518..90d365f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.0.0 - 2026-08-17 ### Added @@ -8,3 +8,8 @@ - Maven publication metadata and local publishing support. - FlexTrack Core Specification 1.0.0 contract baseline. - CI quality gates for build, unit tests, lint, and publication verification. +- Immutable events, enrichment, consent-aware routing, and deterministic sampling. +- Coroutine-based tracker runtime with isolated delivery failures. +- In-memory and durable atomic-file offline queues with selective retry. +- Shared Flutter/Kotlin conformance test runner. +- Runnable Android sample and GitHub Packages release workflow. diff --git a/README.md b/README.md index 24e5eec..ff01096 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,84 @@ The Kotlin SDK targets Android API 21+ and implements [FlexTrack Core Specification 1.0.0](contract/README.md), shared with [FlexTrack Flutter 2.1.0](https://pub.dev/packages/flex_track). -## Project status +## Features -The SDK is under active development toward Kotlin 1.0.0. The current version is -`0.1.0-SNAPSHOT` and is not ready for production use. +- Immutable events with stable IDs and UTC timestamps. +- Priority routing, tracker groups, consent gates, and deterministic sampling. +- Ordered enrichment with transformer failure isolation. +- Coroutine-native tracker lifecycle and concurrent delivery. +- Per-tracker failure isolation and selective retry. +- Durable, atomic offline queue for Android plus an in-memory test queue. +- Shared conformance fixtures verified against the Flutter implementation. + +## Installation + +The release workflow publishes version `1.0.0` through GitHub Packages. After +the release tag is pushed, add the repository and credentials to your Gradle +settings, then add the dependency: + +```kotlin +repositories { + maven { + url = uri("https://maven.pkg.github.com/alirezat66/flex_track_kotlin") + credentials { + username = providers.gradleProperty("gpr.user").orNull + password = providers.gradleProperty("gpr.key").orNull + } + } +} +``` + +```kotlin +dependencies { + implementation("dev.taghizadeh.flextrack:flextrack:1.0.0") +} +``` + +GitHub Packages requires a GitHub username and a token with `read:packages`. + +## Quick start + +Implement an event and a destination adapter: + +```kotlin +class PurchaseEvent : FlexEvent() { + override val name = "purchase" + override val properties = mapOf("plan" to "pro") +} + +class AnalyticsTracker : Tracker { + override val id = "analytics" + override suspend fun track(event: FlexEvent) { + // Forward the event to your analytics vendor. + } +} +``` + +Configure routing and use the lifecycle-aware client: + +```kotlin +val group = TrackerGroup("product", listOf("analytics")) +val client = FlexTrackClient( + routingEngine = RoutingEngine( + RoutingConfiguration( + rules = listOf(RoutingRule(targetGroup = group)), + ), + ), + queue = FileEventQueue(applicationContext), + consentProvider = { ConsentState(general = true) }, +) + +client.register(AnalyticsTracker()) +client.start() +val result = client.track(PurchaseEvent()) +client.flush() +client.shutdown() +``` + +All client operations are `suspend` functions. Call them from an application- +owned coroutine scope. `FileEventQueue` stores failed/offline deliveries in the +app's private files directory and retries only the destinations still pending. ## Modules @@ -25,12 +99,15 @@ The SDK is under active development toward Kotlin 1.0.0. The current version is ./gradlew :flextrack:publishToMavenLocal ``` -## Roadmap +## Release + +The tag must match the version in `flextrack/build.gradle.kts`. Pushing a tag +such as `v1.0.0` verifies the library and publishes it to GitHub Packages: -1. Library foundation and shared contract. -2. Event, enrichment, routing, consent, and deterministic sampling. -3. Tracker runtime, client lifecycle, debug records, and conformance. -4. Sample application, documentation, and Kotlin 1.0.0 release. +```bash +git tag v1.0.0 +git push origin v1.0.0 +``` ## License diff --git a/flextrack/build.gradle.kts b/flextrack/build.gradle.kts index 6e7cee4..1eadae1 100644 --- a/flextrack/build.gradle.kts +++ b/flextrack/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "dev.taghizadeh.flextrack" -version = "0.1.0-SNAPSHOT" +version = "1.0.0" android { namespace = "dev.taghizadeh.flextrack" @@ -56,6 +56,20 @@ dependencies { } publishing { + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/alirezat66/flex_track_kotlin") + credentials { + username = providers.gradleProperty("gpr.user") + .orElse(providers.environmentVariable("GITHUB_ACTOR")) + .orNull + password = providers.gradleProperty("gpr.key") + .orElse(providers.environmentVariable("GITHUB_TOKEN")) + .orNull + } + } + } publications { register("release") { groupId = project.group.toString() diff --git a/flextrack/src/main/kotlin/dev/taghizadeh/flextrack/FlexTrack.kt b/flextrack/src/main/kotlin/dev/taghizadeh/flextrack/FlexTrack.kt index ae99a15..d1ba642 100644 --- a/flextrack/src/main/kotlin/dev/taghizadeh/flextrack/FlexTrack.kt +++ b/flextrack/src/main/kotlin/dev/taghizadeh/flextrack/FlexTrack.kt @@ -2,8 +2,8 @@ package dev.taghizadeh.flextrack /** Package metadata for the FlexTrack Kotlin SDK. */ public object FlexTrack { - /** Current pre-release SDK version. */ - public const val VERSION: String = "0.1.0-SNAPSHOT" + /** Current SDK version. */ + public const val VERSION: String = "1.0.0" /** Language-neutral FlexTrack Core specification implemented by this SDK. */ public const val CORE_SPEC_VERSION: String = "1.0.0" diff --git a/flextrack/src/test/kotlin/dev/taghizadeh/flextrack/FlexTrackTest.kt b/flextrack/src/test/kotlin/dev/taghizadeh/flextrack/FlexTrackTest.kt index 2227a10..151afd4 100644 --- a/flextrack/src/test/kotlin/dev/taghizadeh/flextrack/FlexTrackTest.kt +++ b/flextrack/src/test/kotlin/dev/taghizadeh/flextrack/FlexTrackTest.kt @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test class FlexTrackTest { @Test fun `package metadata identifies SDK and contract versions`() { - assertEquals("0.1.0-SNAPSHOT", FlexTrack.VERSION) + assertEquals("1.0.0", FlexTrack.VERSION) assertEquals("1.0.0", FlexTrack.CORE_SPEC_VERSION) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 56905ff..b8c7b2e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,7 @@ material = { group = "com.google.android.material", name = "material", version.r desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } json = { module = "org.json:json", version.ref = "json" } [plugins] diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index fe48ee6..1db3c11 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -29,6 +29,7 @@ android { } } compileOptions { + isCoreLibraryDesugaringEnabled = true sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } @@ -38,10 +39,12 @@ android { } dependencies { + coreLibraryDesugaring(libs.desugar.jdk.libs) implementation(project(":flextrack")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) + implementation(libs.kotlinx.coroutines.android) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index d2651b4..cef8d4c 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -10,6 +10,15 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.FlexTrackKotlin" /> + android:theme="@style/Theme.FlexTrackKotlin"> + + + + + + + - \ No newline at end of file + diff --git a/sample/src/main/kotlin/dev/taghizadeh/flextrack/sample/MainActivity.kt b/sample/src/main/kotlin/dev/taghizadeh/flextrack/sample/MainActivity.kt new file mode 100644 index 0000000..def7a98 --- /dev/null +++ b/sample/src/main/kotlin/dev/taghizadeh/flextrack/sample/MainActivity.kt @@ -0,0 +1,89 @@ +package dev.taghizadeh.flextrack.sample + +import android.os.Bundle +import android.widget.Button +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import dev.taghizadeh.flextrack.event.FlexEvent +import dev.taghizadeh.flextrack.routing.ConsentState +import dev.taghizadeh.flextrack.routing.RoutingConfiguration +import dev.taghizadeh.flextrack.routing.RoutingEngine +import dev.taghizadeh.flextrack.routing.RoutingRule +import dev.taghizadeh.flextrack.routing.TrackerGroup +import dev.taghizadeh.flextrack.runtime.FileEventQueue +import dev.taghizadeh.flextrack.runtime.FlexTrackClient +import dev.taghizadeh.flextrack.runtime.Tracker +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch + +class MainActivity : AppCompatActivity() { + private val scope = MainScope() + private lateinit var output: TextView + private lateinit var client: FlexTrackClient + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + output = TextView(this).apply { text = getString(R.string.ready) } + val track = Button(this).apply { + text = getString(R.string.track_purchase) + setOnClickListener { sendPurchase() } + } + setContentView(LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + val padding = (24 * resources.displayMetrics.density).toInt() + setPadding(padding, padding, padding, padding) + addView(track) + addView(output) + }) + + client = FlexTrackClient( + routingEngine = RoutingEngine( + RoutingConfiguration( + rules = listOf( + RoutingRule( + targetGroup = TrackerGroup("analytics", listOf("console")), + ), + ), + ), + ), + queue = FileEventQueue(this), + consentProvider = { ConsentState(general = true) }, + ) + scope.launch { + client.register(ConsoleTracker { output.text = it }) + client.start() + client.flush() + } + } + + private fun sendPurchase() { + scope.launch { + val result = client.track(PurchaseEvent()) + output.text = getString( + R.string.result, + result.successfulTrackerIds.joinToString(), + result.queuedTrackerIds.joinToString(), + ) + } + } + + override fun onDestroy() { + scope.launch { client.shutdown() }.invokeOnCompletion { scope.cancel() } + super.onDestroy() + } +} + +private class PurchaseEvent : FlexEvent() { + override val name: String = "purchase" + override val properties: Map = mapOf("plan" to "pro", "currency" to "EUR") +} + +private class ConsoleTracker(private val log: (String) -> Unit) : Tracker { + override val id: String = "console" + + override suspend fun track(event: FlexEvent) { + log("${event.name}: ${event.properties}") + } +} diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 34a2495..ebaa253 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -1,3 +1,6 @@ FlexTrack Kotlin - \ No newline at end of file + FlexTrack is ready. + Track purchase + Delivered: %1$s\nQueued: %2$s +