To build and explore through a basic to-do task app, confirm that you meet the minimum requirements and then do the following:

1

Create a new project in Android Studio. (Creating Your Project)

2

Install and set up Ditto. (Installing Ditto SDK)

3

Add UI extensions. (Adding UI Extensions)

4

Create UI layouts. (Creating UI Layouts)

5

Configure your app MainActivity. (Configuring Main Activity)

6

Integrate Ditto and set up logic for peer-to-peer sync. (Integrating Ditto and Setting Up Sync)

7

Perform your first sync. (Build and Run)

Prerequisites

Before you begin, you’ll need:

  • Android Studio version 6.0 (Marshmallow) or higher
  • minSdk version 23.0
  • compileSdk version 33.0 or higher
  • Java Development Kit (JDK) version 11.0
  • Sync Credentials

Step 1: Creating Your Project

To begin this tutorial, go to Android Studio and create a new project:

1

Click File > New > Basic Views Activity > Next.

2

From the **New Project **form that appears:

  1. For Name, type “Tasks”.
  2. For Package name, type “live.ditto.tasks”.
  3. If desired, change the Save location.
  4. For Minimum SDK, select API 26: (“Oreo”; Android 8.0).
  5. Click Finish.

3

If the following template files display in the left sidebar, remove each by right-clicking and selecting **Delete **from the menu:

  • FirstFragment.kt
  • SecondFragment.kt
  • fragment_first.xml
  • fragment_second.xml
  • nav_graph.xml

Step 2: Installing Ditto SDK

To install Ditto in your project, add the Ditto SDK as a dependency:

1

From the module-level build.gradle file, add the following line within the dependencies block.

Make sure to replace “DITTO_VERSION” with the version number of the Ditto SDK you want to use with your app. For example, implementation ("live.ditto:ditto:4.5.0").

For the version number of the latest Ditto SDK release, see the Directory of Release Notes and select the release notes for your language.

codeblocktabs
implementation ("live.ditto:ditto:DITTO_VERSION_HERE")
2

Sync your Gradle files by doing either of the following:

  1. Click File > Sync Project with Gradle Files.
  2. From the upper-right corner of Android Studio, click the elephant icon.
1

This tutorial uses the Kotlin Synthetics* *plugin for view binding. View binding is an Android feature for streamlining the development workflow when handling views in a UI layout.

For more information, see Android’s official View binding documentation.

plugins {
// ...
id("android.extensions")
}

Step 3: Adding UI Extensions

This tutorial uses the Kotlin Synthetics* *plugin for view binding. View binding is an Android feature for streamlining the development workflow when handling views in a UI layout.

For more information, see Android’s official View binding documentation.  

codeblocktabs

plugins {
    // ...
    id("android.extensions")
}

Step 4: Creating UI Layouts

Set up the interface for your Tasks app:

1

Adjust your default layout files by setting up a toolbar and button for adding new tasks. (Modifying Existing Layouts)

2

Create a new layout file for an alert dialog box for adding new tasks. (Creating AlertDialog Layout)

3

