Skip to content

android-cpp/cpp-tui: add example of using new Ditto initializers in v4.12 #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
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
166 changes: 92 additions & 74 deletions android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "tasks_peer.h"
#include "tasks_log.h"
#include "join_string_values.h"
#include "tasks_log.h"
#include "transform_container.h"

#include "Ditto.h"
Expand Down Expand Up @@ -33,42 +33,70 @@ vector<string> tasks_json_from(const ditto::QueryResult &result) {
}

/// Initialize a Ditto instance.
unique_ptr<ditto::Ditto> init_ditto(JNIEnv *env,
jobject android_context,
string app_id,
string online_playground_token,
bool enable_cloud_sync,
string persistence_dir,
bool is_running_on_emulator,
string custom_url,
const string& websocket_url) {
shared_ptr<ditto::Ditto>
init_ditto(JNIEnv * /* env */, jobject android_context, string app_id,
string online_playground_token, bool enable_cloud_sync,
string persistence_dir, bool is_running_on_emulator,
string custom_url, const string &websocket_url) {
try {

// TODO UPDATE TO USE CUSTOM URL AND WEBSOCKET
// Docs: https://docs.ditto.live/sdk/latest/install-guides/cpp#importing-and-initializing-ditto

// Use the failable initializer with Ditto C++ SDK v4.12 or newer
#if (DITTO_VERSION_MAJOR >= 5) || \
(DITTO_VERSION_MAJOR == 4 && DITTO_VERSION_MINOR >= 12)
auto config = ditto::DittoConfig::default_config()
.set_database_id(std::move(app_id))
.set_persistence_directory(std::move(persistence_dir))
.set_server_connect(std::move(custom_url))
.set_android_context(android_context);
auto ditto = ditto::Ditto::open(config);

ditto->get_auth()->set_expiration_handler(
[login_token = std::move(online_playground_token)](
ditto::Ditto &ditto, uint32_t time_remaining) {
try {
log_debug("expiration handler called; time remaining = " +
std::to_string(time_remaining));
ditto.get_auth()->login(
login_token, ditto::Authenticator::get_development_provider(),
[&](unique_ptr<string> client_info,
unique_ptr<ditto::DittoError> err) {
if (err != nullptr) {
log_error("expiration handler: login error:" +
string(err->what()));
} else {
log_debug("expiration handler: login succeeded");
}
});
} catch (const exception &e) {
log_error("expiration handler: " + string(e.what()));
}
});
#else
// For older Ditto SDK versions, use the constructor.
const auto identity = ditto::Identity::OnlinePlayground(
std::move(app_id),
std::move(online_playground_token),
enable_cloud_sync, // This is required to be set to false to use the correct URLs
std::move(custom_url)
);
std::move(app_id), std::move(online_playground_token),
enable_cloud_sync, // This is required to be set to false to use the
// correct URLs
std::move(custom_url));

auto ditto =
make_unique<ditto::Ditto>(android_context, identity, std::move(persistence_dir));
auto ditto = make_shared<ditto::Ditto>(android_context, identity,
std::move(persistence_dir));
#endif

if (is_running_on_emulator) {
// Some transports don't work correctly on emulator, so disable them.
ditto->update_transport_config([websocket_url](ditto::TransportConfig &config) {
config.peer_to_peer.bluetooth_le.enabled = false;
config.peer_to_peer.wifi_aware.enabled = false;
config.connect.websocket_urls.insert(websocket_url);
});
ditto->update_transport_config(
[websocket_url](ditto::TransportConfig &config) {
config.peer_to_peer.bluetooth_le.enabled = false;
config.peer_to_peer.wifi_aware.enabled = false;
config.connect.websocket_urls.insert(websocket_url);
});
} else {
ditto->update_transport_config([websocket_url](ditto::TransportConfig &config) {
config.enable_all_peer_to_peer();
config.connect.websocket_urls.insert(websocket_url);
});
ditto->update_transport_config(
[websocket_url](ditto::TransportConfig &config) {
config.enable_all_peer_to_peer();
config.connect.websocket_urls.insert(websocket_url);
});
}

// Required for compatibility with DQL.
Expand All @@ -86,38 +114,25 @@ unique_ptr<ditto::Ditto> init_ditto(JNIEnv *env,
class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions)
private:
unique_ptr<mutex> mtx;
unique_ptr<ditto::Ditto> ditto;
shared_ptr<ditto::Ditto> ditto;
shared_ptr<ditto::SyncSubscription> tasks_subscription;

public:
Impl(JNIEnv *env,
jobject context,
string app_id,
string online_playground_token,
bool enable_cloud_sync,
string persistence_dir,
bool is_running_on_emulator,
string custom_auth_url,
const string& websocket_url)
Impl(JNIEnv *env, jobject context, string app_id,
string online_playground_token, bool enable_cloud_sync,
string persistence_dir, bool is_running_on_emulator,
string custom_auth_url, const string &websocket_url)
: mtx(new mutex()),
ditto(init_ditto(
env,
context,
std::move(app_id),
std::move(online_playground_token),
enable_cloud_sync,
std::move(persistence_dir),
is_running_on_emulator,
std::move(custom_auth_url),
websocket_url
)) {}
ditto(init_ditto(env, context, std::move(app_id),
std::move(online_playground_token), enable_cloud_sync,
std::move(persistence_dir), is_running_on_emulator,
std::move(custom_auth_url), websocket_url)) {}

~Impl() noexcept {
try {
stop_sync();
} catch (const exception &err) {
cerr << "Failed to destroy tasks peer instance: " +
string(err.what())
cerr << "Failed to destroy tasks peer instance: " + string(err.what())
<< endl;
}
}
Expand All @@ -129,7 +144,7 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions)

ditto->start_sync();
tasks_subscription =
ditto->sync().register_subscription("SELECT * FROM tasks");
ditto->get_sync().register_subscription("SELECT * FROM tasks");
}

