Skip to main content

کلاینت کاتلین (Android)

بک‌اند یک spec خودکارِ OpenAPI تولید می‌کند — از روی همان Zod schema‌های packages/contracts که DTOهای بک‌اند را تعریف می‌کنند. از روی این spec، یک کلاینت نوع‌امنِ Kotlin با ./scripts/gen-kotlin-client.sh تولید و به‌صورت checked-in در همین ریپو زندگی می‌کند (ماژول :core:network از build خود اپ) — بدون این‌که هیچ مدل یا تابعِ API را دستی بنویسد.

چی تولید می‌شود؟

خروجیتعدادتوضیح
Data class های @Serializable~۷۶Event، Calendar، SyncPushRequest، SyncConflictDto، DeviceDto، … — دقیقاً مطابق Zod schema‌های بک‌اند
Enum ها~۱۰SyncOp.UPSERT، ItemStatus.CREATED، DevicePlatform.ANDROID، ReminderType.MINUTE، …
متدهای typed API~۳۱suspend fun syncPush(req): SyncPushResponseData، suspend fun verifyOtp(req): VerifyOtpResponse، …
تزریق Bearer authخودکارتوکن access در همهٔ درخواست‌ها به‌صورت خودکار در هدر Authorization ارسال می‌شود

چه مشکلی را حل می‌کند؟

بدون کلاینتِ تولید‌شده (دستی، بدون type safety):

val res = httpClient.post("https://api.taghvimam.ir/api/sync/push") {
header("Authorization", "Bearer $token")
setRawBody("""{"events":[{"uid":"abc","op":"upsert","title":"Meeting"}]}""")
}
// parse دستی JSON، cast نوع‌ها، امیدوار که چیزی عوض نشده باشد...

با کلاینتِ تولید‌شده (type-safe، کامپایل‌چک، autocomplete):

val api = TaghvimamApi(
baseUrl = "https://api.taghvimam.ir",
accessToken = token
)

// Push
val pushResult = api.syncPush(SyncPushRequest(
events = listOf(SyncPushEventItem(
uid = "abc",
baseVersion = null,
op = SyncOp.UPSERT,
title = "Meeting",
startTime = "2026-07-03T10:00:00Z",
endTime = "2026-07-03T11:00:00Z"
))
))
// pushResult.events[0].status == ItemStatus.CREATED ← enum کامپایل‌امن
// pushResult.events[0].version == "42" ← String (BigInt-as-string)

// Pull
val pullResult = api.syncChanges(cursor = lastCursor, limit = 500)
// pullResult.nextCursor ← cursor امضا‌شده (black-box) برای pull بعدی

// Conflicts
val conflicts = api.syncConflicts()
conflicts.forEach { c ->
api.syncConflictsResolve(c.conflictId, SyncConflictResolveRequest(action = "restore"))
}

نحوهٔ استفاده (برای توسعه‌دهندهٔ اندروید)

۱. کلاینت کجاست و چطور بازتولید می‌شود؟

کد تولیدشده به‌صورت checked-in در همین monorepo زندگی می‌کند: apps/mobile/core/network/src/commonMain/kotlin/ir/taghvimam/api/. بعد از هر تغییر قرارداد، ./scripts/gen-kotlin-client.sh را از ریشهٔ ریپو اجرا کنید و نتیجه را commit کنید — اگر فراموش شود، CI (workflow «kotlin-client») با خطای drift جلوی merge را می‌گیرد.

۲. اضافه‌کردن به پروژهٔ اندروید

نیازی به کپی‌کردن یا افزودن نیست — کد تولیدشده ماژولی از build خود اپ است (:core:network) و به‌صورت خودکار روی classpath همهٔ targetها (Android و Wasm) قرار دارد. بعد از تغییر قرارداد فقط ./scripts/gen-kotlin-client.sh را اجرا و نتیجه را commit کنید.

ساختار کد تولیدشده در ریپو:

apps/mobile/core/network/src/commonMain/kotlin/ir/taghvimam/api/
├── infrastructure/
│ └── ... ← کلاس‌های داخلی serializer/multipart
├── model/
│ ├── Event.kt ← @Serializable data class
│ ├── Calendar.kt
│ ├── SyncPushRequest.kt
│ ├── SyncPushEventItem.kt
│ ├── SyncOp.kt ← enum
│ ├── ItemStatus.kt ← enum
│ └── ... ← بقیهٔ ۷۶ مدل
└── api/
├── SyncApi.kt ← suspend fun syncPush(), syncChanges(), ...
├── AuthApi.kt ← suspend fun verifyOtp(), refresh(), ...
└── ...

وابستگی‌های Ktor و kotlinx-serialization نیز در build.gradle.kts همین ماژول (از طریق convention pluginهای مشترک) تعریف شده‌اند — نه در پروژهٔ شما.

مصرف‌کنندهٔ بیرونی وجود ندارد. چون کلاینت و API در یک ریپو زندگی می‌کنند، مفهوم «کاربر بیرونی که باید کلاینت را جداگانه دریافت کند» منتفی است؛ هر تغییر در قرارداد، همان‌جا در build اپ اعمال و کامپایل می‌شود.

۳. استفاده در کد

class TaghvimamRepository(private val tokenStore: TokenStore) {

private val api by lazy {
TaghvimamApi(
baseUrl = "https://api.taghvimam.ir",
accessToken = tokenStore.accessToken
)
}

suspend fun pushChanges(events: List<SyncPushEventItem>): SyncPushResponseData {
return api.syncPush(SyncPushRequest(events = events))
}

suspend fun pullChanges(cursor: String?): SyncChangesData {
return api.syncChanges(cursor = cursor, limit = 500)
}
}

همگام‌سازی خودکار

چه اتفاقی می‌افتدچه چیزی تولید می‌شود
توسعه‌دهندهٔ بک‌اند یک endpoint یا فیلد تغییر می‌دهد→ CI به‌صورت خودکار spec را از روی Zod بازتولید می‌کند
spec تغییر می‌کند→ CI کلاینت Kotlin را بازتولید می‌کند و اگر خروجی با commit فعلی فرق داشته باشد، با drift-gate (git diff --cached) جلوی merge را می‌گیرد
توسعه‌دهنده محلی ./scripts/gen-kotlin-client.sh را اجرا و خروجی را commit می‌کند→ اگر چیزی breaking باشد، کد اندروید کامپایل نمی‌شود (type mismatch)

این یعنی: هر تغییرِ breaking در بک‌اند، در زمانِ build اندروید کشف می‌شود — نه در runtime.

مسیر ارتقا (آینده)

قابلیتوضعیت
کلاینت checked-in + drift-gate در CI✅ فعلی — تولید با ./scripts/gen-kotlin-client.sh و commit در همین ریپو
دانلود دستی artifact❌ حذف‌شده — روش قدیمی؛ دیگر artifact‌ای منتشر نمی‌شود
Ktor 3 + KMP⏳ با Litote/openapi-ktor-client-generator — KMP-compatible + kotlinx-serialization

برای جزئیاتِ CI workflow به فایل .github/workflows/kotlin-client.yml در همین ریپو مراجعه کنید.

استفاده در یک کلاینت واقعی

راهنمای گام‌به‌گامِ نصبِ همین SDK و بستنِ یک کلاینتِ کامل اندروید دورش (ورود، pull اولیه، outbox، حلقهٔ push/pull، تعارض‌ها، محرک‌ها) در کلاینت اندروید آمده است.