Skip to content

Repository files navigation

DittoAndroidTools

DittoAndroidTools are diagnostic tools for Ditto. You can view connected peers, export debug logs, browse collections/documents and see Ditto's disk usage/ export this data.

These tools are available via Maven Central.

Important

This repository is a read-only mirror maintained by Ditto's SDK release process. Pull requests opened outside that process are not accepted. To report a problem or request a change, contact Ditto Support (support@ditto.com).

Requirements

  • Ditto Kotlin SDK 5.0.1 or a later 5.x release (com.ditto:ditto-kotlin)
  • Jetpack Compose
  • Package minimum: Android 7.0+ (API 24)

DittoToolsViewer and the demo app require Android 8.0+ (API 26).

Installing

Ditto tools are released via Maven Central. Be sure to include it in your list of repositories.

repositories {
    mavenCentral()
}

Include the tools repository:

dependencies {
    implementation 'com.ditto:ditto-tools-android:LIBRARY_VERSION'
}

You can find the list of versions and release notes in the Releases tab.

Note: The tools used to be released as individual Maven artefacts but have now been moved into a single module, and are released as such.

Usage

All tools require an initialized instance of Ditto to work. For example, with a server connection:

import com.ditto.kotlin.Ditto
import com.ditto.kotlin.DittoAuthenticationProvider
import com.ditto.kotlin.DittoConfig
import com.ditto.kotlin.DittoFactory
import com.ditto.kotlin.DittoLogLevel
import com.ditto.kotlin.DittoLogger

DittoLogger.minimumLogLevel = DittoLogLevel.Debug

ditto = DittoFactory.create(
    DittoConfig(
        databaseId = "YOUR_APP_ID",
        connect = DittoConfig.Connect.Server("YOUR_AUTH_URL"),
    )
).apply {
    auth?.expirationHandler = { dittoInstance, _ ->
        dittoInstance.auth?.login(
            token = "YOUR_TOKEN",
            provider = DittoAuthenticationProvider.development(),
        )
    }
    sync.start()
}

1. Tools Viewer

Tools viewer is the easiest way to integrate all the tools currently available. It provides a single entry point to interact with all other tools, and includes them as a dependency.

It is available as a Composable element that requires a Ditto instance. Optional parameters include:

  • modifier: If you need to adjust the layout
  • onExitTools: Lambda function that will be called when the "Exit Tools" button is tapped. Use this to do any back navigation or dismissal of the tools composable if you need to.
  • onExport: Lambda function that receives the exported file when using the Disk Usage export feature. Useful for custom export handling on locked-down devices. Using the onExport parameter overrides the default file picker.

Example code:

import com.ditto.tools.toolsviewer.DittoToolsViewer

// minimum code required to get started
DittoToolsViewer(
    ditto = YOUR_DITTO_INSTANCE
)

// with custom export callback
DittoToolsViewer(
    ditto = YOUR_DITTO_INSTANCE,
    // Using the onExport parameter overrides the default file picker
    onExport = { file ->
        // Handle the exported file (e.g., upload to server)
        uploadToServer(file)
    }
)

To integrate it in a Views-based app - see instructions here: https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/compose-in-views

2. Presence Viewer

The Presence Viewer displays a mesh graph that allows you to see all connected peers within the mesh and the transport that each peer is using to make a connection.

Within a Composable, you pass ditto to the constructor:

DittoPresenceViewer(ditto = ditto)

Presence Viewer Image

3. Data Browser

The Ditto Data Browser allows you to view all your collections, up to 1,000 local documents within each collection, and the properties and values of a document. With the Data Browser, you can observe local changes to your collections and documents in real time.

The Data Browser only observes data already present in the local store. It never registers sync subscriptions or changes what the application syncs. The application embedding the tool remains responsible for its subscriptions. This prevents a diagnostic view from unexpectedly downloading every document in a collection.

The document query field accepts a DQL WHERE expression, such as color = 'blue'. Clear the field and run the query again to show the collection without a filter. See the DQL documentation for query syntax.

Within a Composable function, you pass ditto to the constructor:

DittoDataBrowser(ditto = ditto)

4. Export Logs

Export Logs allows you to export logs from your application into a file.

Include ExportLogs() in your Composable function. You can pass in a lambda function to be called when the dialog is dismissed.

ExportLogs(onDismiss: () -> Unit)

Export Logs Image

5. Export Logs to Portal

Export Logs to Portal allows you to export logs from your application into the Ditto Portal.

Include ExportLogsToPortal() in your Composable function. You can pass in a lambda function to be called when the dialog is dismissed and a ditto object.

ExportLogsToPortal(ditto: Ditto, onDismiss: () -> Unit)

You'll also be able to use a new public API found at DittoTools.uploadLogsToPortal(ditto: Ditto) that takes a ditto object which will allow you to upload logs from anywhere in your app.

Export Logs Image

