SDK Guides
...
Tutorials in C#
Xamarin Task App
xamarin ios is currently supported on physical devices; however, the ios simulator is not yet supported xamarin android support is coming soon the following guide will show you how to build a task list application using xamarin this tutorial also shows you how to build an example user interface for ios follow the https //docs microsoft com/en us/xamarin/android/get started/ for android specific code samples overview /#setting up /#configuring ditto /#showing the list of tasks docid\ gf4sxf4w4eyqjzf5j88zg /#app overview setting up if applicable, /#macos if applicable, /#windows /#creating the app /#adding dependencies macos before getting started, you will need the latest version of https //apps apple com/us/app/xcode/id497799835 and https //visualstudio microsoft com/downloads/ once the visual studio installer is complete, launch visual studio and open the preferences dialog ( visual studio menu > preferences , or the ⌘, keyboard shortcut) navigate to the build and debug > sdk locations > net core section on the left under net core command line browse to or enter the following in the location box /usr/local/share/dotnet/dotnet finally, add the following lines to your appname csproj file appname csproj \<?xml version="1 0" encoding="utf 8"?> \<project defaulttargets="build" toolsversion="4 0" xmlns="http //schemas microsoft com/developer/msbuild/2003"> // 1 \<propertygroup> \<runtimeidentifier>osx\</runtimeidentifier> \<nugetruntimeidentifier>osx\</nugetruntimeidentifier> \<autogeneratebindingredirects>true\</autogeneratebindingredirects> \</propertygroup> windows xamarin android development can be done entirely on a windows box, but a mac with xcode is required for some parts of xamarin ios development it is possible to https //docs microsoft com/en us/xamarin/ios/get started/installation/windows/connecting to mac/ from visual studio on a windows pc creating the app once you've installed the latest version of xcode and visual studio open visual studio and click file > new project under ios select app and then single view app make sure language is c# fill out the information on the form similar to the screenshot below these are recommended values however they are not crucial to complete this tutorial app name "tasks" organization identifier "live ditto" however, feel free to use your own value here target ios 12 0 and finally click continue and select a directory to create the application adding dependencies follow the instructions on the https //legacydocs ditto live/csharp/installation to use package manager or net cli now open tasks csproj configuring ditto /#creating your ditto app /#adding permissions to the infoplist /#adding ditto to appdelegatecs /#creating a task class /#creating the user interface creating your ditto app before we start coding, we first need to create a new app in the https //portal ditto live/ apps created on the portal will automatically sync data between them and also to the ditto big peer each app created on the portal has a unique appid which can be seen on your app's settings page once the app has been created this id is used in subsequent sections to configure your ditto instance adding permissions to the info plist for ditto to fully use all the network transports like bluetooth low energy, local area network, apple wireless direct, the app will need to ask the user for permissions these permission prompts need to be in the info plist file of your project follow the instructions on the https //legacydocs ditto live/ios/installation#platform permissions adding ditto to appdelegate cs when visual studio generated your project, there should be a file called "appdelegate cs " we will need an instance of ditto throughout this tutorial and the app's lifecycle first import ditto with using dittosdk next, we'll need to hold a reference to our ditto instance after the app has finished launching we will add a working directory currently, xamarin ios apps need to provide a working directory inside the app's sandbox without this, the default directory used by the sdk won't be writable and an exception will be thrown construct an instance of ditto with an online playground identity using the app id and playground token of the app that you just created on the portal we are using an onlineplayground setup, which should suffice for this tutorial however, you should never deploy this to a production environment we want to enable all peer to peer transport configurations we will call startsync() appdelegate cs using foundation; using uikit; using system; //1 using dittosdk; namespace tasks { // the uiapplicationdelegate for the application this class is responsible for launching the // user interface of the application, as well as listening (and optionally responding) to application events from ios \[register ("appdelegate")] public class appdelegate uiresponder, iuiapplicationdelegate { \[export("window")] public uiwindow window { get; set; } //2 internal ditto ditto; \[export ("application\ didfinishlaunchingwithoptions ")] public bool finishedlaunching (uiapplication application, nsdictionary launchoptions) { //3 nsfilemanager filemanager = new nsfilemanager(); nsurl url = filemanager geturl(nssearchpathdirectory documentdirectory, nssearchpathdomain user, null, true, out nserror error); if (error != null) { console writeline($"error creating documents directory {error localizeddescription}"); } url = url append("ditto", true); filemanager createdirectory(url, true, null, out error); if (error != null) { console writeline($"error creating ditto directory {error localizeddescription}"); } string workingdir = url path; //4 dittoidentity identity = dittoidentity onlineplayground(appid "replace me", token "replace me" , false, workingdir workingdir); ditto = new ditto(identity, workingdir); //5 var transportconfig = new dittotransportconfig(); transportconfig enableallpeertopeer(); ditto transportconfig = transportconfig; //6 ditto startsync(); return true; } // uiscenesession lifecycle \[export ("application\ configurationforconnectingscenesession\ options ")] public uisceneconfiguration getconfiguration (uiapplication application, uiscenesession connectingscenesession, uisceneconnectionoptions options) { // called when a new scene session is being created // use this method to select a configuration to create the new scene with return uisceneconfiguration create ("default configuration", connectingscenesession role); } \[export ("application\ diddiscardscenesessions ")] public void diddiscardscenesessions (uiapplication application, nsset\<uiscenesession> scenesessions) { // called when the user discards a scene session // if any sessions were discarded while the application was not running, this will be called shortly after `finishedlaunching` // use this method to release any resources that were specific to the discarded scenes, as they will not return } } } creating a task class https //legacydocs ditto live/csharp/tutorial/xamarin/configure ditto#2 4 create a task class ditto is a document database, which represents all of its rows in the database a json like structure in this tutorial, we will define each task like so { " id" "123abc", "body" "get milk", "iscompleted" true } these task documents will all be in the "tasks" collection we will be referencing this collection throughout this tutorial with var taskscollection = ditto store\["tasks"] create a new cs file called "task cs" in your project import ditto with using dittosdk add the matching variables public string id; , public string body; , and public bool iscompleted; to the class we will use this to match the document values to to the class add a constructor to task that takes in a dittodocument parse out the document's keys with ditto's type safe value accessors this will safely map all the document's values to the struct's variables that we created in step 1 task cs // 1 using dittosdk; namespace tasks { public class task { // 2 public string id; public string body; public bool iscompleted; // 3 public task(dittodocument document) { this id = document\[" id"] stringvalue; this body = document\["body"] stringvalue; this iscompleted = document\["iscompleted"] booleanvalue; } } } once we set up our user interface, you'll notice that reading these values becomes a bit easier with this added class creating the user interface when we generated the project, visual studio created a default main storyboard file right click main storyboard and select open with > xcode interface builder this will open up the xcode application and allow you to design the user interface inside of xcode using storyboards when xcode opens select the main file and open it, and then delete the default viewcontroller open the ui components library and type "navigation controller" into the search drag a new navigation controller onto the screen this will create a navigation controller and a root view controller with a table view select the navigation controller and make sure the box is initital view controller is selected open the ui components library again and type in "bar button item" drag the button to the top right of the "root view controller" screen then, select the button, go to the inspector panel and select the attributes inspector change system item to add this will make the bar button item we just added into a ''+'' sign select the root view controller top bar then go to the inspectors panel and select the identity inspector we will create a custom class for this view controller in the class section type "taskstableviewcontroller" next, select the prototype cells and give it an identifier of "taskcell" save that file in xcode, then open the poject up in visual studio again at this point, visual studio should have auto generated two files for you a taskstableviewcontroller cs file and a taskstableviewcontroller designer cs file these are the class files that were created from the taskstableviewcontroller we created in xcode open the project up in xcode again and you should now see two new files added to the project directory a taskstableviewcontroller h file and a taskstableviewcontroller m file open the main storyboard file while pressing the control button on your keyboard select the button and drag under the @interface scenedelegate uiresponder { } code inside the taskstableviewcontroller h file fill the information as follows connection action name didclickaddtask type uibarbuttonitem save the file showing the list of tasks in the last part of the tutorial we setup the user interface using the xcode interfaace builder and created a custom class taskstableviewcontroller /#setting up taskstableviewcontroller class /#creating the tasktablesource class setting up taskstableviewcontroller class first, we need to add some variables that will be created on viewdidload of the taskstableviewcontroller so adjust the class to match this code taskstableviewcontroller cs using system; using uikit; // remember to import ditto using dittosdk; using system collections generic; namespace tasks { public partial class taskstableviewcontroller uitableviewcontroller { // these hold references to ditto for easy access private dittolivequery livequery; private dittosubscription subscription; private ditto ditto { get { var appdelegate = (appdelegate)uiapplication sharedapplication delegate; return appdelegate ditto; } } private dittocollection collection { get { return this ditto store collection("tasks"); } } // list that will contain all the tasks list\<task> tasks = new list\<task>(); // data source for the taskstableviewcontroller private taskstablesource taskstablesource = new taskstablesource(); public taskstableviewcontroller(intptr handle) base(handle) { } public override void viewwillappear(bool animated) { base viewwillappear(animated); tableview\ source = taskstablesource; } public override void viewdidload() { base viewdidload(); setuptasklist(); } // sets up live query for syncing public void setuptasklist() { subscription = ditto store\["tasks"] find("!isdeleted") subscribe() livequery = ditto store\["tasks"] find("!isdeleted") observelocal((docs, event) => { tasks = docs convertall(d => new task(d)); taskstablesource updatetasks(tasks); invokeonmainthread(() => { tableview\ reloaddata(); }); }); ditto store\["tasks"] find("isdeleted == true") evict(); } // creates a new task partial void didclickaddtask(uibarbuttonitem sender) { // create an alert var alertcontrol = uialertcontroller create( title "add new task", message null, preferredstyle uialertcontrollerstyle alert); // add a text field to the alert for the new task text alertcontrol addtextfield(configurationhandler (uitextfield obj) => obj placeholder = "enter task"); alertcontrol addaction(uialertaction create(title "cancel", style uialertactionstyle cancel, handler null)); // add a "ok" button to the alert alertcontrol addaction(uialertaction create(title "ok", style uialertactionstyle default, alarm => addtask(alertcontrol textfields\[0] text))); // present the alert to the user presentviewcontroller(alertcontrol, animated true, null); } public void addtask(string text) { var dict = new dictionary\<string, object> { {"body", text}, {"iscompleted", false} }; var docid = this collection upsert(dict); } } } let's break down what this code does first, we create the variables needed and then initialize them in viewwillappear() taskstableviewcontroller cs // these hold references to ditto for easy access private dittolivequery livequery; private ditto ditto { get { var appdelegate = (appdelegate)uiapplication sharedapplication delegate; return appdelegate ditto; } } private dittocollection collection { get { return this ditto store collection("tasks"); } } // list that will contain all the tasks list\<task> tasks = new list\<task>(); // data source for the taskstableviewcontroller private taskstablesource taskstablesource = new taskstablesource(); public taskstableviewcontroller(intptr handle) base(handle) { } public override void viewwillappear(bool animated) { base viewwillappear(animated); tableview\ source = taskstablesource; } after setting up the variables and starting ditto, 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 uitableview 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 taskstableviewcontroller cs public override void viewdidload() { base viewdidload(); setuptasklist(); } // sets up live query for syncing public void setuptasklist() { livequery = ditto store\["tasks"] find("!isdeleted") observelocal((docs, event) => { tasks = docs convertall(d => new task(d)); // we will implement this later taskstablesource updatetasks(tasks); invokeonmainthread(() => { tableview\ reloaddata(); }); }); } after setting up the variables and starting ditto, 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 uitableview 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 taskstableviewcontroller cs public override void viewdidload() { base viewdidload(); setuptasklist(); } // sets up live query for syncing public void setuptasklist() { livequery = ditto store\["tasks"] find("!isdeleted") observelocal((docs, event) => { tasks = docs convertall(d => new task(d)); // we will implement this later taskstablesource updatetasks(tasks); invokeonmainthread(() => { tableview\ reloaddata(); }); }); } 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 with this in place, we can now add user actions and configure the uitableview to display the tasks look in the taskstableviewcontroller design cs file and you should see \[action ("didclickaddtask ")] partial void didclickaddtask (uikit uibarbuttonitem sender); this is the button we created earlier in xcode now we need to add an action to it taskstableviewcontroller cs // triggered when add button is pressed partial void didclickaddtask(uibarbuttonitem sender) { // create an alert var alertcontrol = uialertcontroller create( title "add new task", message null, preferredstyle uialertcontrollerstyle alert); // add a text field to the alert for the new task text alertcontrol addtextfield(configurationhandler (uitextfield obj) => obj placeholder = "enter task"); alertcontrol addaction(uialertaction create(title "cancel", style uialertactionstyle cancel, handler null)); // add an "ok" button to the alert alertcontrol addaction(uialertaction create(title "ok", style uialertactionstyle default, alarm => addtask(alertcontrol textfields\[0] text))); // present the alert to the user presentviewcontroller(alertcontrol, animated true, null); } public void addtask(string text) { var dict = new dictionary\<string, object> { {"body", text}, {"iscompleted", false}, {"isdeleted", false} }; // adds the new task to the ditto collection var docid = this collection upsert(dict); } when we upsert the new task into the ditto collection then the live query that is observing the collection will be triggered creating the tasktablesource class https //legacydocs ditto live/csharp/tutorial/xamarin/edit screen#4 1 creating the tasktablesource class open file > new file > empty class (c#) and name it "tasktablesource" import uikit with using uikit this class will implement uitableviewsource tasktablesource cs using system; using foundation; using uikit; namespace tasks { public class tasktablesource uitableviewsource { public tasktablesource() { } public override uitableviewcell getcell(uitableview tableview, nsindexpath indexpath) { throw new notimplementedexception(); } public override nint rowsinsection(uitableview tableview, nint section) { throw new notimplementedexception(); } } } now we need to get our instance of ditto and setup the other class variables we will be using tasktablesource cs public class tasktablesource uitableviewsource { task\[] tasks; nsstring cellidentifier = new nsstring("taskcell"); private dittocollection collection { get { return this ditto store collection("tasks"); } } private ditto ditto { get { var appdelegate = (appdelegate)uiapplication sharedapplication delegate; return appdelegate ditto; } } public tasktablesource() { } public override uitableviewcell getcell(uitableview tableview, nsindexpath indexpath) { throw new notimplementedexception(); } public override nint rowsinsection(uitableview tableview, nint section) { throw new notimplementedexception(); } } implement is the uitableviewsource inherited method rowsinsection by checking whether the tasks array is empty if its not empty then return the number of tasks in the tasks array tasktablesource cs public override nint rowsinsection(uitableview tableview, nint section) { if (this tasks == null) { return 0; } return tasks length; } implement is the uitableviewsource inherited method getcell by doing the following get the cell using the cell identifier within our cell we want a text label and a cell accessory that will be used to check whether the task has been completed or not we will assign the name of the task to the textlabel text setup a tap gesture that will update whether the task has been completed or not when tapped, we will update the task document inside the ditto collection and update the iscompleted key tasktablesource cs public override uitableviewcell getcell(uitableview tableview, nsindexpath indexpath) { // 1 uitableviewcell cell = tableview\ dequeuereusablecell(cellidentifier); if (cell == null) { cell = new uitableviewcell(uitableviewcellstyle default, cellidentifier); } task task = tasks\[indexpath row]; // 2 cell textlabel text = task body; var taskcomplete = task iscompleted; //checks whether the tasks has been completed or not if (taskcomplete) { cell accessory = uitableviewcellaccessory checkmark; } else { cell accessory = uitableviewcellaccessory none; } // 3 var tapgesture = new uitapgesturerecognizer(); tapgesture addtarget(() => { if (taskcomplete) { collection findbyid(task id) update(mutabledoc => mutabledoc\["iscompleted"] set(false) ); } else { collection findbyid(task id) update(mutabledoc => mutabledoc\["iscompleted"] set(true) ); } }); cell addgesturerecognizer(tapgesture); return cell; } the next thing to add to our taskstablesource is the updatetasks method we called earlier this method will take a list of tasks and then assign it to our tasks array that is used for displaying the tasks tasktablesource cs public void updatetasks(list\<task> tasks) { this tasks = tasks toarray(); } the last thing that we need to do is to add a way to delete any tasks that we no longer want to do so, we will override the commiteditingstyle method this method has a default delete value and so we just need to tell the app what to do when the delete is called in this case, we want to remove the task document from the ditto collection when we remove that documents the live query we setup earlier will be called and will refresh the ui with the removed task tasktablesource cs public override void commiteditingstyle(uitableview tableview, uitableviewcelleditingstyle editingstyle, foundation nsindexpath indexpath) { switch (editingstyle) { case uitableviewcelleditingstyle delete var task = tasks\[indexpath row]; collection findbyid(task id) update((mutabledoc) => { if (mutabledoc == null) return; mutabledoc\["isdeleted"] set(true); }); break; case uitableviewcelleditingstyle none console writeline("commiteditingstyle\ none called"); break; } } app overview our application is complete! our taskstablesource cs file should look like the following tasktablesource cs using system; using system collections generic; using foundation; using uikit; using dittosdk; namespace tasks { public class taskstablesource uitableviewsource { task\[] tasks; nsstring cellidentifier = new nsstring("taskcell"); private dittocollection collection { get { return this ditto store collection("tasks"); } } private ditto ditto { get { var appdelegate = (appdelegate)uiapplication sharedapplication delegate; return appdelegate ditto; } } public taskstablesource(task\[] tasklist) { this tasks = tasklist; } public taskstablesource() { } public override uitableviewcell getcell(uitableview tableview, nsindexpath indexpath) { uitableviewcell cell = tableview\ dequeuereusablecell(cellidentifier); if (cell == null) { cell = new uitableviewcell(uitableviewcellstyle default, cellidentifier); } task task = tasks\[indexpath row]; cell textlabel text = task body; var taskcomplete = task iscompleted; if (taskcomplete) { cell accessory = uitableviewcellaccessory checkmark; } else { cell accessory = uitableviewcellaccessory none; } var tapgesture = new uitapgesturerecognizer(); tapgesture addtarget(() => { if (taskcomplete) { collection findbyid(task id) update(mutabledoc => mutabledoc\["iscompleted"] set(false) ); } else { collection findbyid(task id) update(mutabledoc => mutabledoc\["iscompleted"] set(true) ); } }); cell addgesturerecognizer(tapgesture); return cell; } public override nint rowsinsection(uitableview tableview, nint section) { if (this tasks == null) { return 0; } return tasks length; } public void updatetasks(list\<task> tasks) { this tasks = tasks toarray(); } public override void commiteditingstyle(uitableview tableview, uitableviewcelleditingstyle editingstyle, foundation nsindexpath indexpath) { switch (editingstyle) { case uitableviewcelleditingstyle delete var task = tasks\[indexpath row]; collection findbyid(task id) update((mutabledoc) => { if (mutabledoc == null) return; mutabledoc\["isdeleted"] set(true); }); break; case uitableviewcelleditingstyle none console writeline("commiteditingstyle\ none called"); break; } } } } congratulations you are now complete with the ditto xamarin ios task app!