Skip to content
Draft
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
12 changes: 6 additions & 6 deletions android-kotlin/QuickStartTasks/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ androidComponents {
buildConfigFields.forEach { (key, description) ->
it.buildConfigFields.put(
key,
BuildConfigField("String", "\"${prop[key]}\"", description)
BuildConfigField("String", "${prop[key]}", description)
)
}
}
Expand All @@ -61,7 +61,7 @@ android {

defaultConfig {
applicationId = "live.ditto.quickstart.tasks"
minSdk = 23
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
Expand All @@ -83,12 +83,12 @@ android {
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
}

buildFeatures {
Expand Down Expand Up @@ -132,7 +132,7 @@ dependencies {
implementation(libs.koin.androidx.compose.navigation)

// Ditto SDK
implementation(libs.live.ditto)
implementation(libs.com.ditto)

// Testing
testImplementation(libs.junit)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
package live.ditto.quickstart.tasks

import live.ditto.*
import com.ditto.kotlin.*

class DittoHandler {
companion object {
lateinit var ditto: Ditto
private set

fun initialize(config: DittoConfig) {
if (::ditto.isInitialized) {
throw IllegalStateException("Ditto is already initialized")
}
ditto = DittoFactory.create(config = config)
}

val isInitialized: Boolean
get() = ::ditto.isInitialized
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package live.ditto.quickstart.tasks
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import live.ditto.transports.DittoSyncPermissions
import com.ditto.kotlin.transports.DittoSyncPermissions
import android.os.StrictMode

class MainActivity : ComponentActivity() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import live.ditto.Ditto
import live.ditto.DittoIdentity
import live.ditto.android.DefaultAndroidDittoDependencies
import com.ditto.kotlin.Ditto
import com.ditto.kotlin.DittoAuthenticationProvider
import com.ditto.kotlin.DittoConfig
import com.ditto.kotlin.DittoConnection
import com.ditto.kotlin.DittoFactory
import com.ditto.kotlin.DittoLog
import com.ditto.kotlin.error.DittoException
import live.ditto.quickstart.tasks.DittoHandler.Companion.ditto

class TasksApplication : Application() {

// Create a CoroutineScope
// Use SupervisorJob so if one coroutine launched in this scope fails, it doesn't cancel the scope
//
// https://developer.android.com/kotlin/coroutines/coroutines-adv
// Dispatchers.IO - This dispatcher is optimized to perform disk or network I/O outside of the main thread.
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

// SDKS-1294: Don't create Ditto in a scope using Dispatchers.IO
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val tag = "TaskApplication"

companion object {
private var instance: TasksApplication? = null
Expand All @@ -35,43 +37,58 @@ class TasksApplication : Application() {

override fun onCreate() {
super.onCreate()
ioScope.launch {
setupDitto()

// Initialize Ditto synchronously - completes before UI loads
initializeDitto()

// Perform authentication asynchronously - can happen in background
scope.launch {
performAuthentication()
}
}

private suspend fun setupDitto() {
val androidDependencies = DefaultAndroidDittoDependencies(applicationContext)

//read values from build.gradle.kts (Module:app) which reads from environment file
val appId = BuildConfig.DITTO_APP_ID
val token = BuildConfig.DITTO_PLAYGROUND_TOKEN
val authUrl = BuildConfig.DITTO_AUTH_URL
val webSocketURL = BuildConfig.DITTO_WEBSOCKET_URL

val enableDittoCloudSync = false

/*
* Setup Ditto Identity
* https://docs.ditto.live/sdk/latest/install-guides/kotlin#integrating-and-initializing
*/
val identity = DittoIdentity.OnlinePlayground(
dependencies = androidDependencies,
appId = appId,
token = token,
customAuthUrl = authUrl,
enableDittoCloudSync = enableDittoCloudSync // This is required to be set to false to use the correct URLs
)

ditto = Ditto(androidDependencies, identity)
ditto.updateTransportConfig { config ->
// Set the Ditto Websocket URL
config.connect.websocketUrls.add(webSocketURL)
private fun initializeDitto() {
try {
val appId = BuildConfig.DITTO_APP_ID
val authUrl = BuildConfig.DITTO_AUTH_URL

val config = DittoConfig(
databaseId = appId,
connect = DittoConfig.Connect.Server(url = authUrl)
)

DittoHandler.initialize(config)
DittoLog.d(tag, "Ditto instance created successfully")

} catch (ex: Throwable) {
DittoLog.e(tag, "Failed to initialize Ditto: $ex")
ex.printStackTrace()
throw ex
}
}

private suspend fun performAuthentication() {
try {
val token = BuildConfig.DITTO_PLAYGROUND_TOKEN

ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = false")
DittoHandler.ditto.auth?.setExpirationHandler { ditto, _ ->
try {
val clientInfo = ditto.auth?.login(
token = token,
provider = DittoAuthenticationProvider.development()
)
DittoLog.d(tag, "Auth response: $clientInfo")
} catch (ex: Throwable) {
DittoLog.e(tag, "Authentication failed: $ex")
ex.printStackTrace()
}
}

// disable sync with v3 peers, required for DQL
ditto.disableSyncWithV3()
DittoLog.d(tag, "Ditto authentication setup complete")

} catch (ex: Throwable) {
DittoLog.e(tag, "Failed to setup authentication: $ex")
ex.printStackTrace()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import live.ditto.DittoError
import com.ditto.kotlin.error.DittoException
import com.ditto.kotlin.serialization.toDittoCbor
import live.ditto.quickstart.tasks.DittoHandler.Companion.ditto
import live.ditto.quickstart.tasks.data.Task

import com.ditto.kotlin.serialization.DittoCborSerializable
import com.ditto.kotlin.serialization.DittoCborSerializable.Utf8String

class EditScreenViewModel : ViewModel() {

companion object {
Expand All @@ -22,21 +26,25 @@ class EditScreenViewModel : ViewModel() {
var canDelete = MutableLiveData<Boolean>(false)

fun setupWithTask(id: String?) {
check(live.ditto.quickstart.tasks.DittoHandler.isInitialized) {
"Ditto must be initialized before ViewModels are created"
}

canDelete.postValue(id != null)
val taskId: String = id ?: return

viewModelScope.launch {
try {
val item = ditto.store.execute(
"SELECT * FROM tasks WHERE _id = :_id AND NOT deleted",
mapOf("_id" to taskId)
mapOf("_id" to taskId).toDittoCbor()
).items.first()

val task = Task.fromJson(item.jsonString())
_id = task._id
title.postValue(task.title)
done.postValue(task.done)
} catch (e: DittoError) {
} catch (e: DittoException) {
Log.e(TAG, "Unable to setup view task data", e)
}
}
Expand All @@ -45,23 +53,35 @@ class EditScreenViewModel : ViewModel() {
fun save() {
viewModelScope.launch {
try {
val titleValue = title.value ?: ""
val doneValue = done.value ?: false
if (_id == null) {
// Add tasks into the ditto collection using DQL INSERT statement
// https://docs.ditto.live/sdk/latest/crud/write#inserting-documents
val addMap = DittoCborSerializable.Dictionary(
mapOf(
Utf8String("title") to Utf8String(titleValue),
Utf8String("done") to DittoCborSerializable.BooleanValue(doneValue),
Utf8String("deleted") to DittoCborSerializable.BooleanValue(false)
)
)
ditto.store.execute(
"INSERT INTO tasks DOCUMENTS (:doc)",
mapOf(
"doc" to mapOf(
"title" to title.value,
"done" to done.value,
"deleted" to false
)
DittoCborSerializable.Dictionary(
mapOf(Utf8String("doc") to addMap)
)
)
} else {
// Update tasks into the ditto collection using DQL UPDATE statement
// https://docs.ditto.live/sdk/latest/crud/update#updating
_id?.let { id ->
val editMap = DittoCborSerializable.Dictionary(
mapOf(
Utf8String("title") to Utf8String(titleValue),
Utf8String("done") to DittoCborSerializable.BooleanValue(doneValue),
Utf8String("id") to DittoCborSerializable.Utf8String(id)
)
)
ditto.store.execute(
"""
UPDATE tasks
Expand All @@ -71,15 +91,11 @@ class EditScreenViewModel : ViewModel() {
WHERE _id = :id
AND NOT deleted
""",
mapOf(
"title" to title.value,
"done" to done.value,
"id" to id
)
arguments = editMap
)
}
}
} catch (e: DittoError) {
} catch (e: Throwable) {
Log.e(TAG, "Unable to save task", e)
}
}
Expand All @@ -93,10 +109,14 @@ class EditScreenViewModel : ViewModel() {
_id?.let { id ->
ditto.store.execute(
"UPDATE tasks SET deleted = true WHERE _id = :id",
mapOf("id" to id)
DittoCborSerializable.Dictionary(
mapOf(
Utf8String("id") to Utf8String(id)
)
)
)
}
} catch (e: DittoError) {
} catch (e: Throwable) {
Log.e(TAG, "Unable to set deleted=true", e)
}
}
Expand Down
Loading
Loading