6. Disk Usage/ Export Data

Disk Usage allows you to see Ditto's file space usage. Export Data allows you to export the Ditto directory.

DittoDiskUsage(ditto = ditto)

// with custom export callback
DittoDiskUsage(
    ditto = ditto,
    // Using the onExport parameter overrides the default file picker
    onExport = { file ->
        // Handle the exported file (e.g., upload to server)
        uploadToServer(file)
    }
)

The onExport callback allows you to provide custom export logic instead of using the default Android file picker. This is particularly useful for locked-down devices where file picker access may be restricted.

Disk Usage Image

7. Health

Health allows you to see the status of Ditto's services.

Example: WiFi/Bluetooth state/permissions, device capabilities

The default implementation is a Composable that displays all facets of information.

HealthScreen()

This Composable also takes in an optional list of enum's if you need to show/hide certain groups of information. Current valid enums are:

TRANSPORT_HEALTH -- shows WiFi/Bluetooth status (enabled/disabled, permissions state) 
WIFI_AWARE_STATE -- displays whether the device supports WiFi Aware

Health

8. Heartbeat

The Ditto Heartbeat tool allows you to monitor, locally or remotely, the peers in your mesh.

Configure Heartbeat

These are the values you need to provide to the Heartbeat:

  1. id - Unique value that identifies the device
  2. secondsInterval - The frequency at which the Heartbeat will scrape the data
  3. metaData - Optional - any metadata you wish to add to the Heartbeat
  4. healthMetricProviders List of HealthMetricProviders
  5. publishToDittoCollection - Optional - set to false to prevent from publishing the heartbeat to Ditto collection. Default true.

Available healthMetricProviders:

  1. HealthViewModel() - health metrics for Permissions Health Tool
  2. DiskUsageViewModel() - health metrics for Ditto disk usage. isHealthy is determined by the size of the ditto_store and ditto_replication folders. The default isHealthy size is 2GB, but this can be configured.

There is a DittoHeartbeatConfig data class you can use to construct your configuration.

// Provided with the Heartbeat tool
data class DittoHeartbeatConfig(
    val id: String,
    val secondsInterval: Int,
    val metaData: Map<String, Any>? = null,
    val healthMetricProviders: List<HealthMetricProvider>?,
    val publishToDittoCollection: Boolean = true // Toggle to avoid publishing
)

// Example:
// User defines the values here
// Passed into Heartbeat tool
var healthMetricProviders: MutableList<HealthMetricProvider> = mutableListOf()
val diskUsageViewModel = DiskUsageViewModel()
diskUsageViewModel.isHealthyMBSizeLimit = 2048 //2GB worth of data
healthMetricProviders.add(diskUsageViewModel)
val config = DittoHeartbeatConfig(
    id = <unique device id>,
    secondsInterval = 30, //seconds
    metaData = mapOf(
        "deviceType" to "KDS"
    ),
    healthMetricProviders = healthMetricProviders,
    publishToDittoCollection = true
)

// Provide the config and your Ditto instance to startHearbeat()
startHeartbeat(ditto, config).collect { heartbeatInfo = it }

User Interface

You will need to provide your own UI around the values emitted by startHeartbeat().

There are two ways you can access the data:

  1. The Ditto collection you provided
  2. startHeartBeat() provides a callback with the data

Ditto Collection:

This is the model of the data and what you can use for reference

{
    _id: <ditto peerKey>,
    _schema: String,
    secondsInterval: String,
    presenceSnapshotDirectlyConnectedPeersCount: Int,
    lastUpdated: String (ISO-8601),
    sdk: String,
    presenceSnapshotDirectlyConnectedPeers: {
        <peerKey>: {
            deviceName: String,
            sdk: String,
            isConnectedToDittoCloud: Bool,
            bluetooth: Int,
            p2pWifi: Int,
            lan: Int,
            connectionTypes: {
                <SDK connection type>: Int,
                …
            },
        },
        <peerKey>…,
        …
    },
    metaData: {},
    healthMetrics: {},
}

connectionTypes maps every observed SDK connection-type string to its count. New code should use this map for connection-type counts. The bluetooth, p2pWifi, and lan keys are legacy summary fields retained in heartbeat documents for compatibility. The SDK enum names LAN connections AccessPoint, so that enum case supplies lan. WebSocket, Multicast, and other connection types are reported by their SDK names in connectionTypes.

Wire compatibility is additive: tools released before connectionTypes ignore that extra field and continue reading the fixed summary fields. Current tools also accept older documents without connectionTypes, treating the missing map as empty. For those documents, the legacy summaries are the only available breakdown. The lan summary means AccessPoint only. Earlier GA tools also included WebSocket in lan, so documents written by those versions can report a larger LAN count. In current documents, WebSocket has its own exact connectionTypes entry.

