A professional Kotlin Multiplatform AR application demonstrating Clean Architecture, DDD principles, and platform-specific AR implementations (ARCore/ARKit)
Features β’ Architecture β’ Tech Stack β’ Getting Started β’ Documentation
ARSample is a production-ready Augmented Reality application built with Kotlin Multiplatform Mobile (KMM), showcasing enterprise-grade architecture patterns and modern mobile development practices. The app allows users to import, place, and manage 3D objects in AR scenes across both Android and iOS platforms with a fully shared business logic layer.
- ποΈ Clean Architecture + DDD: Domain-driven design with clear separation of concerns
- π 95%+ Code Sharing: Business logic, UI, and domain layer shared between platforms
- π MVVM Pattern: Reactive state management with Kotlin Flow
- π¨ Jetpack Compose: Modern declarative UI for both platforms
- π§ͺ High Test Coverage: 85-100% test coverage with comprehensive unit tests
- π Type Safety: Value Objects pattern for domain validation
- π¦ Repository Pattern: Clean data abstraction with DTO/Mapper pattern
- β 3D Model Import: Support for GLB and USDZ formats
- β AR Object Placement: Real-time hit testing and object positioning
- β Scene Persistence: Auto-save/restore AR scenes across app restarts
- β Object Management: Add, remove, and list imported 3D models
- β Cross-Platform UI: Identical user experience on Android and iOS
- π Domain Validation: Value Objects with sealed classes (ModelUri, ObjectName)
- π Result Pattern: Type-safe error handling throughout the application
- π― Use Case Pattern: Single-responsibility business logic units
- ποΈ Local Storage: Platform-specific implementations (DataStore/UserDefaults)
- π§© Expect/Actual Pattern: Clean platform-specific abstractions
This project follows Eric Evans' Domain-Driven Design (DDD) + Clean Architecture principles with four distinct layers:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Presentation Layer β
β β’ ViewModels (State Management) β
β β’ Compose UI Screens β
β β’ Platform-specific AR Views (AndroidView/UIViewWrapper) β
β β’ Depends on: Application Layer β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ
β Application Layer β
β β’ Use Cases (ImportObject, PlaceObject, RemoveObject) β
β β’ Business Workflows β
β β’ Use Case DTOs (Input/Output models) β
β β’ Depends on: Domain Layer only β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ
β Domain Layer β
β β’ Entities (ARObject, ARScene, PlacedObject) β
β β’ Value Objects (ModelUri, ObjectName) β
β β’ Repository Interfaces β
β β’ Domain Exceptions β
β β’ NO dependencies (innermost layer) β
ββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β²
ββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββ
β Infrastructure Layer β
β β’ Repository Implementations β
β β’ DTOs + Mappers (Persistence) β
β β’ Local Data Sources (Platform-specific) β
β β’ File Storage (Internal Storage / Documents Directory) β
β β’ Depends on: Domain Layer β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Presentation β Application β Domain β Infrastructure
Key Architectural Rules:
- Domain Layer (innermost): Pure business logic, zero dependencies
- Application Layer: Orchestrates domain objects, depends on Domain only
- Infrastructure Layer: Technical implementations, depends on Domain (not Application)
- Presentation Layer: UI layer, depends on Application
sealed class ModelUri private constructor(val value: String) {
companion object {
fun create(uri: String): Result<ModelUri> {
return when {
uri.isBlank() -> Result.failure(ValidationException("URI cannot be blank"))
!uri.matches(Regex(".*\\.(glb|usdz)$")) ->
Result.failure(ValidationException("Invalid model format"))
else -> Result.success(ValidModelUri(uri))
}
}
}
private class ValidModelUri(value: String) : ModelUri(value)
}// Use cases in APPLICATION LAYER (not domain)
// Location: application/usecase/
interface ImportObjectUseCaseInterface : BaseUseCase<ImportObjectInput, ARObject>
class ImportObjectUseCase(
private val repository: ARObjectRepository
) : ImportObjectUseCaseInterface {
override suspend fun invoke(input: ImportObjectInput): Result<ARObject> {
// Validation with Value Objects (from domain)
val nameResult = ObjectName.create(input.name)
if (nameResult.isFailure) return Result.failure(nameResult.exceptionOrNull()!!)
return repository.importObject(input.uri, input.name, input.modelType)
}
}
// Import paths:
import com.trendhive.arsample.application.usecase.ImportObjectUseCase
import com.trendhive.arsample.application.base.BaseUseCase
import com.trendhive.arsample.application.dto.ImportObjectInput// Persistence DTO in INFRASTRUCTURE LAYER
// Location: infrastructure/persistence/dto/
@Serializable
data class ARObjectDTO(
val id: String,
val name: String,
val modelUri: String,
val modelType: String
)
// Mapper in INFRASTRUCTURE LAYER
// Location: infrastructure/persistence/mapper/
class ARObjectMapper : BaseMapper<ARObjectDTO, ARObject> {
override fun toDTO(model: ARObject): ARObjectDTO
override fun toModel(dto: ARObjectDTO): ARObject
}
// Import paths:
import com.trendhive.arsample.infrastructure.persistence.dto.ARObjectDTO
import com.trendhive.arsample.infrastructure.persistence.mapper.ARObjectMapper
import com.trendhive.arsample.infrastructure.persistence.BaseMapper- Kotlin 2.1.0 - Primary programming language
- Compose Multiplatform 1.7.1 - Declarative UI framework
- Kotlin Coroutines - Asynchronous programming
- Kotlin Flow - Reactive state management
- ARCore (Android) - Google's AR platform
- SceneView - ARCore wrapper library
- ARKit (iOS) - Apple's AR platform
- RealityKit - iOS AR rendering
- Kotlin Serialization - JSON serialization
- DataStore (Android) - Preferences storage
- UserDefaults (iOS) - Preferences storage
- Kotlin Test - Testing framework
- MockK - Mocking library
- Turbine - Flow testing utilities
- Gradle Version Catalog - Dependency management
- Android Gradle Plugin 8.7.3 - Android build
- Xcode 15+ - iOS build
Required:
- JDK 17 or higher
- Android Studio Ladybug (2024.2.1) or newer
- Xcode 15+ (for iOS development)
- macOS (for iOS builds)
AR Device Requirements:
- Android: ARCore-supported device (Check compatibility)
- iOS: A12+ chip with ARKit support (iPhone XS and newer)
-
Clone the repository
git clone https://github.com/recepteksi/ARSample.git cd ARSample -
Build Android
./gradlew :composeApp:assembleDebug
-
Build iOS
# Open in Xcode open iosApp/iosApp.xcodeproj # Or use xcodebuild xcodebuild -project iosApp/iosApp.xcodeproj -scheme iosApp -configuration Debug
# Run all tests
./gradlew :composeApp:testDebugUnitTest
# Run specific test class
./gradlew :composeApp:testDebugUnitTest --tests "com.trendhive.arsample.domain.usecase.ImportObjectUseCaseTest"
# Run with coverage
./gradlew :composeApp:testDebugUnitTest --tests "*" --infoARSample/
βββ composeApp/src/
β βββ commonMain/kotlin/com/trendhive/arsample/
β β βββ domain/ # Domain Layer (NO dependencies)
β β β βββ base/ # BaseModel, BaseRepository
β β β βββ model/ # Domain entities (ARObject, ARScene, PlacedObject)
β β β β βββ valueobjects/ # Value Objects (ModelUri, ObjectName)
β β β βββ repository/ # Repository interfaces
β β β βββ exception/ # Domain exceptions
β β β
β β βββ application/ # Application Layer (depends on Domain)
β β β βββ base/ # BaseUseCase<Input, Output>
β β β βββ dto/ # Use Case Input/Output DTOs
β β β βββ usecase/ # Business workflows (use cases)
β β β
β β βββ infrastructure/ # Infrastructure Layer (depends on Domain)
β β β βββ persistence/
β β β βββ dto/ # Persistence DTOs
β β β βββ mapper/ # DTO β Model mappers
β β β βββ repository/ # Repository implementations
β β β βββ local/ # Data source interfaces
β β β βββ BaseMapper.kt # Mapper base class
β β β
β β βββ presentation/ # Presentation Layer (depends on Application)
β β βββ viewmodel/ # State management
β β βββ ui/ # Compose screens and components
β β
β βββ androidMain/ # Android-specific (ARCore, DataStore)
β β βββ ar/ # ARCore implementation
β β βββ infrastructure/persistence/local/ # Android data sources
β β
β βββ iosMain/ # iOS-specific (ARKit, UserDefaults)
β β βββ ar/ # ARKit implementation
β β βββ infrastructure/persistence/local/ # iOS data sources
β β
β βββ commonTest/ # Shared unit tests
β
βββ iosApp/ # iOS app entry point
βββ docs/ # Architecture docs and guides
βββ .claude/agents/ # AI agent system documentation
- Domain Layer: 90%+ coverage
- Use Cases: 100% coverage
- ViewModels: 85%+ coverage
- Repositories: 90%+ coverage
class ImportObjectUseCaseTest {
private lateinit var repository: ARObjectRepository
private lateinit var useCase: ImportObjectUseCase
@Test
fun `import valid object succeeds`() = runTest {
// Arrange
val input = ImportObjectInput("file://model.glb", "Chair", ModelType.GLB)
coEvery { repository.importObject(any(), any(), any()) } returns
Result.success(mockARObject)
// Act
val result = useCase(input)
// Assert
assertTrue(result.isSuccess)
coVerify { repository.importObject("file://model.glb", "Chair", ModelType.GLB) }
}
}- Architecture Overview - System design and patterns
- Agent System - Multi-agent development workflow
- Hit Testing Guide - AR interaction implementation
- Android ARCore - Android AR implementation
- iOS ARKit - iOS AR implementation
- Code Review Checklist - Quality standards
interface ARObjectRepository : BaseRepository {
suspend fun importObject(uri: String, name: String, type: ModelType): Result<ARObject>
suspend fun getAllObjects(): Result<List<ARObject>>
suspend fun deleteObject(id: String): Result<Unit>
}// BaseModel and BaseRepository in DOMAIN layer
interface BaseModel
interface BaseRepository
// BaseUseCase in APPLICATION layer
interface BaseUseCase<Input : BaseModel, Output : BaseModel> {
suspend operator fun invoke(input: Input): Result<Output>
}
// BaseMapper in INFRASTRUCTURE layer
interface BaseMapper<DTO, Model> {
fun toDTO(model: Model): DTO
fun toModel(dto: DTO): Model
}
// Import paths:
import com.trendhive.arsample.domain.base.BaseModel
import com.trendhive.arsample.domain.base.BaseRepository
import com.trendhive.arsample.application.base.BaseUseCase
import com.trendhive.arsample.infrastructure.persistence.BaseMappersealed class DomainException(message: String) : Exception(message)
class ValidationException(message: String) : DomainException(message)
class EntityNotFoundException(message: String) : DomainException(message)
class StorageException(message: String) : DomainException(message)Contributions are welcome! This project follows professional development practices:
- Code Standards: Kotlin conventions, Clean Architecture compliance
- Testing: All new features must include unit tests
- Documentation: Update relevant docs with changes
- Review Process: Code review checklist validation
See CONTRIBUTING.md for detailed guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.
Recep TekΕi
- GitHub: @recepteksi
- LinkedIn: Recep TekΕi
This project demonstrates:
β
Modern Android/iOS Development - KMM, Compose, ARCore/ARKit
β
Enterprise Architecture - Clean Architecture, DDD, SOLID principles
β
Professional Practices - High test coverage, type safety, documentation
β
Platform Expertise - Native AR implementations, platform-specific optimizations
β
Team Collaboration - Multi-agent system, code review standards
Built with β€οΈ using Kotlin Multiplatform
β Star this repo if you find it useful!