void stop_sync() {
Expand All @@ -147,9 +162,7 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions)
string add_task(const string &title, bool done) {
try {
const json task_args = {
{"title", title},
{"done", done},
{"deleted", false}};
{"title", title}, {"done", done}, {"deleted", false}};
const auto command = "INSERT INTO tasks DOCUMENTS (:newTask)";
const auto result =
ditto->get_store().execute(command, {{"newTask", task_args}});
Expand Down Expand Up @@ -200,10 +213,10 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions)
" deleted = :deleted"
" WHERE _id = :id";
const auto result =
ditto->get_store().execute(stmt, {{"title", task.title},
{"done", task.done},
ditto->get_store().execute(stmt, {{"title", task.title},
{"done", task.done},
{"deleted", task.deleted},
{"id", task._id}});
{"id", task._id}});
if (result.mutated_document_ids().empty()) {
throw runtime_error("task not found with ID: " + task._id);
}
Expand All @@ -224,8 +237,7 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions)

const auto stmt = "UPDATE tasks SET done = :done WHERE _id = :id";
const auto result =
ditto->get_store().execute(stmt, {{"done", done},
{"id", task_id}});
ditto->get_store().execute(stmt, {{"done", done}, {"id", task_id}});
log_debug("Marked task " + task_id +
(done ? " complete" : " incomplete"));
} catch (const exception &err) {
Expand Down Expand Up @@ -255,8 +267,8 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions)
}
}

