SDK Guides
...
Task App Tutorials for Kotlin
Kotlin Task App Quickstart
discover ditto by building and exploring through a task app /#create android studio project /#create ui /#configure main activity part ii /#add tasksadapter /#integrate ditto /#build and run the following procedures are based on android studio 4 1 and kotlin 1 4 create android studio project this guide is based on android studio 4 1 and kotlin 1 4 the first step is to create a project go to file → new → new project and select basic activity next, fill out the options with the product name "tasks", choose kotlin, and set the minimum api level to 26 in the newer version of android studio the basic activity template includes additional files that are not needed for this tutorial to continue, remove the following if they exist firstfragment kt secondfragment kt fragment first xml fragment second xml nav graph xml install ditto to install ditto, we need to add it as a dependency in the build gradle script for the app, as well as ensuring that we have the relevant java compatibility set android requires requesting permission to use bluetooth low energy and wifi aware for instructions, see docid\ gkgzhlccwdz1 4jmszwop add extensions for the ui in this example, we are still using kotlin synthetics, which are no longer bundled automatically we need to add kotlin android extensions in the the plugins section of build gradle to enable build gradle plugins { // id 'kotlin android extensions' } be sure to sync project with gradle files after you add ditto as a dependency click the elephant icon with the blue arrow in the top right to manually trigger if it doesn't prompt at this point, you have the basic project in place! now we need to start to build the ui elements create ui set up the interface for your task app adjust your default layout files ( /#adjusting existing layouts ) set up a toolbar and button for adding new tasks ( docid\ dd15ptffuuabqft8bnge7 create an alert and define the string values create a dialog box for adding new tasks adjusting existing layouts https //docs ditto live/android/tutorial/kotlin/create ui#2 1 adjust existing layouts navigate to the content main xml layout file and replace the xml in the text representation view this will remove the existing text view and a recycler view that we will use to display the list of tasks now navigate to activity main xml layout file and replace the xml in the text representation view this will adjust the floating action button to use a white add icon 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> the layout should look like this now navigate to activity main xml layout file and replace the xml in the text representation view this will adjust the floating action button to use a white add icon 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="#ffffff" app\ srccompat="@android\ drawable/ic input add" /> \</androidx coordinatorlayout widget coordinatorlayout> now navigate to activity main xml layout file and replace the xml in the text representation view this will adjust the floating action button to use a white add icon \<?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="#ffffff" app\ srccompat="@android\ drawable/ic input add" /> \</androidx coordinatorlayout widget coordinatorlayout> the layout should look like this now create alertdialog layout https //docs ditto live/android/tutorial/kotlin/create ui#2 2 create alertdialog layout we now need to create a new layout resource file to define our alert dialog right click on the layouts folder in the project and go to file → new → xml → layout xml name the resource file dialog new task open the new dialog new task xml layout file and replace the xml in the text representation view this will add an editable text input to allow the user to enter the task \<?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> the layout should look like this now define strings we need to create a few string constants open strings xml in the /res/values folder and replace it with this xml \<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> create dialogfragment https //docs ditto live/android/tutorial/kotlin/create ui#2 4 create dialogfragment https //docs ditto live/android/tutorial/kotlin/create ui#2 3 define strings to use the alertdialog we will create a dialogfragment create a new kotlin class by right clicking the app folder within java in the project view name the new file newtaskdialogfragment replace the contents of newtaskdialogfragment kt with this 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") } } } configure main activity part i we need to import ditto and create a few variables open the mainactivity file and replace the existing code with this 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) } } if it does not run automatically, execute sync project with gradle files add new task functions https //docs ditto live/android/tutorial/kotlin/activities#3 2 add new task functions we will add a function and override two now that mainactivity is an abstract class insert this code after oncreate() function in the class 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") } create a task view layout https //docs ditto live/android/tutorial/kotlin/activities#3 3 create a task view layout right click on the layouts folder in the project and go to file → new → xml → layout xml name the file task view open the task view\ xml layout file and replace the xml in the text representation view this will add a text view and checkbox to display the task in each row of the recyclerview 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> the layout should look like this now configure main activity part ii we now need to continue to configure the mainactivity to customize the recyclerview , display the tasks, and add the logic for the user actions replace the oncreate() function with this code that will configure the recycler view 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)) } https //docs ditto live/android/tutorial/kotlin/activities#3 4 configure main activity part ii add tasksadapter https //docs ditto live/android/tutorial/kotlin/activities#3 5 add tasksadapter we need to declare a recyclerview\ adapter to provide a data source to the recyclerview add this code to the bottom of mainactivity , as a new class within the file 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 } } add swipe to delete https //docs ditto live/android/tutorial/kotlin/activities#3 6 add swipe to delete to match the ios getting started app, we also want to add swipe to delete functionality insert this code at the bottom of mainactivity as a new class 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) } } almost there! at this point, we have most of the app created, but we now need to integrate ditto! integrate ditto in order to integrate ditto into our app we first need to create a new app on the https //portal ditto live/ apps created on the portal will automatically sync data between them and also to the ditto big peer for instructions, see docid\ hk2s3ld8sj ti5snxwkq1 initialize ditto to finish the app, we now need to integrate ditto we will initialize it in the oncreate() function within mainactivity furthermore, we will add handlers for the swipe to delete and listening for row clicks to mark a task as completed (or in completed) replace the existing oncreate() code with this 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() } the important things to note is that you need an access license to use ditto if you do not have one yet, reach out and we can supply one to enable background synchronization, we need to call startsync() which allows you to control when synchronization occurs for this application we want it to run the entire time the app is in use set up store observer query finally, we then use ditto's key api to observe changes to the database by creating a live query in the setuptasklist() function this allows us to set the initial state of the recyclerview after the query is immediately run and then subsequently get callbacks for any new data changes that occur locally or that were synced from other devices note, that we are using the observelocal api in ditto this sets up a local observer for data changes in the database that match the query you also need to create a subscription for the same query that will be used to request this data from other devices 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() } this is a best practice when using ditto, since it allows your ui to simply react to data changes which can come at any time given the ad hoc nature of how ditto synchronizes with nearby devices check for location permissions android requires you to request location permissions to fully enable bluetooth low energy (since bluetooth can be involved with location tracking) insert this function in mainactivity mainactivity fun checkpermissions() { val missing = dittosyncpermissions(this) missingpermissions() if (missing isnotempty()) { this requestpermissions(missing, 0) } } ensure 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 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 or the necessary network system is available to allow simulators to find each other or another device