Create the Kotlin class file to handle the alert dialog box. (Creating DialogFragment Kotlin Class

Modifying Existing Layouts​

Customize how your app’s content displays to the end user by adjusting the appropriate layout resource files so that the floating action button uses a white icon for adding tasks and you use a RecycleView for displaying a list of tasks.

To modify the existing layout, from the res > layout directory:

1

Open activity_main.xml file and replace the existing XML with the following:

content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2

Open the content_main.xml file and replace the existing XML with the following:

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.Tasks.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/Theme.Tasks.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>

    <include layout="@layout/content_main" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/addTaskButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:tint="#FFFFF0"
        app:srcCompat="@android:drawable/ic_input_add" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
3

From Design view, confirm your layout appears as follows:

Creating AlertDialog Layout​

Add an editable text input element to allow your end user to enter a task:

1

From the New Resource File dialog box:

  1. For File name, type “dialog_new_task”.
  2. For Root element, make sure LinearLayout is selected.

2

Open the new dialog_new_task.xml layout file in Code view, and then replace the existing XML with the following:

XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:inputType="text" />
</LinearLayout>

3

Click Design from the upper-right corner of Android Studio and confirm that your layout appears as follows:

4

Set string constants for the alert dialog box by opening the res > values > strings.xml file and replacing the existing XML with the following:

<resources>
    <string name="app_name">Tasks</string>
    <string name="action_settings">Settings</string>
    <string name="title_activity_main">Tasks</string>
    <string name="add_new_task_dialog_title">Add New Task</string>
    <string name="save">Save</string>
</resources>

Creating DialogFragment​​ Kotlin Class

Now that you’ve created the AlertDialog layout element, create a new Kotlin class file to encapsulate the logic for the dialog’s behavior and appearance:

1

From the Project Source Files in the upper-left corner of Android Studio, right-click the java folder located in the app > src > main directory.

2

From the New Kotlin Class/File dialog box:

  1. For Name, type “NewTaskDialogFragment”.
  2. Double-click Class:
3

From the NewTaskDialogFragment file, replace the existing contents with the following live.ditto.tasks package:

NewTaskDialogFragment.kt
package live.ditto.tasks

import android.app.Activity
import android.app.AlertDialog
import android.app.Dialog
import android.os.Bundle
import android.widget.TextView
import androidx.fragment.app.DialogFragment

class NewTaskDialogFragment: DialogFragment() {

    interface NewTaskDialogListener {
        fun onDialogSave(dialog: DialogFragment, task: String)
        fun onDialogCancel(dialog: DialogFragment)
    }

    var newTaskDialogListener: NewTaskDialogListener? = null

    companion object {
        fun newInstance(title: Int): NewTaskDialogFragment {
            val newTaskDialogFragment = NewTaskDialogFragment()
            val args = Bundle()
            args.putInt("dialog_title", title)
            newTaskDialogFragment.arguments = args
            return newTaskDialogFragment
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { // 5
        val title = arguments!!.getInt("dialog_title")
        val builder = AlertDialog.Builder(activity)
        builder.setTitle(title)

        val dialogView = activity!!.layoutInflater.inflate(R.layout.dialog_new_task, null)
        val task = dialogView.findViewById<TextView>(R.id.editText)

        builder.setView(dialogView)
            .setPositiveButton(R.string.save) { _, _ -> newTaskDialogListener?.onDialogSave(this, task.text.toString()) }
            .setNegativeButton(android.R.string.cancel) { _, _ -> newTaskDialogListener?.onDialogCancel(this) }
        return builder.create()
    }

    @Suppress("DEPRECATION")
    override fun onAttach(activity: Activity) { // 6
        super.onAttach(activity)
        try {
            newTaskDialogListener = activity as NewTaskDialogListener
        } catch (e: ClassCastException) {
            throw ClassCastException("$activity must implement NewTaskDialogListener")
        }
    }
}

Step 5: Configuring Main Activity

Set up the main activity of your app:

1

Import Ditto, create a few variables and then create the functions for handling events related to the alert dialog box. (Setting Up Ditto, Variables, and Functions)

2

Create a new layout resource file for the checkbox that displays the task in each row. (Setting Up task_view Layout)

3

Configure the RecyclerView to display tasks and add the logic for end-user actions. (Setting Up RecyclerViewer Layout

4

Create a separate class for handling swipe-to-delete functionality. (Adding Swipe-to-Delete Functionality)

Setting Up Ditto, Variables, and Functions

From the MainActivity file located in the app/src/main/java/live/ditto/tasks directory:

1

Replace the existing contents with the following:

MainActivity
package live.ditto.tasks

import android.os.Bundle
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import androidx.recyclerview.widget.RecyclerView
import androidx.fragment.app.DialogFragment
import java.time.Instant

import kotlinx.android.synthetic.main.activity_main.*

import live.ditto.*
import live.ditto.android.DefaultAndroidDittoDependencies


class MainActivity : AppCompatActivity(), NewTaskDialogFragment.NewTaskDialogListener {
    private lateinit var recyclerView: RecyclerView
    private lateinit var viewAdapter: RecyclerView.Adapter<*>
    private lateinit var viewManager: RecyclerView.LayoutManager

    private var ditto: Ditto? = null
    private var collection: DittoCollection? = null
    private var liveQuery: DittoLiveQuery? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
    }
}

2

After the onCreate() function:

MainActivity
override fun onDialogSave(dialog: DialogFragment, task:String) {
    // Add the task to Ditto
    this.collection!!.upsert(mapOf("body" to task, "isCompleted" to false))
}

override fun onDialogCancel(dialog: DialogFragment) { }

fun showNewTaskUI() {
    val newFragment = NewTaskDialogFragment.newInstance(R.string.add_new_task_dialog_title)
    newFragment.show(supportFragmentManager,"newTask")
}
3

Sync your Gradle files by doing either of the following:

  1. Click File > Sync Project with Gradle Files.
  2. From the upper-right corner of Android Studio, click the elephant icon.

Setting Up task_view Layout​

Add a text view and checkbox for displaying the task in each row of the RecyclerView:

1

Create a new layout file by right-clicking the layout folder, and then clicking New > Layout Resource File.

2

From the New Resource File dialog box:

  1. For File name, type “task_view”.
  2. For the Root element, make sure LinearLayout is selected.
3

Open the new task_view.xml layout file in Code view, and then replace the existing XML with the following:

task_view.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/taskTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="TextView"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/taskCheckBox"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <CheckBox
        android:id="@+id/taskCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:backgroundTint="#FFFFFF"
        android:clickable="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/taskTextView"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

4

Sync your Gradle files by doing either of the following:

  1. Click File > Sync Project with Gradle Files.
  2. From the upper-right corner of Android Studio, click the elephant icon.
5

Click Design from the upper right corner of Android Studio and confirm that your layout appears as follows:

Setting Up RecyclerViewer Layout

From MainActivity, configure the recycler viewer to efficiently display a dynamic and scrollable list of tasks:

1

Replace the onCreate() function with the following:

MainActivity
override fun onDialogSave(dialog: DialogFragment, task:String) {
    // Add the task to Ditto
    this.collection!!.upsert(mapOf("body" to task, "isCompleted" to false))
}

override fun onDialogCancel(dialog: DialogFragment) { }

fun showNewTaskUI() {
    val newFragment = NewTaskDialogFragment.newInstance(R.string.add_new_task_dialog_title)
    newFragment.show(supportFragmentManager,"newTask")
}
2

Declare a RecyclerView.Adapter as an inner class of MainActivity:

MainActivity
class TasksAdapter: RecyclerView.Adapter<TasksAdapter.TaskViewHolder>() {
    private val tasks = mutableListOf<DittoDocument>()

    var onItemClick: ((DittoDocument) -> Unit)? = null

    class TaskViewHolder(v: View): RecyclerView.ViewHolder(v)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.task_view, parent, false)
        return TaskViewHolder(view)
    }

    override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
        val task = tasks[position]
        holder.itemView.taskTextView.text = task["body"].stringValue
        holder.itemView.taskCheckBox.isChecked = task["isCompleted"].booleanValue
        holder.itemView.setOnClickListener {
            // NOTE: Cannot use position as this is not accurate based on async updates
            onItemClick?.invoke(tasks[holder.adapterPosition])
        }
    }

    override fun getItemCount() = this.tasks.size

    fun tasks(): List<DittoDocument> {
        return this.tasks.toList()
    }

    fun set(tasks: List<DittoDocument>): Int {
        this.tasks.clear()
        this.tasks.addAll(tasks)
        return this.tasks.size
    }

    fun inserts(indexes: List<Int>): Int {
        for (index in indexes) {
            this.notifyItemRangeInserted(index, 1)
        }
        return this.tasks.size
    }

    fun deletes(indexes: List<Int>): Int {
        for (index in indexes) {
            this.notifyItemRangeRemoved(index, 1)
        }
        return this.tasks.size
    }

    fun updates(indexes: List<Int>): Int {
        for (index in indexes) {
            this.notifyItemRangeChanged(index, 1)
        }
        return this.tasks.size
    }

    fun moves(moves: List<DittoLiveQueryMove>) {
        for (move in moves) {
            this.notifyItemMoved(move.from, move.to)
        }
    }

    fun setInitial(tasks: List<DittoDocument>): Int {
        this.tasks.addAll(tasks)
        this.notifyDataSetChanged()
        return this.tasks.size
    }
}