The isConnectedToDittoCloud field reports whether the peer is connected to Ditto Server. SDK v4 exposed this connection state as isConnectedToDittoCloud; SDK v5 renamed the SDK property to isConnectedToDittoServer. The heartbeat document schema retains the original key so independently versioned tools can exchange and read the same documents.

Callback:

You will receive a HeartbeatInfo data class back

data class DittoHeartbeatInfo(
    val id: String,
    val schema: String,
    val lastUpdated: String,
    val metaData: Map<String, Any>?,
    val secondsInterval: Int,
    val presenceSnapshotDirectlyConnectedPeersCount: Int,
    val presenceSnapshotDirectlyConnectedPeers: Map<String, Any>,
    val sdk: String,
    val peerKey: String,
    var healthMetrics: MutableMap<String, HealthMetric> = mutableMapOf()

)

9. Presence Degradation Reporter

Tracks the status of your mesh, allowing to define the minimum of required peers that needs to be connected. Exposes an API to notify when the condition of minimum required peers is not met.

The SDK enum names LAN connections AccessPoint, so the reporter's LAN count uses that enum case. WebSocket is a separate transport.

UI Composable

PresenceDegradationReporterScreen(ditto = ditto)

API

ditto.presenceDegradationReporterFlow().collect { state: PresenceDegradationReporterApiState ->
    // state.settings
    // state.localPeer
    // state.remotePeers
}

Health

10. Log File Viewer

The Log File Viewer provides a comprehensive interface for viewing and analyzing Ditto log files directly within your application. It includes two main screens:

Log Details Screen - Displays log configuration settings and directory information:

  • Max file age, size, and files on disk settings
  • Total log directory size
  • Log file count
  • Current log file error count

Log File Screen - View and analyze log file contents with:

  • Real-time log tailing (continuously updates as new logs are written)
  • Search and filter capabilities by log level (DEBUG, INFO, WARN, ERROR)
  • Expandable log entries for detailed inspection
  • Reverse chronological order option

UI Composables

Log Details Screen:

LogDetailsScreen(
    ditto = ditto,
    onButtonClick = { /* Navigate to log file screen */ }
)

Log File Screen:

LogFileScreen(
    ditto = ditto
)

Features

  • Real-time Tailing: Automatically updates with new log entries as they're written
  • Search: Full-text search across all log entries
  • Filter by Level: Show only specific log levels (DEBUG, INFO, WARN, ERROR)
  • Expandable Entries: Tap to expand and see full log details
  • Reverse Order: Toggle chronological vs reverse chronological order
  • Color-coded: Log levels are color-coded for easy identification
    • DEBUG: Blue
    • INFO: Green
    • WARN: Orange
    • ERROR: Red

Integration

The Log File Viewer is available in the Data/Debugging section when using DittoToolsViewer. It can be accessed directly through the tools menu or embedded as standalone composables:

As Standalone Screens:

// In your navigation graph
composable("logDetails") {
    LogDetailsScreen(
        onButtonClick = { navController.navigate("logFile") },
        ditto = ditto
    )
}

composable("logFile") {
    LogFileScreen(ditto = ditto)
}

//Or if  only interested in LogFileScreen can use as standalone
LogFileScreen(ditto = ditto)
Log Details Log Viewer Log Tails

Shrinking the app size then not using all tools

If you are not using all the tools, you can use the built-in R8 shrinker to remove unused code. This will reduce the size of the app. You will need to configure Proguard to ensure the underlying Ditto SDK is not removed.

# proguard-rules.pro

# --- Ditto SDK rules ---
# Selective package definition will allow shrinking of all code in com.ditto.tools and its subpackages.
-keep class com.ditto.kotlin.** { *; }
# --- End Ditto SDK rules ---

# --- Ditto Tools names ---
# The following can be removed to obfuscate tools code further.
-keepnames class com.ditto.tools.** { *; }
# --- End Ditto Tools names ---

Testing Changes Locally

There are two ways you can test things locally. Either in the demo app, or in an external project.

Testing in the Demo App Locally

To run the demo app locally, copy .env.sample to .env at the repo root and fill in credentials from the Ditto Portal:

cp .env.sample .env

Required keys: DITTO_APP_ID, DITTO_PLAYGROUND_TOKEN, DITTO_AUTH_URL. The app throws at launch if any are empty.

To test your changes to a module in the demo app, make sure to import the local module in app/build.gradle dependencies section:

add: implementation(project(":DittoToolsAndroid")) remove or comment out the com.ditto:ditto-tools-android Maven dependency

Testing in an External Project

  1. Run ./gradlew publishToMavenLocal
  2. In your external project add the mavenLocal() entry to your list of repository sources
  3. When importing a tool, the version will be SNAPSHOT

License

MIT

About

Diagnostic and Debugging Tools for Ditto Android SDK

Resources

Stars

7 stars

Watchers

21 watching

Forks

Releases

Packages

Used by

Contributors

Languages