> ## Documentation Index
> Fetch the complete documentation index at: https://ekacare-quickstart-cleanup.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Medical records

# Records SDK - Android (Developer Platform)

## SDK Introduction

The **Records SDK** provides a streamlined and flexible way to manage digital records within your Android application. It acts as a unified entry point for storing, retrieving, updating, and deleting record data, allowing developers to easily integrate document workflows, tagging, filtering, and metadata management.

Built with modern Android architecture principles, the SDK is fully coroutine-compatible, supports reactive data flows with `Flow`, and ensures efficient local storage with robust database handling. Whether you're building a document management tool, a compliance platform, or a file organization feature, the Records SDK handles the heavy lifting so you can focus on building great user experiences.

### Key Features

* 📄 **Record Creation**: Easily add new records with support for metadata and tagging.
* 🔍 **Filtering & Retrieval**: Retrieve records by owner, filters, document type, and more.
* 📊 **Analytics Support**: Get grouped counts for document types and track record usage.
* ✏️ **Metadata Updates**: Update existing records with new document dates or types.
* 🗑️ **Deletion & Cleanup**: Soft-delete individual records or clear all local data when needed.
* ⚙️ **Coroutines & Flow**: Built for modern asynchronous Android development.

###

## API Reference

For complete API reference, please refer to the source code documentation in the GitHub repository:
[https://github.com/eka-care/eka-health-records-android](https://github.com/eka-care/eka-health-records-android)

## Prerequisites

Before using the Eka Health Records SDK, ensure your development environment meets the following requirements:

* Minimum SDK version: 23
* Kotlin version: 1.5.0 or newer
* JDK 8 or newer
* Authentication with Eka platform (tokens required for initialisation)

## **Installing the Library**

Add the repository to your project-level `build.gradle` or `settings.gradle` file:

```kotlin theme={null}
repositories {
		maven { url = uri("https://www.jitpack.io") }
}
```

Include the dependency in your app-level `build.gradle.kts` file:

```kotlin theme={null}
implementation("com.github.eka-care:eka-health-records-android:2.0.0")
```

## SDK Initialisation

Initialise the SDK by creating an implementation of the `IOkHttpSetup` interface to provide authentication tokens:

```kotlin theme={null}
class OkHttpImpl : IOkHttpSetup {
    // Provide headers necessary for authentication
    override fun getDefaultHeaders(url: String): Map<String, String> {
        val headers = HashMap<String, String>()
        headers["auth"] = "YOUR_SESSION_TOKEN_HERE"
        return headers
    }

    override fun refreshAuthToken(url: String): Map<String, String>? {
        // Implement token refresh logic
        return null
    }

    override fun onSessionExpire() {
        // Handle session expiration
    }
}
```

Pass the `IOkHttpSetup` implementation to `DocumentConfiguration` and call the init

```kotlin theme={null}
Document.init(
    context = applicationContext,
    documentConfiguration = DocumentConfiguration(
        okHttpSetup = OkHttpImpl()
    )
)
```

### RecordModel

A data transfer object representing document records, used for API communication.

```kotlin theme={null}
data class RecordModel(
    val id: String,
    val thumbnail: String? = null,
    val status: RecordStatus = RecordStatus.NONE,
    val createdAt: Long,
    val updatedAt: Long,
    val documentDate: Long? = null,
    val documentType: String = "ot",
    val isSmart: Boolean = false,
    val smartReport: String? = null,
    val files: List<RecordFile> = emptyList(),
) {
    data class RecordFile(
        val id: Long,
        val filePath: String?,
        val fileType: String,
    )
}
```

### Document Types

Constants used to specify document categories:

| Type ID | Description         |
| ------- | ------------------- |
| lr      | Lab Report          |
| ps      | Prescription        |
| ds      | Discharge Summary   |
| vc      | Vaccine Certificate |
| in      | Insurance           |
| iv      | Invoice             |
| sc      | Scan                |
| ot      | Other               |
| null    | All types (default) |

### SortOrder

Enum used for specifying document sort criteria:

```kotlin theme={null}
enum class SortOrder(val value: String, val order: String) {
    CREATED_AT_ASC("created_at", "ASC"),
    CREATED_AT_DSC("created_at", "DESC"),
    DOC_DATE_ASC("document_date", "ASC"),
    DOC_DATE_DSC("document_date", "DESC"),
    UPDATED_AT_ASC("updated_at", "ASC"),
    UPDATED_AT_DSC("updated_at", "DESC"),
}
```

### Getting the `Records` Instance

To begin using the SDK, you must first obtain an instance of `RecordsManager`. This instance serves as the **single entry point** to the SDK and provides access to all available functionality.

Use the following code to initialize and retrieve the instance:

```kotlin theme={null}
val recordsManager = Records.getInstance(
    context = app.applicationContext
)
```

> **Note:** Always pass the application context to ensure proper lifecycle management and avoid memory leaks.

### Creating a New Record

Once you have initialised the `recordsManager` instance, you can use it to create a new record entry using the `addNewRecord` function. This function is a `suspend` function and **must be called from a coroutine**.

### Function Signature

```kotlin theme={null}
suspend fun addNewRecord(
    files: List<File>,
    ownerId: String,
    filterId: String? = null,
    documentType: String = "ot",
    documentDate: Long? = null,
    tags: List<String> = emptyList()
): String?
```

Here's an example using `viewModelScope` and `Dispatchers.IO`:

```kotlin theme={null}
val recordsManager = Records.getInstance(
    context = app.applicationContext
)

viewModelScope.launch(Dispatchers.IO) {
    recordsManager.addNewRecord(
        files = listOf(file1, file2),         // List of files to be attached
        ownerId = "user_123",                 // Unique identifier for the owner of the record
        filterId = "filter_abc",              // Optional filter ID
        documentType = "ot",                  // Document type, default is "ot"
        documentDate = 1681234567890,         // Optional epoch timestamp for the document
        tags = listOf("invoice", "2025")      // Optional tags associated with the record
    )
}
```

### Function Parameters:

* **files**: `List<File>` — (Required). The files you want to associate with the record.
* **ownerId**: `String` — (Required). Identifier for the owner of the record.
* **filterId**: `String?` — (Optional). Used to apply a predefined filter or processing rule.
* **documentType**: `String` — (Optional). Specifies the type of document. Default is `"ot"`.
* **documentDate**: `Long?` — (Optional). UNIX timestamp representing the date of the document.
* **tags**: `List<String>` — (Optional). Tags for classifying or grouping records.

> Important: Ensure this function is called from a coroutine scope such as viewModelScope, and ideally off the main thread using Dispatchers.IO to avoid blocking UI operations.

### Retrieving Records

The `getRecords` function allows you to retrieve a list of records for a specific owner. The function returns a `Flow` of `List<RecordModel>`, enabling asynchronous and reactive collection of record data.

### Function Signature

```kotlin theme={null}
fun getRecords(
    ownerId: String,
    filterIds: List<String>? = null,
    includeDeleted: Boolean = false,
    documentType: String? = null,
    sortOrder: SortOrder,
): Flow<List<RecordModel>>
```

### Parameters

* **ownerId**: `String` — *(Required)* The unique identifier for the owner whose records are to be fetched.
* **filterIds**: `List<String>?` — *(Optional)* A list of filter IDs to apply during retrieval. If `null`, no filters are applied.
* **includeDeleted**: `Boolean` — *(Optional, default: false)* Whether to include records that have been marked as deleted.
* **documentType**: `String?` — *(Optional)* Filters records by the specified document type.
* **sortOrder**: `SortOrder` — *(Required)* Defines the sorting order of the results (e.g., ascending, descending). This must be provided.

### Return Value

Returns a `Flow<List<RecordModel>>` that emits updates to the list of records matching the criteria.

### Example Usage

```kotlin theme={null}
lifecycleScope.launch {
    recordsManager.getRecords(
        ownerId = "user_123",
        filterIds = listOf("filter_abc"),
        includeDeleted = false,
        documentType = "invoice",
        sortOrder = SortOrder.DESCENDING
    ).collect { records ->
        // Handle the list of records
    }
}
```

> Note: Since this function returns a Flow, it must be collected from a coroutine scope to receive updates.

### Getting Record Counts Grouped by Document Type

The `getRecordsCountGroupByType` function returns the total number of records for a specific owner, **grouped by document type**. This is useful for analytics, reporting, or building UI elements like dashboards or filters.

### Function Signature

```kotlin theme={null}
fun getRecordsCountGroupByType(
    ownerId: String,
    filterIds: List<String>? = null
): Flow<List<DocumentTypeCount>>

data class DocumentTypeCount(
    val documentType: String?,
    val count: Int?
)
```

### Parameters

* **ownerId**: `String` — *(Required)* The unique identifier for the owner whose records are being counted.
* **filterIds**: `List<String>?` — *(Optional)* A list of filter IDs to narrow down the results. If `null`, all records for the owner are considered.

### Return Value

Returns a `Flow<List<DocumentTypeCount>>`, where each `DocumentTypeCount` represents a document type and the associated count of records.

### Example Usage

```kotlin theme={null}
lifecycleScope.launch {
    recordsManager.getRecordsCountGroupByType(
        ownerId = "user_123",
        filterIds = listOf("filter_abc")
    ).collect { counts ->
        counts.forEach {
            Log.d("DocType", "${it.type}: ${it.count}")
        }
    }
}
```

> Note: This function is reactive and returns a Flow, which should be collected within a coroutine.

### Updating a Record

The `updateRecord` function allows you to update the metadata of an existing record, such as the document date or document type. This is a `suspend` function and must be called from within a coroutine.

### Function Signature

```kotlin theme={null}
suspend fun updateRecord(
    id: String,
    documentDate: Long? = null,
    documentType: String? = null,
): String?
```

### Parameters

* **id**: `String` — *(Required)* The unique identifier of the record to be updated.
* **documentDate**: `Long?` — *(Optional)* The new document date, represented as a UNIX timestamp. Pass `null` to leave unchanged.
* **documentType**: `String?` — *(Optional)* The new type of the document. Pass `null` to keep the existing type.

### Return Value

Returns a `String?` — the ID of the updated record if the operation is successful, or `null` if the update fails.

### Example Usage

```kotlin theme={null}
viewModelScope.launch {
    val updatedId = recordsManager.updateRecord(
        id = "record_456",
        documentDate = 1681234567890,
        documentType = "invoice"
    )

    if (updatedId != null) {
        Log.d("RecordUpdate", "Record updated: $updatedId")
    } else {
        Log.e("RecordUpdate", "Failed to update record.")
    }
}
```

> Note: Call this function from a coroutine (e.g., viewModelScope) to avoid blocking the main thread.

### Fetching Record Details by ID

The `getRecordDetailsById` function retrieves the full details of a specific record using its unique identifier. This is a `suspend` function and must be called from a coroutine.

### Function Signature

```kotlin theme={null}
suspend fun getRecordDetailsById(id: String): RecordModel?
```

### Parameters

* **id**: `String` — *(Required)* The unique identifier of the record whose details are to be retrieved.

### Return Value

Returns a `RecordModel?` — the complete record object if found, or `null` if the record does not exist.

### Example Usage

```kotlin theme={null}
viewModelScope.launch {
    val record = recordsManager.getRecordDetailsById("record_456")
    if (record != null) {
        Log.d("RecordDetails", "Document Type: ${record.documentType}")
    } else {
        Log.e("RecordDetails", "Record not found.")
    }
}
```

> Note: Always check for null as the record might not exist or could have been deleted.

### Deleting Records

The `deleteRecords` function allows you to delete one or more records by their unique identifiers. This operation is performed asynchronously and must be called from a coroutine.

### Function Signature

```kotlin theme={null}
suspend fun deleteRecords(ids: List<String>)
```

### Parameters

* **ids**: `List<String>` — *(Required)* A list of unique record IDs to be deleted.

### Example Usage

```kotlin theme={null}
viewModelScope.launch {
    try {
        recordsManager.deleteRecords(
            ids = listOf("record_001", "record_002")
        )
        Log.d("RecordDelete", "Records deleted successfully.")
    } catch (e: Exception) {
        Log.e("RecordDelete", "Failed to delete records: ${e.message}")
    }
}
```

### Clearing All Stored Data

The `clearAllData` function **permanently deletes all local data** stored by the SDK. This includes all records, metadata, and cached information managed by the SDK’s local database.

### Function Signature

```kotlin theme={null}
fun clearAllData()
```

### Behavior

* This function **clears all tables** in the local database.
* It is **irreversible** — once executed, all local data will be lost.
* Remote data (if applicable) will remain unaffected unless explicitly synchronised afterward.

### Example Usage

```kotlin theme={null}
recordsManager.clearAllData()
```

> ⚠️ Caution: Use this method only when a full data reset is required, such as during user logout or when resetting the app state. Ensure the user is properly informed before performing this action.

### Syncing Records

The `syncRecords` function triggers the **synchronisation of records** for a specific owner. This ensures that the local records are kept in sync with a remote server or cloud service (if applicable). The sync process is initiated asynchronously.

### Function Signature

```kotlin theme={null}
fun syncRecords(ownerId: String)
```

### Parameters

* **ownerId**: `String` — *(Required)* The unique identifier for the owner whose records need to be synchronised.

### Behavior

* This function **starts an automatic synchronisation** process to ensure that records are up-to-date both locally and remotely.
* Depending on the implementation, synchronisation may involve uploading local changes, downloading updated records, or both.

### Example Usage

```kotlin theme={null}
viewModelScope.launch {
    recordsManager.syncRecords(ownerId = "user_123")
    Log.d("Sync", "Sync process started.")
}
```

> Note: The synchronisation process is handled asynchronously, and the app may need to listen for completion or errors. Ensure proper handling of network conditions and sync progress.

### Refreshing Records

The `refreshRecords` function triggers a background sync process to refresh records for a specific owner, optionally applying filters. It uses **WorkManager** to ensure the sync task runs reliably in the background, even when the app is closed or the device is restarted. This operation ensures the records are up-to-date based on the latest data from the server or cloud service.

### Function Signature

```kotlin theme={null}
fun refreshRecords(
    context: Context,
    ownerId: String?,
    filterIds: List<String>? = null
)
```

### Parameters

* **context**: `Context` — *(Required)* The application context used to initialise and enqueue the background task.
* **ownerId**: `String?` — *(Required)* The unique identifier for the owner whose records should be refreshed. Can be `null` in case there is no specific owner.
* **filterIds**: `List<String>?` — *(Optional)* A list of filter IDs to apply during the record refresh process. If `null`, no filters will be applied.

### Behavior

* This function creates a background **OneTimeWorkRequest** using **WorkManager** to perform the sync task when the device is connected to the network.
* The sync task is defined by the `RecordsSync` worker, which processes the record refresh asynchronously.
* The task will only run if the device is connected to a network.

### Example Usage

```kotlin theme={null}
val context: Context = app.applicationContext
recordsManager.refreshRecords(
    context = context,
    ownerId = "user_123",
    filterIds = listOf("filter_abc", "filter_xyz")
)
Log.d("Refresh", "Records refresh process started.")
```

> Note: This function will enqueue a background task that will run once the device meets the necessary network requirements.

### WorkManager Constraints

* **Network Type**: The task requires an active network connection to run. The task will not run if the device is offline.

### Setting a Custom Log Interceptor

The `setLogInterceptor` function allows you to set a custom log interceptor for capturing and processing log events. This is useful for integrating with external logging systems, monitoring tools, or simply capturing logs in a custom format.

### Function Signature

```kotlin theme={null}
fun setLogInterceptor(listener: LogInterceptor)
```

### Parameters

* **listener**: `LogInterceptor` — *(Required)* A custom listener that implements the `LogInterceptor` interface. This listener will be notified of all log events that occur in the SDK.

### Example Usage

```kotlin theme={null}
val logInterceptor = object : LogInterceptor {
    override fun logEvent(eventLog: EventLog) {
        // Custom logging logic
        Log.d("CustomLog", "Event: ${eventLog.message}")
    }
}

recordsManager.setLogInterceptor(logInterceptor)
```

> Note: This function allows developers to implement custom logging behavior that suits their needs, such as sending logs to a server or filtering events based on severity.

***

### `LogInterceptor` Interface

The `LogInterceptor` interface defines a method for logging events. Implement this interface to capture and process log data from the SDK.

### Interface Definition

```kotlin theme={null}
interface LogInterceptor {
    /**
     * Log the event with the given event log.
     * @param eventLog The event log to log.
     */
    fun logEvent(eventLog: EventLog)
}
```

> Note: The logEvent method is called every time an event occurs in the SDK. You can implement custom logic within this method to handle logs as needed.

***

### `EventLog` Data Class

The `EventLog` data class encapsulates the log event data, including any parameters and an optional message. It is used to store the event information that will be passed to the `LogInterceptor`.

### Data Class Definition

```kotlin theme={null}
data class EventLog(
    val params: JSONObject = JSONObject(),
    val message: String? = null,
)
```

### Parameters

* **params**: `JSONObject` — *(Optional)* A `JSONObject` containing key-value pairs of parameters associated with the event. This can be used to include additional metadata with the log event.
* **message**: `String?` — *(Optional)* A message describing the event. This is the main content of the log.

### Example Usage

```kotlin theme={null}
val eventLog = EventLog(
    params = JSONObject().apply {
        put("userId", "user_123")
        put("action", "record_created")
    },
    message = "New record has been created successfully."
)

logInterceptor.logEvent(eventLog)

```

### `RecordStatus` Enum

The `RecordStatus` enum represents the various stages of a record's synchronization lifecycle. It is used internally and externally to track and represent the current state of a record as it moves through the upload and sync pipeline.

### Enum Definition

```kotlin theme={null}
enum class RecordStatus(val status: Int) {
    NONE(0),
    WAITING_TO_UPLOAD(1),
    WAITING_FOR_NETWORK(2),
    SYNCING(3),
    SYNC_FAILED(4),
    SYNC_SUCCESS(5)
}
```

### Status Values

* **NONE (0)**

  Default or initial state.

* **WAITING\_TO\_UPLOAD (1)**

  The record is queued and waiting to begin the upload process.

* **WAITING\_FOR\_NETWORK (2)**

  The record is ready to upload but a required network connection is not yet available.

* **SYNCING (3)**

  The record is currently in the process of being synchronised with the server.

* **SYNC\_FAILED (4)**

  The record sync attempt has failed. Retry logic may apply based on implementation.

* **SYNC\_SUCCESS (5)**

  The record has been successfully synchronised with the server.

### Example Usage

```kotlin theme={null}
when (record.status) {
    RecordStatus.SYNC_SUCCESS -> Log.d("RecordStatus", "Record synced successfully.")
    RecordStatus.SYNC_FAILED -> Log.e("RecordStatus", "Record sync failed.")
    else -> Log.d("RecordStatus", "Current status: ${record.status}")
}
```

> Note: These statuses are especially useful for building UI indicators (e.g., progress spinners, error states) or managing sync retries in your application.

## Media Picker Integration

The `PhotoPickerHost` interface provides an abstraction layer for handling various media input actions such as taking a photo, picking an image, selecting a PDF, or scanning documents. Implementing this interface allows you to delegate these operations to your app's UI layer using activity or fragment launchers.

### `PhotoPickerHost` Interface

```kotlin theme={null}
interface PhotoPickerHost {
    fun takePhoto(cameraIntent: Intent, uri: Uri)
    fun pickPhoto(request: PickVisualMediaRequest)
    fun pickPdf()
    fun scanDocuments(request: IntentSenderRequest)
}
```

### Methods

* **takePhoto(cameraIntent: Intent, uri: Uri)**

  Launches the camera to take a photo and saves it to the specified `Uri`.

* **pickPhoto(request: PickVisualMediaRequest)**

  Opens a system picker for selecting an image from the device.

* **pickPdf()**

  Launches a file picker to select one or more PDF documents.

* **scanDocuments(request: IntentSenderRequest)**

  Starts a document scanning activity (e.g., using a third-party scanner or system service).

***

### Setting the Host

Before initiating any media actions, you must set a host implementation using `MediaPickerManager.setHost(...)`. This connects the SDK's internal media operations to your app's UI.

### Example

```kotlin theme={null}
MediaPickerManager.setHost(object : PhotoPickerHost {
    override fun takePhoto(intent: Intent, uri: Uri) {
        viewModel.updatePhotoUri(uri)
        cameraLauncher.launch(intent)
    }

    override fun pickPhoto(request: PickVisualMediaRequest) {
        mediaPickerLauncher.launch(request)
    }

    override fun pickPdf() {
        pdfPickerLauncher.launch(arrayOf("application/pdf"))
    }

    override fun scanDocuments(request: IntentSenderRequest) {
        scannerLauncher.launch(request)
    }
})
```

> Note: All launchers (cameraLauncher, mediaPickerLauncher, etc.) should be properly registered using the Activity Result API (registerForActivityResult(...)) in your Activity or Fragment.