Adding Swipe-to-Delete Functionality​

To match the iOS getting-started app, add swipe-to-delete functionality by inserting the following as a new class object at the bottom of the MainActivity enclosure:

MainActivity
// Swipe to delete based on https://medium.com/@kitek/recyclerview-swipe-to-delete-easier-than-you-thought-cff67ff5e5f6
abstract class SwipeToDeleteCallback(context: Context) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {

    private val deleteIcon = ContextCompat.getDrawable(context, android.R.drawable.ic_menu_delete)
    private val intrinsicWidth = deleteIcon!!.intrinsicWidth
    private val intrinsicHeight = deleteIcon!!.intrinsicHeight
    private val background = ColorDrawable()
    private val backgroundColor = Color.parseColor("#f44336")
    private val clearPaint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }


    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        return false
    }

    override fun onChildDraw(
        c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
        dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean
    ) {

        val itemView = viewHolder.itemView
        val itemHeight = itemView.bottom - itemView.top
        val isCanceled = dX == 0f && !isCurrentlyActive

        if (isCanceled) {
            clearCanvas(c, itemView.right + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat())
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
            return
        }

        // Draw the red delete background
        background.color = backgroundColor
        background.setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom)
        background.draw(c)

        // Calculate position of delete icon
        val deleteIconTop = itemView.top + (itemHeight - intrinsicHeight) / 2
        val deleteIconMargin = (itemHeight - intrinsicHeight) / 2
        val deleteIconLeft = itemView.right - deleteIconMargin - intrinsicWidth
        val deleteIconRight = itemView.right - deleteIconMargin
        val deleteIconBottom = deleteIconTop + intrinsicHeight

        // Draw the delete icon
        deleteIcon!!.setBounds(deleteIconLeft, deleteIconTop, deleteIconRight, deleteIconBottom)
        deleteIcon.setTint(Color.parseColor("#ffffff"))
        deleteIcon.draw(c)

        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
    }

    private fun clearCanvas(c: Canvas?, left: Float, top: Float, right: Float, bottom: Float) {
        c?.drawRect(left, top, right, bottom, clearPaint)
    }
}