shared_ptr<ditto::StoreObserver> register_tasks_observer(
function<void(const vector<string> &)> callback) {
shared_ptr<ditto::StoreObserver>
register_tasks_observer(function<void(const vector<string> &)> callback) {
try {
const auto observer = ditto->get_store().register_observer(
"SELECT * FROM tasks WHERE NOT deleted ORDER BY _id",
Expand Down Expand Up @@ -290,13 +302,13 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions)
{"50191411-4C46-4940-8B72-5F8017A04FA7", "Buy groceries"},
{"6DA283DA-8CFE-4526-A6FA-D385089364E5", "Clean the kitchen"},
{"5303DDF8-0E72-4FEB-9E82-4B007E5797F0",
"Schedule dentist appointment"},
"Schedule dentist appointment"},
{"38411F1B-6B49-4346-90C3-0B16CE97E174", "Pay bills"}};

for (const auto &task: initial_tasks) {
const json task_args = {{"_id", task._id},
{"title", task.title},
{"done", task.done},
for (const auto &task : initial_tasks) {
const json task_args = {{"_id", task._id},
{"title", task.title},
{"done", task.done},
{"deleted", task.deleted}};
const auto command = "INSERT INTO tasks INITIAL DOCUMENTS (:newTask)";
ditto->get_store().execute(command, {{"newTask", task_args}});
Expand All @@ -319,12 +331,14 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions)
}
}; // class TasksPeer::Impl

TasksPeer::TasksPeer(JNIEnv *env, jobject context, string app_id, string online_playground_token,
bool enable_cloud_sync, string persistence_dir, bool is_running_on_emulator,
string custom_auth_url, const string& websocket_url)
: impl(make_unique<Impl>(env, context, std::move(app_id), std::move(online_playground_token),
enable_cloud_sync, std::move(persistence_dir),
is_running_on_emulator, std::move(custom_auth_url), std::move(websocket_url))) {}
TasksPeer::TasksPeer(JNIEnv *env, jobject context, string app_id,
string online_playground_token, bool enable_cloud_sync,
string persistence_dir, bool is_running_on_emulator,
string custom_auth_url, const string &websocket_url)
: impl(make_unique<Impl>(
env, context, std::move(app_id), std::move(online_playground_token),
enable_cloud_sync, std::move(persistence_dir), is_running_on_emulator,
std::move(custom_auth_url), std::move(websocket_url))) {}

TasksPeer::~TasksPeer() noexcept {
try {
Expand Down Expand Up @@ -368,3 +382,7 @@ string TasksPeer::get_ditto_sdk_version() {
}

void TasksPeer::insert_initial_tasks() { impl->insert_initial_tasks(); }

jobjectArray TasksPeer::missing_permissions_jni_array() const {
return impl->missing_permissions_jni_array();
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ class TasksPeer {
/// Add a set of initial documents to the tasks collection.
void insert_initial_tasks();

/// Get array of missing permissions
jobjectArray missing_permissions_jni_array() const;

private:
class Impl; // private implementation class ("pimpl pattern")
std::unique_ptr<Impl> impl;
Expand Down
20 changes: 20 additions & 0 deletions android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,23 @@ Java_live_ditto_quickstart_tasks_TasksLib_removeTasksObserver(JNIEnv *env, jobje
throw_java_exception(env, err.what());
}
}

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_live_ditto_quickstart_tasks_TasksLib_getMissingPermissions(JNIEnv *env, jobject thiz) {
__android_log_print(ANDROID_LOG_DEBUG, TAG,
"Java_live_ditto_quickstart_tasks_TasksLib_getMissingPermissions");
try {
std::lock_guard<std::recursive_mutex> lock(mtx);
if (!peer) {
// Return empty array if not initialized yet
jclass stringClass = env->FindClass("java/lang/String");
return env->NewObjectArray(0, stringClass, nullptr);
}
return peer->missing_permissions_jni_array();
} catch (const std::exception &err) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "getMissingPermissions failed: %s", err.what());
throw_java_exception(env, err.what());
return nullptr;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package live.ditto.quickstart.tasks
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import live.ditto.transports.DittoSyncPermissions

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -17,9 +16,13 @@ class MainActivity : ComponentActivity() {
}

private fun requestMissingPermissions() {
val missingPermissions = DittoSyncPermissions(this).missingPermissions()
if (missingPermissions.isNotEmpty()) {
this.requestPermissions(missingPermissions, 0)
// Check if TasksApplication has been initialized first
val app = application as? TasksApplication
if (app?.isInitialized() == true) {
val missingPermissions = TasksLib.getMissingPermissions()
if (missingPermissions.isNotEmpty()) {
this.requestPermissions(missingPermissions, 0)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ val isProbablyRunningOnEmulator: Boolean by lazy {
}

class TasksApplication : Application() {
private var dittoInitialized = false

companion object {
private const val TAG = "TasksApplication"
Expand All @@ -66,6 +67,10 @@ class TasksApplication : Application() {
init {
instance = this
}

fun isInitialized(): Boolean {
return dittoInitialized
}

override fun onCreate() {
super.onCreate()
Expand Down Expand Up @@ -94,6 +99,7 @@ class TasksApplication : Application() {
webSocketURL
)
TasksLib.startSync()
dittoInitialized = true
} catch (e: Exception) {
Log.e(TAG, "unable to initialize Ditto", e)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,8 @@ object TasksLib {

// Remove the tasks observer.
external fun removeTasksObserver()

// Get array of missing permissions
external fun getMissingPermissions(): Array<String>
}

23 changes: 8 additions & 15 deletions cpp-tui/taskscpp/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,12 @@ int main(int argc, const char *argv[]) {
opt_parse.count("online-playground-token") > 0
? opt_parse["online-playground-token"].as<string>()
: DITTO_PLAYGROUND_TOKEN;
const auto websocket_url =
opt_parse.count("websocket-url") > 0
? opt_parse["websocket-url"].as<string>()
: DITTO_WEBSOCKET_URL;
const auto auth_url =
opt_parse.count("auth-url") > 0
? opt_parse["auth-url"].as<string>()
: DITTO_AUTH_URL;
const auto websocket_url = opt_parse.count("websocket-url") > 0
? opt_parse["websocket-url"].as<string>()
: DITTO_WEBSOCKET_URL;
const auto auth_url = opt_parse.count("auth-url") > 0
? opt_parse["auth-url"].as<string>()
: DITTO_AUTH_URL;

const auto enable_cloud_sync = opt_parse.count("enable-cloud-sync") > 0;

Expand All @@ -209,13 +207,8 @@ int main(int argc, const char *argv[]) {

// The peer is destroyed at the end of this scope
{
TasksPeer peer(
app_id,
online_playground_token,
websocket_url,
auth_url,
enable_cloud_sync,
persistence_dir);
TasksPeer peer(app_id, online_playground_token, websocket_url, auth_url,
enable_cloud_sync, persistence_dir);
peer.insert_initial_tasks();
peer.start_sync();

Expand Down
Loading
Loading