Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 78 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,83 @@

# TidepoolKotlinAPI

A Kotlin library for connecting to the [Tidepool API][api-docs].
This library is currently unfinished, so not all requests and schemas are implemented.
A Kotlin library for interacting with the Tidepool API.

## Features

- **Dependency Injection**: Uses Koin for clean dependency management
- **Environment Support**: Easy switching between Production, Integration, Dev, and QA environments
- **Kotlinx Serialization**: Type-safe JSON handling with polymorphic serialization
- **Coroutines**: Full support for Kotlin coroutines and suspend functions
- **Repository Pattern**: Clean separation between API and business logic
- **Token Management**: Token-based authentication with flexible token provider

## Quick Start

```kotlin
val sdk = TidepoolSDK(
environment = Environments.Production,
getToken = {
// Provide your token retrieval logic
"your-session-token"
}
)

// Get current user information
val userResult = sdk.getCurrentUserInfo()
userResult.fold(
onSuccess = { user -> println("User: ${user.username}") },
onFailure = { error -> println("Error: ${error.message}") }
)

// Get diabetes data for a user
val dataResult = sdk.getUserData(
userId = "user-id",
types = listOf(BaseData.DataType.Cbg, BaseData.DataType.Bolus),
startDate = Instant.now().minus(Duration.ofDays(7)),
endDate = Instant.now()
)

// Get care partner invitations
val invitationsResult = sdk.getReceivedInvitations()

// Get trust relationships (care partners)
val trustUsersResult = sdk.getTrustUsers()

// Clean up when done
sdk.shutdown()
```

## Architecture

This library is structured in multiple modules:

- **`lib`** - Main SDK module with public API and domain models
- **`data`** - Data layer with repository implementations, DTOs, and Retrofit API interfaces (internal)

The SDK follows Clean Architecture principles with clear separation between domain models (in `lib`) and data transfer
objects (in `data`).

## Supported Data Types

The SDK currently supports the following Tidepool data types:

- Continuous Glucose (CGM/CBG)
- Bolus insulin doses
- Basal insulin (automated)
- Food entries
- Dosing decisions
- Insulin data

## Environment Configuration

Available environments:

- `Environments.Production` - Production Tidepool environment
- `Environments.Integration` - Integration testing environment
- `Environments.Dev1` - Development environment
- `Environments.Qa1` - QA environment 1
- `Environments.Qa2` - QA environment 2

## Example

Expand All @@ -16,3 +91,4 @@ See the [example application][example].
## Other languages

* [Swift (TidepoolKit)][swift-api]

226 changes: 226 additions & 0 deletions data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# Tidepool Kotlin API - Data Module

This module contains the data layer implementation for the Tidepool Kotlin API, following Clean
Architecture principles with modern Android development practices.

## Structure

```
data/
├── src/main/kotlin/org/tidepool/sdk/
│ ├── api/ # Retrofit API interfaces
│ │ ├── AuthApi.kt # Authentication endpoints
│ │ ├── ConfirmationApi.kt # Email/phone confirmation endpoints
│ │ ├── DataApi.kt # Diabetes data endpoints
│ │ ├── MetadataApi.kt # Metadata endpoints
│ │ └── UserApi.kt # User management endpoints
│ ├── dto/ # Data Transfer Objects
│ │ ├── auth/ # Authentication DTOs
│ │ ├── confirmation/ # Email/phone confirmation DTOs
│ │ ├── data/ # Diabetes data DTOs (bolus, CGM, food, etc.)
│ │ ├── metadata/ # Metadata DTOs
│ │ └── user/ # User management DTOs
│ ├── repository/ # Repository interfaces & implementations
│ │ ├── AuthRepository.kt
│ │ ├── ConfirmationRepository.kt
│ │ ├── DataRepository.kt
│ │ ├── MetadataRepository.kt
│ │ ├── UserRepository.kt
│ │ └── impl/ # Repository implementations
│ ├── deserialization/ # JSON serialization utilities
│ ├── di/ # Dependency injection modules
│ │ └── DataModule.kt # Koin module configuration
│ └── datasource/ # Data source implementations
```

## Key Features

- **Clean Architecture**: Separation of concerns with repository pattern
- **Dependency Injection**: Koin-based DI for easy testing and modularity
- **Type Safety**: Comprehensive DTO classes with Kotlin serialization
- **Async Operations**: Coroutines-based async/await pattern
- **Error Handling**: Result types for safe error handling
- **Polymorphic Serialization**: Support for different diabetes data types

## Dependencies

This module leverages modern Android/Kotlin libraries:

- **Retrofit 3.0.0** - HTTP networking with coroutines support
- **Kotlinx Serialization** - JSON parsing with polymorphic support
- **Koin 4.1.0** - Dependency injection
- **OkHttp 5.1.0** - HTTP client with logging interceptor
- **Coroutines 1.10.2** - Async operations

## Setup

### 1. Initialize Dependencies

```kotlin
// Initialize Koin DI
startKoin {
modules(dataModule)
}

// Provide environment configuration
val environment = EnvironmentInternal(
url = "https://api.tidepool.org",
auth = AuthenticationServerInternal("https://auth.tidepool.org")
)
```

### 2. Inject Repositories