Step 6: Integrating Ditto and Setting Up Sync

To finish creating the Task app, integrate Ditto:

1

Create the task app in the Ditto portal and get your sync access credentials. (Sync Credentials)

2

Instantiate the ditto object you’ll use in your app to interact with the platform. (Setting Up Store Observer Query)

3

Verify configurations for location permissions. (Checking for Location Permissions)

4

Confirm imports. (Ensuring Imports)

Initializing Ditto

Add handlers for the swipe-to-delete functionality and event listening for the row clicks that mark a task as completed (or in-completed) and provide your access credentials for one-time authentication with the Big Peer by doing the following within MainActivity:

1

Replace the existing onCreate() function with the following:

MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar)

    // Setup the layout
    viewManager = LinearLayoutManager(this)
    val tasksAdapter = TasksAdapter()
    viewAdapter = tasksAdapter

    recyclerView = findViewById<RecyclerView>(R.id.recyclerView).apply {
        setHasFixedSize(true)
        layoutManager = viewManager
        adapter = viewAdapter
    }

    recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))

    // Create an instance of Ditto
    val androidDependencies = DefaultAndroidDittoDependencies(applicationContext)
    val ditto = Ditto(androidDependencies, DittoIdentity.OnlinePlayground(androidDependencies, "REPLACE_WITH_YOUR_APP_ID", "REPLACE_WITH_TOKEN"))
    this.ditto = ditto

    // This starts Ditto's background synchronization
    ditto.startSync()

    // We will create a long-running live query to keep the database up-to-date
    this.collection = this.ditto!!.store.collection("tasks")
    this.subscription = this.collection!!.find("!isDeleted").subscribe()

    // Add swipe to delete
    val swipeHandler = object : SwipeToDeleteCallback(this) {
        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
            val adapter = recyclerView.adapter as TasksAdapter
            // Retrieve the task at the row swiped
            val task = adapter.tasks()[viewHolder.adapterPosition]
            // Delete the task from Ditto
            ditto.store.collection("tasks").findByID(task.id).update { doc ->
            doc!!["isDeleted"].set(true)
            }
        }
    }

    // Configure the RecyclerView for swipe to delete
    val itemTouchHelper = ItemTouchHelper(swipeHandler)
    itemTouchHelper.attachToRecyclerView(recyclerView)

    // Respond to new task button click
    addTaskButton.setOnClickListener { _ ->
        showNewTaskUI()
    }

    // Listen for clicks to mark tasks [in]complete
    tasksAdapter.onItemClick = { task ->
        ditto.store.collection("tasks").findById(task.id).update { newTask ->
            newTask!!["isCompleted"].set(!newTask["isCompleted"].booleanValue)
        }
    }

    // This function will create a "live-query" that will update
    // our RecyclerView
    setupTaskList()

    // This will check if the app has permissions
    // to fully enable Bluetooth
    checkPermissions()
}
2

Provide the Sync Credentials generated when you created your app in the portal:

If using the OnlinePlayground identity, obtain your sync credentials from the portal.

To use the OfflinePlayground identity, request an offline token from the portal.

  1. For appID, replace the string value with your app ID.
  2. For token, replace the string value with your playground token.

Setting Up Store Observer Query

Finally, set up a sync subscription for the desired query and create a store observer for handling updates by adding the following within MainActivity:

Use Ditto’s store observer along with sync subscriptions to react dynamically to data changes. This best practice ensures a realtime end-user experience, allowing your UI to stay in sync with the underlying data as updates occur.

MainActivity
fun setupTaskList() {
    // We use observeLocal to create a live query with a subscription to sync this query with other devices
    this.liveQuery = collection!!.find("!isDeleted").observeLocal { docs, event ->
        val adapter = (this.viewAdapter as TasksAdapter)
        when (event) {
            is DittoLiveQueryEvent.Update -> {
                runOnUiThread {
                    adapter.set(docs)
                    adapter.inserts(event.insertions)
                    adapter.deletes(event.deletions)
                    adapter.updates(event.updates)
                    adapter.moves(event.moves)
                }
            }
            is DittoLiveQueryEvent.Initial -> {
                runOnUiThread {
                    adapter.setInitial(docs)
                }
            }
        }
    }

    ditto!!.store.collection("tasks").find("isDeleted == true").evict()
}

Checking For Location Permissions

Since Bluetooth Low Energy (LE) can be involved with location tracking, Android requires you to request location permissions (since Bluetooth can be involved with location tracking). Insert this function in MainActivity:

For more information about Android’s protection of end-user privacy, see the official Request location permissions documentation.

MainActivity
fun checkPermissions() {
    val missing = DittoSyncPermissions(this).missingPermissions()
    if (missing.isNotEmpty()) {
        this.requestPermissions(missing, 0)
    }
}

Ensuring Imports

Just in case your project did not auto-import as you went along, you can replace the import statements in MainActivity with these:

MainActivity
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.*
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.task_view.view.*
import live.ditto.*
import live.ditto.transports.*
import live.ditto.android.DefaultAndroidDittoDependencies
import java.time.Instant

Step 7: Build and Run!

You now have a fully functioning tasks app. Build and run it on a device. The simulator will not show any data sync because neither Bluetooth LE or the necessary network system is available to allow simulators to find each other or another device.