```kotlin
// Get repositories from Koin
val authRepository: AuthRepository by inject()
val dataRepository: DataRepository by inject()
val userRepository: UserRepository by inject()
val confirmationRepository: ConfirmationRepository by inject()
val metadataRepository: MetadataRepository by inject()
```

## Usage Examples

### Authentication Flow

```kotlin
// Password-based authentication
val tokenRequest = TokenRequestDto.createWithPassword(
client_id = "your-client-id",
username = "user@example.com",
password = "password"
)

val tokenResult = authRepository.obtainToken(
realm = RealmDto.Tidepool,
tokenRequest = tokenRequest
)

tokenResult.fold(
onSuccess = { tokenResponse ->
val sessionToken = tokenResponse.access_token
println("Authenticated successfully")
},
onFailure = { error ->
println("Authentication failed: ${error.message}")
}
)
```

### Email Confirmation

```kotlin
// Send confirmation email
val confirmationResult = confirmationRepository.sendConfirmation(
sessionToken = sessionToken,
type = ConfirmationType.SIGNUP,
emailAddress = "user@example.com"
)

// Confirm email with verification code
val confirmResult = confirmationRepository.confirmSignup(
confirmationKey = "verification-key-from-email"
)
```

### Fetching Diabetes Data

```kotlin
// Fetch specific data types for a date range
val dataResult = dataRepository.getDataForUser(
sessionToken = sessionToken,
userId = "user-id",
types = CommaSeparatedArray(
BaseDataDto.DataTypeDto.Bolus,
BaseDataDto.DataTypeDto.Cbg,
BaseDataDto.DataTypeDto.Food
),
startDate = Instant.now().minus(Duration.ofDays(7)),
endDate = Instant.now()
)

dataResult.fold(
onSuccess = { dataList ->
// Process different data types polymorphically
dataList.forEach { baseData ->
when (baseData) {
is BolusDataDto -> println("Insulin bolus: ${baseData.normal}u")
is ContinuousGlucoseDataDto -> println("BG: ${baseData.value} mg/dL")
is FoodDataDto -> println("Carbs: ${baseData.nutrition?.carbohydrate?.net}g")
// Handle other data types...
}
}
},
onFailure = { error ->
println("Failed to fetch data: ${error.message}")
}
)
```

### User Management

```kotlin
// Get user profile
val userResult = userRepository.getUser(
sessionToken = sessionToken,
userId = userId
)

// Get user metadata
val metadataResult = metadataRepository.getMetadataForUser(
sessionToken = sessionToken,
userId = userId
)
```

## Data Types Supported

The module supports comprehensive diabetes data types:

- **BolusDataDto** - Insulin bolus data
- **BasalAutomatedDataDto** - Automated basal insulin
- **ContinuousGlucoseDataDto** - CGM readings
- **FoodDataDto** - Food and carbohydrate entries
- **DosingDecisionDataDto** - Automated insulin dosing decisions
- **InsulinDataDto** - General insulin data

## Error Handling

All operations return `Result<T>` types for explicit error handling:

```kotlin
when (val result = repository.someOperation()) {
is Result.Success -> {
val data = result.getOrNull()
// Handle success
}
is Result.Failure -> {
val error = result.exceptionOrNull()
// Handle error
}
}
```

## Testing

The module includes comprehensive test coverage with:

- **JUnit 5** for unit tests
- **Coroutines Test** for async testing
- **MockWebServer** for API mocking
- **Koin Test** for DI testing

Run tests with:

```bash
./gradlew :data:test
```

## Architecture Benefits

- **Testability**: Dependency injection enables easy mocking
- **Maintainability**: Clear separation of concerns
- **Scalability**: Repository pattern supports multiple data sources
- **Type Safety**: Kotlin's type system prevents runtime errors
- **Modern**: Uses latest Android/Kotlin best practices
59 changes: 59 additions & 0 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
plugins {
kotlin("jvm") version "2.2.0"
kotlin("plugin.serialization") version "2.2.0"
id("com.google.devtools.ksp") version "2.2.20-2.0.3"
// Apply the java-library plugin for API and implementation separation.
`java-library`
}

kotlin {
compilerOptions {
freeCompilerArgs.add("-Xcontext-parameters")
}
}

repositories {
mavenCentral()
google()
}

dependencies {
// Networking
implementation("com.squareup.retrofit2:retrofit:3.0.0")
implementation("com.squareup.okhttp3:okhttp:5.1.0")

// Serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")

// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")

// Room KMP dependencies for local storage
implementation("androidx.room:room-runtime:2.8.1")
implementation("androidx.sqlite:sqlite-bundled:2.5.0")
implementation("com.squareup.okhttp3:logging-interceptor:5.1.0")
add("ksp", "androidx.room:room-compiler:2.8.1")

// Koin dependency injection
implementation("io.insert-koin:koin-core:4.1.0")

// Testing
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
testImplementation("com.squareup.okhttp3:mockwebserver:5.1.0")
testImplementation("io.insert-koin:koin-test:4.1.0")

testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}

tasks.named<Test>("test") {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.tidepool.sdk

enum class AuthenticationServerInternal(val url: String){
Development("https://auth.dev.tidepool.org"),
QA("https://auth.qa2.tidepool.org"),
External("https://auth.external.tidepool.org"),
Production("https://auth.tidepool.org");
}
Loading