Convertigo Client SDK is a set of native libraries used by mobile or Windows desktop applications to access Convertigo Server services.

Convertigo Client SDK is a set of native libraries used by mobile or Windows desktop applications to access Convertigo Server services. An application using the SDK can easily access Convertigo services such as Sequences and Transactions.

The Client SDK will abstract the programmer from handling the communication protocols, local cache, FullSync off line data managment, UI thread management and remote logging. So the developer can focus on building the application.

Convertigo Client SDK

Client SDK is available for:

  • Android Native apps as a standard Gradle dependency
  • iOS native apps as a standard Cocoapod
  • Windows desktop or Xamarin apps as Nugets or Xamarin Components
  • Google Angular (Ex Angular 2) JavaScript framework as an NPM package

More information about Client SDK in the sections below

Installation Guide

Android Studio

The Convertigo Client SDK for Android is provided on jCenter:

Convertigo Client SDK for Android

To use it in your project, you just have to reference the SDK in the “dependencies” closure of your module build.gradle file. Be sure to update the sdk reference to the correct version.

Sample gradle file :

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"

    packagingOptions {
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/NOTICE'
    }

    defaultConfig {
        applicationId "com.example.opic.myc8osdkapp"
        minSdkVersion 10
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.1.1'

    /*
     * Include Convertigo Client SDK version 2.1.2
     */
    compile 'com.convertigo.sdk:c8oSDKAndroid:2.1.2'
}

By default Convertigo SDK Brings several Database Storage engines :

  • SQLite for standard SQLite storage
  • SQLiteCipher for Encrypted SQLite storage
  • ForestDB for High performance Encrypted or not Storage You can reduce the size of your applications by excluding the unwanted storage engines.
compile ('com.convertigo.sdk:c8oSDKAndroid:2.1.2') {
    exclude module: 'couchbase-lite-android-sqlcipher' // Exclude if you dont need the optional SQLCipher Storage Engine
    exclude module: 'couchbase-lite-android-forestdb'  // Exclude if you dont need the optional ForestDB Storage Engine
}

As a reminder, don’t forget to grant INTERNET permission to your app in the AndroidManifest.xml file.

‹uses-permission android:name="android.permission.INTERNET" /›

Xcode

The Convertigo SDK provided as pod on Cocoapods here : Convertigo Xcode SDK

Create a PodFile with :

use_frameworks!
target 'MyApp' do
  pod 'C8oSDK', '2.1.0'
end

Then in the console, run:

 pod install 

Restart Xcode and open the .xcworkspace And there you go !

Be sure to read the installation information on Cocoapods

Xamarin Studio

The Convertigo Client SDK for Android is provided as a convertigo-mbaas-x.y.xam component. In order to be able to use it, you need to import the component for each target platform (Android, IOs, etc …).

The xam file can be found under the SDK_x.y.z/c# + Xamarin folder of our SourceForge files.

component —————–(IMAGE)——————–

Once the module imported, you should see the package in the Component folder.

componentInstalled —————(IMAGE)———————

Before using any SDK function you need to initialize the SDK. Although all other code calling the SDK can be used in a shared project the initialisation code must be done in an Android or iOS project. The best place to initialize the SDK is in:

  • the MainActivity.cs (Android):
protected override void OnCreate (Bundle bundle)
	{
		base.OnCreate (bundle);
		global::Xamarin.Forms.Forms.Init (this, bundle);
		// Initialize the SDK Here.
		C8oPlatform.Init();
		LoadApplication (new App ());
	}
  • the AppDelegate.cs (Apple)
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
	{
		global::Xamarin.Forms.Forms.Init ();
		LoadApplication (new AppTestSDK.App ());
		// Initialize the SDK Here.
		C8oPlatform.Init();
		return base.FinishedLaunching (app, options);
	}

Angular

C8oClientSDK for Angular is provided as a standard NPM package. You can use it in your package.json just like any other package. see

Convertigo SDK Angular

type this command in your console to install the SDK in your current Angular Project

 npm install c8osdkangular --save 

Programming Guide

Table of content

Initializing and creating a C8o instance for an Endpoint

For the .NET SDK, there is a common static initialization to be done before using the SDK feature. It prepares some platform specific features. After that, you will be able to create and use the C8o instance to interact with the Convertigo server and the Client SDK features.

For the Angular there is some imports and declaration to do in the app’s module to do.

For the Angular, Javascript and React Native there is a specific initialization to do.

For the Angular and Javascript a specific method must be called “finalized init” to be sure that initialization has been finished

A C8o instance is linked to a server through is endpoint and cannot be changed after.

You can have as many C8o instances (except Angular), pointing to a same or different endpoint. Each instance handles its own session and settings. We strongly recommend using a single C8o instance per application because server licensing can based on the number of sessions used.

    import com.convertigo.clientsdk.*;
    
    C8o c8o = new C8o(getApplicationContext(), "https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery");
    // the C8o instance is ready to interact over https with the demo.convertigo.net server, using sampleMobileUsDirectoryDemo as default project.
    
    using Convertigo.SDK;
    
    // The .NET initialization for platform specific initialization, should be called once
    C8oPlatform.Init();
    
    var c8o = new C8o("https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery");
    // the C8o instance is ready to interact over https with the demo.convertigo.net server, using sampleMobileUsDirectoryDemo as default project.
    
    import C8o
    // In swift there is two ways to handle errors :
    // We can either choose to don't care about errors using the following syntax (this may produce in case of error an "EXC_BAD_INSTRUCTION")
    let c8o : C8o = try! C8o(endpoint: "https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery", c8oSettings: nil)

    // or we can choose to use do/catch syntax that will allow us to catch errors
    do{
        let c8o : C8o = try C8o(endpoint: "https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery", c8oSettings: nil)
    }
    catch let e as NSError{
        print(e.description)
    }
    // the C8o instance is ready to interact over https with the demo.convertigo.net server, using sampleMobileUsDirectoryDemo as default project.
    
    //Into app.module.ts
    import {C8o} from "c8osdkangular";
    import {HttpClientModule} from "@angular/common/http";

    @NgModule({
    imports: [
        BrowserModule,
        HttpClientModule
    ],
    providers: [
        C8o
    ]
    });

    //Into the target page
    import { C8o, C8oSettings } from "c8osdkangular";

    export class MyClass {

        // Instanciate c8o thanks to Angular di (dependency injection)
        constructor(private c8o: C8o) {
            
            // Call init method with a c8osettings class giving an endpoint
            this.c8o.init(new C8oSettings().setEndPoint("http://localhost:18080/convertigo/projects/template_Ionic2"));
            
            // Use the specific method to be sure that init has been finished 
            this.c8o.finalizeInit(()=>{
                // Do stuff with c8o object
            });
        }
    }
    
    import { C8o, C8oSettings } from "c8osdkjs"

    // Instantiate C8o
    const c8o = new C8o()

    // Call init method with a c8osettings class giving an endpoint
    c8o.init(new C8oSettings().setEndPoint("http://localhost:18080/convertigo/projects/template_Ionic2"));

    // Use the specific method to be sure that init has been finished 
    c8o.finalizeInit(()=>{
        // Do stuff with c8o object
    });
    
 
    import { C8o, C8oSettings } from "react-native-c8osdk";

    // Instantiate C8o
    let c8o: C8o = new C8o();

    // Init Endpoint
    c8o.init("https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery", new C8oSettings());
    

Advanced instance settings

The endpoint is the mandatory setting to get a C8o instance, but there is additional settings through the C8oSettings class.

A C8oSettings instance should be passed after the endpoint. Settings are copied inside the C8o instance and a C8oSettings instance can be modified and reused after the C8o constructor.

Setters of C8oSettings always return its own instance and can be chained.

A C8oSettings can be instantiated from an existing C8oSettings or C8o instance.

    // the common way
    C8o c8o = new C8o(getApplicationContext(), "https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery", new C8oSettings()
    .setDefaultDatabaseName("mydb_fullsync")
    .setTimeout(30000));

    // the verbose way
    String endpoing = "https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery";
    C8oSettings c8oSettings = new C8oSettings();
    c8oSettings.setDefaultDatabaseName("mydb_fullsync");
    c8oSettings.setTimeout(30000);
    c8o = new C8o(getApplicationContext(), endpoint, c8oSettings);

    // customize existing settings
    C8oSettings customSettings = new C8oSettings(c8oSettings).setTimeout(60000);
    // or from a C8o instance
    customSettings = new C8oSettings(c8o).setTimeout(60000);

    // all settings can be retrieve from a C8o or C8oSettings instance
    int timeout = c8o.getTimeout();
    
    // the common way
    C8o c8o = new C8o("https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery", new C8oSettings()
    .SetDefaultDatabaseName("mydb_fullsync")
    .SetTimeout(30000));

    // the verbose way
    string endpoing = "https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery";
    C8oSettings c8oSettings = new C8oSettings();
    c8oSettings.SetDefaultDatabaseName("mydb_fullsync");
    c8oSettings.SetTimeout(30000);
    c8o = new C8o(endpoint, c8oSettings);

    // customize existing settings
    C8oSettings customSettings = new C8oSettings(c8oSettings).SetTimeout(60000);
    // or from a C8o instance
    customSettings = new C8oSettings(c8o).SetTimeout(60000);

    // all settings can be retrieve from a C8o or C8oSettings instance
    int timeout = c8o.Timeout;
    
 
    import C8o
    // In swift there is two ways to handle errors :
    // We can either choose to don't care about errors using the following syntax (this may produce in case of error an "EXC_BAD_INSTRUCTION")
    let c8o : C8o = try! C8o(endpoint: "https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery", c8oSettings: nil)

    // or we can choose to use do/catch syntax that will allow us to catch errors
    do{
        let c8o : C8o = try C8o(endpoint: "https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery", c8oSettings: nil)
    }
    catch let e as NSError{
        print(e.description)
    }
    // the C8o instance is ready to interact over https with the demo.convertigo.net server, using sampleMobileUsDirectoryDemo as default project.
    
    //Into app.module.ts
    import {C8o} from "c8osdkangular";
    import {HttpClientModule} from "@angular/common/http";

    @NgModule({
    imports: [
        BrowserModule,
        HttpClientModule
    ],
    providers: [
        C8o
    ]
    });

    //Into the target page
    import { C8o, C8oSettings } from "c8osdkangular";

    export class MyClass {

        // Instanciate c8o thanks to Angular di (dependency injection)
        constructor(private c8o: C8o) {
            
            // Call init method with a c8osettings class giving an endpoint
            this.c8o.init(new C8oSettings().setEndPoint("http://localhost:18080/convertigo/projects/template_Ionic2"));
            
            // Use the specific method to be sure that init has been finished 
            this.c8o.finalizeInit(()=>{
                // Do stuff with c8o object
            });
        }
    }
    
    import { C8o, C8oSettings } from "c8osdkjs"
    
    // The only way
    let settings: C8oSettings = new C8oSettings();
    settings
        .setEndPoint("https://demo.convertigo.net/cems/projects/sampleMobileCtfGallery")
        .setDefaultDatabaseName("mydb_fullsync")
        .setTimeout(30000);
    //Then we need to assign C8oSettings object to our C8o object
    c8o.init(settings);

    //Then to use c8o Object and be sure that initialization has been properly done
    c8o.finalizeInit().then(() => {
        //Do stuff with c8o Object
    })

    // all settings can be retrieve from a C8o or C8oSettings instance
    let timeout : number = c8o.timeout;
    
    import {C8o, C8oSettings} from "react-native-c8osdk";
    
    // Instanciate C8oSettings
    let settings = new C8oSettings();

    // Add Settings properties
    settings
    .setTimeout(3000)
    .setDefaultDatabaseName("myfullsyncDbName")
    .setTrustAllCertificates(true)
    .setLogLevelLocal(C8oLogLevel.TRACE);

    // Instanciate C8o
    let c8o: C8o = new C8o();

    // Init C8o instance with a given endpoint and settings
    c8o.init("http://c8o-dev.convertigo.net:80/cems/projects/ClientSDKtestig", settings);
    

Calling a Convertigo requestable

With a C8o instance you can call Convertigo Sequence and Transaction or make query to your local FullSync database. You must specify the result type you want: an XML Document or a JSON Object response. Returning XML Just use the c8o.callXml method to request a XML response.

    import org.w3c.dom.Document;
    
    // c8o is a C8o instance
    Document document = c8o.callXml(".getSimpleData").sync();
    
    using System.Xml.Linq;
    
    // c8o is a C8o instance
    XDocument document = c8o.CallXml(".getSimpleData").Sync();
    
 
    import AEXML
    
    // c8o is a C8o instance
    let document : AEXMLDocument = try! c8o.callXml(".getSimpleData").sync()!
    
    import { C8o, C8oSettings } from "c8osdkangular"
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter syntax 
    let result = await this.c8o.callJson('.login')
                        .async();

    // Here using Javascript's Promises with then/catch syntax
    this.c8o.callJson(".login")
        .async()
        .then((response)=>{
        //handle result
        });

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README
    this.c8o.callJson(".login")
        .then((response)=>{
        //handle result
        });
    
    import { C8o, C8oSettings } from "c8osdkjs"
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter syntax 
    let result = await this.c8o.callJson('.login')
                        .async();

    // Here using Javascript's Promises with then/catch syntax
    this.c8o.callJson(".login")
        .async()
        .then((response)=>{
        //handle result
        });

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README
    this.c8o.callJson(".login")
        .then((response)=>{
        //handle result
        });
    
    import {C8o, C8oSettings} from "react-native-c8osdk";
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter syntax 
    let result = await this.c8o.callJson('.login')
                        .async();

    // Here using Javascript's Promises with then/catch syntax
    this.c8o.callJson(".login")
        .async()
        .then((response)=>{
        //handle result
        });

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README
    this.c8o.callJson(".login")
        .then((response)=>{
        //handle result
        });
    

Returning jSON Just use the c8o.callJson method to request a JSON response.

    import org.json.JSONObject;
    
    // c8o is a C8o instance
    JSONObject jObject = c8o.callJson(".getSimpleData").sync();
    
    using Newtonsoft.Json.Linq;
    
    // c8o is a C8o instance
    JObject jObject = c8o.CallJson(".getSimpleData").Sync();
    
 
    import SwiftyJSON
    
    // c8o is a C8o instance
    let JObject : JSON = try! c8o.callJson(".getSimpleData")!.sync()!
    
    import { C8o, C8oSettings } from "c8osdkangular"
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter syntax 
    let result = await this.c8o.callJson('.login')
                        .async();

    // Here using Javascript's Promises with then/catch syntax
    this.c8o.callJson(".login")
        .async()
        .then((response)=>{
        //handle result
        });

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README
    this.c8o.callJson(".login")
        .then((response)=>{
        //handle result
        });
    
    import { C8o, C8oSettings } from "c8osdkjs"
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter syntax 
    let result = await this.c8o.callJson('.login')
                        .async();

    // Here using Javascript's Promises with then/catch syntax
    this.c8o.callJson(".login")
        .async()
        .then((response)=>{
        //handle result
        });

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README
    this.c8o.callJson(".login")
        .then((response)=>{
        //handle result
        });
    
    import {C8o, C8oSettings} from "react-native-c8osdk";
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter syntax 
    let result = await this.c8o.callJson('.login')
                        .async();

    // Here using Javascript's Promises with then/catch syntax
    this.c8o.callJson(".login")
        .async()
        .then((response)=>{
        //handle result
        });

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README
    this.c8o.callJson(".login")
        .then((response)=>{
        //handle result
        });
    

Calls parameters

The call method expects the requester string of the following syntax:

For a transaction: [project].connector.transaction For a sequence: [project].sequence The project name is optional, i.e. if not specified, the project specified in the endpoint will be used.

Convertigo requestables generally needs key/value parameters. The key is always a string and the value can be any object but a string is the standard case.

Here a sample with JSON but this would be the same for XML calls:

    // the common way with parameters
    JSONObject jObject = c8o.callJson(".getSimpleData",
    "firstname", "John",
    "lastname", "Doe"
    ).sync();

    // the verbose way
    Map<String, Object> parameters = new HashMap<String, Object>();
    parameters.put("firstname", "John");
    parameters.put("lastname", "Doe");
    JSONObject jObject = c8o.callJson(".getSimpleData", parameters).sync();
    
    // the common way with parameters
    JObject jObject = c8o.CallJson(".getSimpleData",
    "firstname", "John",
    "lastname", "Doe"
    ).Sync();

    // the verbose way
    IDictionnary<string, object> parameters = new Dictionnary<string, object>();
    parameters["firstname"] = "John";
    parameters["lastname"] = "Doe";
    JSONObject jObject = c8o.CallJson(".getSimpleData", parameters).Sync();
    
 
    // the common way with parameters
    let JObject : JSON = try! c8o.callJson(".getSimpleData",
                parameters:
                "firstname", "John",
                "lastname", "Doe"
    )!.sync()!

    // the verbose way
    var parameters : Dictionary<String, AnyObject> = Dictionary<String, AnyObject>()
    parameters["firstname"] = "John"
    parameters["lastname"] = "Doe"
    let JSONObject : JSON = try! c8o.callJson(".getSimpleData", parameters: parameters)!.sync()!
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter syntax
    let result = await this.c8o.callJsonObject('.login', {
                            login: "barnett.christine",
                            password: "mySuperPassword123"
                            })
                            .async();

    // Here using Javascript's Promises with then/catch syntax
    this.c8o.callJsonObject('.login', {
        login: "barnett.christine",
        password: "mySuperPassword123"
    })
    .async()
    .then((response)=>{
        // handle result
    });

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README.
    this.c8o.callJsonObject(".login",{
        login: "barnett.christine",
        password: "mySuperPassword123"
        })
        .then((response)=>{
        //handle result
        });
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter syntax
    let result = await this.c8o.callJsonObject('.login', {
                            login: "barnett.christine",
                            password: "mySuperPassword123"
                            })
                            .async();

    // Here using Javascript's Promises with then/catch syntax
    this.c8o.callJsonObject('.login', {
        login: "barnett.christine",
        password: "mySuperPassword123"
    })
    .async()
    .then((response)=>{
        // handle result
    });

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README.
    this.c8o.callJsonObject(".login",{
        login: "barnett.christine",
        password: "mySuperPassword123"
        })
        .then((response)=>{
        //handle result
        });
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter syntax
    let result = await this.c8o.callJson('.login', {
                            login: "barnett.christine",
                            password: "mySuperPassword123"
                            })
                            .async();

    // Here using Javascript's Promises with then/catch syntax
    this.c8o.callJson('.login', {
        login: "barnett.christine",
        password: "mySuperPassword123"
    })
    .async()
    .then((response)=>{
        // handle result
    });

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README.
    this.c8o.callJson(".login",{
        login: "barnett.christine",
        password: "mySuperPassword123"
        })
        .then((response)=>{
        //handle result
        });
    

Working with threads

Maybe you noticed that the calls methods doesn’t return the result directly and that all the sample code chains to the .sync() method.

This is because the calls methods return a C8oPromise instance. That allows the developer to choose if he wants to block the current thread, make an async request or get the response in a callback.

The .sync() method locks the current thread and return the result as soon as it’s avalaible. Of course this should not be used in an Android UI thread as this will result to a frozen UI untill data is returned by the server. In Android you should use the .sync() method only in worker threads.

However The .Net language offers the async/await mechanism that allows to wait and use the result without blocking the current thread.

Of course this does not apply to Angular as there is not threading for this framework.

    // lock the current thread while the request is done
    JSONObject jObject = c8o.callJson(".getSimpleData").sync();
    // the response can be used in this scope
    
    // lock the current thread while the request is done
    JObject jObject = c8o.CallJson(".getSimpleData").Sync();
    // the response can be used in this scope
    
    public async Task OnClick(object sender, EventArgs args)
    {
    // doesn't lock the current thread, needs to be in a 'async' scope (function or method)
    JObject jObject = await c8o.CallJson(".getSimpleData").Async();
    // the response can be used in this scope
    
    }
    
 
    // lock the current thread while the request is done
    let JSONObject : JSON = try! c8o.callJson(".getSimpleData")!.sync()!
    // the response can be used in this scope
    

As in many cases, locking the current thread is not recommended, the .then() method allows to register a callback that will be executed on a worker thread.

The .thenUI() method does the same but the callback will be executed on a UI thread. This is useful for quick UI widgets updates.

The .then() and .thenUI() callbacks receives as parameters the response and the request parameters.

    // doesn't lock the current thread while the request is done
    c8o.callJson(".getSimpleData").then(new C8oOnResponse<JSONObject>() {
    @Override
    public C8oPromise<JSONObject> run(JSONObject jObject, Map<String, Object> parameters) throws Throwable {
        // the jObject is available, the current code is executed in an another working thread
        
        return null; // return null for a simple call
    }
    });
    // following lines are executed immediately, before the end of the request.

    c8o.callJson(".getSimpleData").thenUI(new C8oOnResponse<JSONObject>() {
    @Override
    public C8oPromise<JSONObject> run(JSONObject jObject, Map<String, Object> parameters) throws Throwable {
        // the jObject is available, the current code is executed in the UI thread
        output.setText(jObject.toString());
        
        return null; // return null for a simple call
    }
    });
    // following lines are executed immediately, before the end of the request.
    
    // doesn't lock the current thread while the request is done
    c8o.CallJson(".getSimpleData").Then((jObject, parameters) =>
    {
    // the jObject is available, the current code is executed in an another working thread
    
    return null; // return null for a simple call
    }
    });
    // following lines are executed immediately, before the end of the request.

    c8o.CallJson(".getSimpleData").ThenUI((jObject, parameters) =>
    {
    // the jObject is available, the current code is executed in the UI thread
    Output.Text = jObject.ToString();
    
    return null; // return null for a simple call
    }
    });
    // following lines are executed immediately, before the end of the request.
    
 
    // doesn't lock the current thread while the request is done
    c8o.callJson(".getSimpleData")?.then({ (response, parameters) -> (C8oPromise?) in
        // the jObject is available, the current code is executed in an another working thread
        
        return nil // return nil for a simple call
    })
    // following lines are executed immediately, before the end of the request.
            
    c8o.callJson(".getSimpleData")?.thenUI({ (response, parameters) -> (C8oPromise?) in
        // the jObject is available, the current code is executed in the UI thread
        self.simpleLabel.text = response.stringValue
        
        return nil // return nil for a simple call
    })
    // following lines are executed immediately, before the end of the request.
    

Chaining calls

The .then() or .thenUI() returns a C8oPromise that can be use to chain other promise methods, such as .then() or .thenUI() or failure handlers. The last .then() or .thenUI() must return a null value. .then() or .thenUI() can be mixed but the returning type must be the same: Xml or Json.

    c8o.callJson(".getSimpleData", "callNumber", 1).then(new C8oOnResponse<JSONObject>() {
    @Override
    public C8oPromise<JSONObject> run(JSONObject jObject, Map<String, Object> parameters) throws Throwable {
        // you can do stuff here and return the next C8oPromise<JSONObject> instead of deep nested blocks
        return c8o.callJson(".getSimpleData", "callNumber", 2);
    }
    }).thenUI(new C8oOnResponse<JSONObject>() { // use .then or .thenUI is allowed
    @Override
    public C8oPromise<JSONObject> run(JSONObject jObject, Map<String, Object> parameters) throws Throwable {
        // you can do stuff here and even modify previous parameters
        parameters.put("callNumber", 3);
        parameters.put("extraParameter", "ok");
        return c8o.callJson(".getSimpleData", parameters);
    }
    }).then(new C8oOnResponse<JSONObject>() { // use .then or .thenUI is allowed
    @Override
    public C8oPromise<JSONObject> run(JSONObject jObject, Map<String, Object> parameters) throws Throwable {
        // you can do stuff here and return null because this is the end of the chain
        return null;
    }
    });
    
    c8o.CallJson(".getSimpleData", "callNumber", 1).Then((jObject, parameters) =>
    {
    // you can do stuff here and return the next C8oPromise<JObject> instead of deep nested blocks
    return c8o.CallJson(".getSimpleData", "callNumber", 2);
    }).ThenUI((jObject, parameters) => // use .Then or .ThenUI is allowed
    {
    // you can do stuff here and even modify previous parameters
    parameters["callNumber"] = 3;
    parameters["extraParameter"] = "ok";
    return c8o.CallJson(".getSimpleData", parameters);
    }).Then((jObject, parameters) =>
    {
    // you can do stuff here and return null because this is the end of the chain
    return null;
    });
    
 
    c8o.callJson(".getSimpleData", parameters: "callNumber", 1)?.then({ (response, parameters) -> (C8oPromise?) in
        // you can do stuff here and return the next C8oPromise instead of deep nested blocks
        return c8o.callJson(".getSimpleData", parameters: "callNumber", 2)
    })?.thenUI({ (response, parameters) -> (C8oPromise?) in
        // you can do stuff here and even modify previous parameters
        var parameters : [String : AnyObject]? = nil
        parameters!["callNumber"] = 3
        parameters!["extraParameter"] = "ok"
        return c8o.callJson(".getSimpleData", parameters: parameters)
    })?.then({ (response, parameters) -> (C8oPromise?) in
        // you can do stuff here and return nil because this is the end of the chain
        return nil
    })
    
    c8o.callJson(".getSimpleData", "callNumber", 1)
    .then((response) => {
        // you can do stuff here and return the next C8oPromise instead of deep nested blocks
        return c8o.callJson(".getSimpleData", "callNumber", 2);
    })
    .then((response)=>{
    // you can do stuff here and even modify previous parameters
    parameters["callNumber"] = 3;
    parameters["extraParameter"] = "ok";
    return c8o.callJsonObject(".getSimpleData", parameters);
    })
    .then((response)=>{
    // you can do stuff here and return null because this is the end of the chain
    return null;
    })
    
    c8o.callJson(".getSimpleData", "callNumber", 1)
    .then((response) => {
        // you can do stuff here and return the next C8oPromise instead of deep nested blocks
        return c8o.callJson(".getSimpleData", "callNumber", 2);
    })
    .then((response)=>{
    // you can do stuff here and even modify previous parameters
    parameters["callNumber"] = 3;
    parameters["extraParameter"] = "ok";
    return c8o.callJsonObject(".getSimpleData", parameters);
    })
    .then((response)=>{
    // you can do stuff here and return null because this is the end of the chain
    return null;
    })
    
    c8o.callJson(".getSimpleData", "callNumber", 1)
    .then((response) => {
        // you can do stuff here and return the next C8oPromise instead of deep nested blocks
        return c8o.callJson(".getSimpleData", "callNumber", 2);
    })
    .then((response)=>{
    // you can do stuff here and even modify previous parameters
    parameters["callNumber"] = 3;
    parameters["extraParameter"] = "ok";
    return c8o.callJsonObject(".getSimpleData", parameters);
    })
    .then((response)=>{
    // you can do stuff here and return null because this is the end of the chain
    return null;
    })
    

Handling failures

A call can throw an error for many reasons: technical failure, network error and so on.

The standard try/catch should be used to handle this.

This is the case for the .sync() and .async() methods: if an exception occurs during the request execution, the original exception is thrown by the method and can be encapsulated in a C8oException.

    try {
    c8o.callJson(".getSimpleData").sync();
    } catch (Exception exception) {
    // process the exception
    }
    
    try
    {
    c8o.CallJson(".getSimpleData").Sync();
    // or in an async scope
    await c8o.CallJson(".getSimpleData").Async();
    }
    catch (Exception exception)
    {
    // process the exception
    }
    
 
    do{
        try c8o.callJson(".getSimpleData")!.sync()
    } catch let Exception as NSError{
        // process the exception
    }
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter
    try{
    let result = await this.c8o.callJsonObject('.login', {
                    login: "barnett.christine",
                    password: "mySuperPassword123"
                }).async();
    }
    catch(error){
    // Do something with the error
    }
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter
    try{
    let result = await this.c8o.callJsonObject('.login', {
                    login: "barnett.christine",
                    password: "mySuperPassword123"
                }).async();
    }
    catch(error){
    // Do something with the error
    }
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above, and '.login' is the name of a sequence of your project

    // Here using Javascript's Promises with awaiter
    try{
    let result = await this.c8o.callJson('.login', {
                    login: "barnett.christine",
                    password: "mySuperPassword123"
                }).async();
    }
    catch(error){
    // Do something with the error
    }
    

When you use the .then() or the .thenUI() methods, the try/catch mechanism can’t catch a “future” exception or throwable: you have to use the .fail() or .failUI() methods at the end on the promise chain.

One fail handler per promise chain is allowed. The fail callback provide the object thrown (like an Exception) and the parameters of the failed request.

    c8o.callJson(".getSimpleData", "callNumber", 1).then(new C8oOnResponse<JSONObject>() {
        @Override
        public C8oPromise<JSONObject> run(JSONObject jObject, Map<String, Object> parameters) throws Throwable {
          return c8o.callJson(".getSimpleData", "callNumber", 2);
        }
      }).thenUI(new C8oOnResponse<JSONObject>() {
        @Override
        public C8oPromise<JSONObject> run(JSONObject jObject, Map<String, Object> parameters) throws Throwable {
          return null;
        }
      }).fail(new C8oOnFail() {
        @Override
        public void run(Throwable throwable, Map<String, Object> parameters) {
          // throwable catched from the first or the second callJson, can be an Exception
          // this code runs in a worker thread
          
        }
      });
      
      c8o.callJson(".getSimpleData", "callNumber", 1).then(new C8oOnResponse<JSONObject>() {
        @Override
        public C8oPromise<JSONObject> run(JSONObject jObject, Map<String, Object> parameters) throws Throwable {
          return c8o.callJson(".getSimpleData", "callNumber", 2);
        }
      }).thenUI(new C8oOnResponse<JSONObject>() {
        @Override
        public C8oPromise<JSONObject> run(JSONObject jObject, Map<String, Object> parameters) throws Throwable {
          return null;
        }
      }).failUI(new C8oOnFail() {
        @Override
        public void run(Throwable throwable, Map<String, Object> parameters) {
          // throwable catched from the first or the second callJson, can be an Exception
          // this code runs in a UI thread
          
        }
      });
    
    c8o.CallJson(".getSimpleData", "callNumber", 1).Then((jObject, parameters) =>
    {
    return c8o.CallJson(".getSimpleData", "callNumber", 2);
    }).ThenUI((jObject, parameters) =>
    {
    return null;
    }).Fail((exception, parameters) =>
    {
        // exception catched from the first or the second CallJson, can be an Exception
        // this code runs in a worker thread
        
    });

    c8o.CallJson(".getSimpleData", "callNumber", 1).Then((jObject, parameters) =>
    {
    return c8o.CallJson(".getSimpleData", "callNumber", 2);
    }).ThenUI((jObject, parameters) =>
    {
    return null;
    }).FailUI((exception, parameters) =>
    {
        // exception catched from the first or the second CallJson, can be an Exception
        // this code runs in a UI thread
        
    });
    
 
    c8o.callJson(".getSimpleData", parameters: "callNumber", 1)?.then({ (jObject, parameters) -> (C8oPromise?) in
        return c8o.callJson(".getSimpleData", parameters: "callNumber", 2)
    })?.thenUI({ (response, parameters) -> (C8oPromise?) in
        return nil
    })?.fail({ (exception, parameters) in
        // exception catched from the first or the second CallJson, can be an Exception
        // this code runs in a worker thread
        
    })
            
    c8o.callJson(".getSimpleData", parameters: "callNumber", 1)?.then({ (jObject, parameters) -> (C8oPromise?) in
        return c8o.callJson(".getSimpleData", parameters: "callNumber", 2)
    })?.thenUI({ (jObject, parameters) -> (C8oPromise?) in
        return nil
    })?.failUI({ (exception, parameters) in
        // exception catched from the first or the second CallJson, can be an Exception
        // this code runs in a UI thread
        
    })
    
    // Here using Javascript's Promises
    this.c8o.callJsonObject('.login', {
                    login: "barnett.christine",
                    password: "mySuperPassword123"
                }).
                .async()
                .then((response)=>{
                //handle result
                })
                .catch((error)=>{
                // Do something with the error
                })

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README.
    this.c8o.callJsonObject('.login', {
                    login: "barnett.christine",
                    password: "mySuperPassword123"
                }).
                .then((response)=>{
                //handle result
                })
                .fail((error)=>{
                // Do something with the error
                })
    
    // Here using Javascript's Promises
    this.c8o.callJsonObject('.login', {
                    login: "barnett.christine",
                    password: "mySuperPassword123"
                }).
                .async()
                .then((response)=>{
                //handle result
                })
                .catch((error)=>{
                // Do something with the error
                })

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README.
    this.c8o.callJsonObject('.login', {
                    login: "barnett.christine",
                    password: "mySuperPassword123"
                }).
                .then((response)=>{
                //handle result
                })
                .fail((error)=>{
                // Do something with the error
                })
    
    // Here using Javascript's Promises
    this.c8o.callJson('.login', {
                    login: "barnett.christine",
                    password: "mySuperPassword123"
                }).
                .async()
                .then((response)=>{
                //handle result
                })
                .catch((error)=>{
                // Do something with the error
                })

    // Using C8oPromise that allow for example progress and Live. C8oPromise is described in Api doc in section Api documentation of this README.
    this.c8o.callJson('.login', {
                    login: "barnett.christine",
                    password: "mySuperPassword123"
                }).
                .then((response)=>{
                //handle result
                })
                .fail((error)=>{
                // Do something with the error
                })
    

Writing the device logs to the Convertigo server

An application developer usually adds log information in his code. This is useful for the code execution tracking, statistics or debugging.

The Convertigo Client SDK offers an API to easily log on the standard device logger, generally in a dedicated console. To see this console, a device must be physically connected on a computer.

Fortunately, the same API also send log to the Convertigo server and they are merged with the server log. You can easily debug your device and server code on the same screen, on the same timeline. Logs from a device contain metadata, such as the device UUID and can help to filter logs on the server.

A log level must be specified:

  • Fatal: used for critical error message
  • Error: used for common error message
  • Warn: used for not expected case
  • Info: used for high level messages
  • Debug: used for help the developer to understand the execution
  • Trace: used for help the developer to trace the code To write a log string, use the C8oLogger instance of a C8o instance:
    try {
        c8o.log.info("hello world!"); // the message can be a simple string
      } catch (Exception e) {
        c8o.log.error("bye world...", e); // the message can also take an Exception argument
      }
      if (c8o.log.isDebug()) { // check if currents log levels are enough
        // enter here only if a log level is 'trace' or 'debug', can prevent unnecessary CPU usage
        String msg = serializeData(); // compute a special string, like a Document serialization
        c8o.log.debug(msg);
      }
    
    try
    {
    c8o.Log.Info("hello world!"); // the message can be a simple string
    }
    catch (Exception exception)
    {
    c8o.Log.Error("bye world...", e); // the message can also take an Exception argument
    }
    if (c8o.Log.IsDebug()) // check if currents log levels are enough
    {
    // enter here only if a log level is 'trace' or 'debug', can prevent unnecessary CPU usage
    string msg = serializeData(); // compute a special string, like a Document serialization
    c8o.Log.Debug(msg);
    }
    
 
    do{
        try c8o.log.info("hello world!") // the message can be a simple string
    }
    catch let e as C8oException{
        c8o.log.error("bye world...", exceptions: e) // the message can also take an Exception argument
    }
    if(c8o.log.isDebug){ // check if currents log levels are enough
        // enter here only if a log level is 'trace' or 'debug', can prevent unnecessary CPU usage
        let msg : String  = serializeData() // compute a special string, like a Document serialization
        c8o.log.debug(msg)
    }
    
    try {
        c8o.log.info("hello world!"); // the message can be a simple string
    } catch (error) {
        c8o.log.error("bye world...", error); // the message can also take an error argument
    }
    if (c8o.log.isDebug) { // check if currents log levels are enough
        // enter here only if a log level is 'trace' or 'debug', can prevent unnecessary CPU usage
        let msg : string = serializeData(); // compute a special string, like a Document serialization
        c8o.log.debug(msg);
    }
    
    try {
        c8o.log.info("hello world!"); // the message can be a simple string
    } catch (error) {
        c8o.log.error("bye world...", error); // the message can also take an error argument
    }
    if (c8o.log.isDebug) { // check if currents log levels are enough
        // enter here only if a log level is 'trace' or 'debug', can prevent unnecessary CPU usage
        let msg : string = serializeData(); // compute a special string, like a Document serialization
        c8o.log.debug(msg);
    }
    
    try {
        c8o.log.info("hello world!"); // the message can be a simple string
    } catch (error) {
        c8o.log.error("bye world..." + JSON.stringify(error)); // the message can also take an error argument
    }
    if (c8o.log.isDebug) { // check if currents log levels are enough
        // enter here only if a log level is 'trace' or 'debug', can prevent unnecessary CPU usage
        let msg : string = serializeData(); // compute a special string, like a Document serialization
        c8o.log.debug(msg);
    }
    

A C8oLogger have 2 log levels, one for local logging and the other for the remote logging. With the Android SDK, the local logging is set by the logcat options. With the .Net SDK, the local logging depends of the LogLevelLocal setting of C8oSettings.

The remote logging level is enslaved by Convertigo server Log levels property: devices output logger. In case of failure, the remote logging is disabled and cannot be re-enabled for the current C8o instance. It can also be disabled using the LogRemote setting of C8oSettings, enabled with true (default) and disabled with false.

To monitor remote logging failure, a LogOnFail handler can be registered with the C8oSetting.

The Convertigo Client SDK itself writes logs. They can be turned off using the LogC8o setting of C8oSettings, enabled with true (default) and disabled with false.

    new C8oSetting()
    .setLogC8o(false)    // disable log from the Convertigo Client SDK itself
    .setLogRemote(false); // disable remote logging
    // or
    new C8oSetting().setLogOnFail(new C8oOnFail() {
    @Override
    public void run(Throwable throwable, Map<String, Object> parameters) {
        // the throwable contains the cause of the remote logging failure
    }
    });
    
    new C8oSetting()
    .SetLogC8o(false)    // disable log from the Convertigo Client SDK itself
    .SetLogRemote(false) // disable remote logging
    .SetLogLevelLocal(C8oLogLevel.TRACE);
    // or
    new C8oSetting().SetLogOnFail((exception, parameters) =>
    {
    // the exception contains the cause of the remote logging failure
    });
    
 
    C8oSettings()
        .setLogC8o(false)   // disable log from the Convertigo Client SDK itself
        .setLogRemote(false) // disable remote logging
        .setLogLevelLocal(C8oLogLevel.TRACE)
        // or
    C8oSettings().setLogOnFail { (exception, parameters) -> (Void) in
        // the exception contains the cause of the remote logging failure
    }
    
    C8oSettings()
        .setLogC8o(false)   // disable log from the Convertigo Client SDK itself
        .setLogRemote(false) // disable remote logging
        .setLogLevelLocal(C8oLogLevel.TRACE);
    
    C8oSettings()
        .setLogC8o(false)   // disable log from the Convertigo Client SDK itself
        .setLogRemote(false) // disable remote logging
        .setLogLevelLocal(C8oLogLevel.TRACE);
    
    C8oSettings()
        .setLogC8o(false)   // disable log from the Convertigo Client SDK itself
        .setLogRemote(false) // disable remote logging
        .setLogLevelLocal(C8oLogLevel.TRACE);
    

Using the Local Cache

Sometimes we would like to use local cache on C8o calls and responses, in order to:

  • save network traffic between the device and the server,
  • be able to display data when the device is not connected to the network. The Local Cache feature allows to store locally on the device the responses to a C8o call, using the variables and their values as cache key.

To use the Local Cache, add to a call a pair parameter of C8oLocalCache.PARAM and a C8oLocalCache instance. The constructor of C8oLocalCache needs some parameters:

  • C8oLocalCache.Priority (SERVER / LOCAL): defines whether the response should be retrieved from local cache or from Convertigo server when the device can access the network. When the device has no network access, the local cache response is used.
  • ttl: defines the time to live of the cached response, in milliseconds. If no value is passed, the time to live is infinite.
  • enabled: allows to enable or disable the local cache on a Convertigo requestable, default value is true.
        // return the response if is already know and less than 180 sec else call the server
    c8o.callJson(".getSimpleData",
    C8oLocalCache.PARAM, new C8oLocalCache(C8oLocalCache.Priority.LOCAL, 180 * 1000)
    ).sync();

    // same sample but with parameters, also acting as cache keys
    c8o.callJson(".getSimpleData",
    "firstname", "John",
    "lastname", "Doe",
    C8oLocalCache.PARAM, new C8oLocalCache(C8oLocalCache.Priority.LOCAL, 180 * 1000)
    ).sync();

    // make a standard network call with the server
    // but in case of offline move or network failure
    // return the response if is already know and less than 1 hour
    c8o.callJson(".getSimpleData",
    C8oLocalCache.PARAM, new C8oLocalCache(C8oLocalCache.Priority.SERVER, 3600 * 1000)
    ).sync();
    
    // return the response if is already know and less than 180 sec else call the server
    c8o.CallJson(".getSimpleData",
    C8oLocalCache.PARAM, new C8oLocalCache(C8oLocalCache.Priority.LOCAL, 180 * 1000)
    ).Sync();

    // same sample but with parameters, also acting as cache keys
    c8o.CallJson(".getSimpleData",
    "firstname", "John",
    "lastname", "Doe",
    C8oLocalCache.PARAM, new C8oLocalCache(C8oLocalCache.Priority.LOCAL, 180 * 1000)
    ).Sync();

    // make a standard network call with the server
    // but in case of offline move or network failure
    // return the response if is already know and less than 1 hour
    c8o.CallJson(".getSimpleData",
    C8oLocalCache.PARAM, new C8oLocalCache(C8oLocalCache.Priority.SERVER, 3600 * 1000)
    ).Sync();
    
 
    // return the response if is already know and less than 180 sec else call the server
    try! c8o.callJson(".getSimpleData",
            parameters: C8oLocalCache.PARAM, C8oLocalCache(priority: C8oLocalCache.Priority.LOCAL, ttl: 180 * 1000)
    )!.sync()
            
    // same sample but with parameters, also acting as cache keys
    try! c8o.callJson(".getSimpleData",
            parameters: "firstname", "John",
                        "lastname", "Doe",
            C8oLocalCache.PARAM, C8oLocalCache(priority: C8oLocalCache.Priority.LOCAL, ttl: 180 * 1000)
    )!.sync()
            
    // make a standard network call with the server
    // but in case of offline move or network failure
    // return the response if is already know and less than 1 hour
    try! c8o.callJson(".getSimpleData",
                parameters: C8oLocalCache.PARAM, C8oLocalCache(priority: C8oLocalCache.Priority.SERVER, ttl: 3600 * 1000)
    )!.sync()
    
    // return the response if is already know and less than 180 sec else call the server

    c8o.callJson(".getSimpleData",
            C8oLocalCache.PARAM, new C8oLocalCache(Priority.LOCAL, 180 * 1000)
        )
        .then((response: any, _) => {
            // do stuff
        });

    // same sample but with parameters, also acting as cache keys

    c8o.callJson(".getSimpleData",
            "firstname", "John",
            "lastname", "Doe",
            C8oLocalCache.PARAM, new C8oLocalCache(Priority.LOCAL, 180 * 1000)
        )
        .then((response: any, _) => {
            // do stuff
        });

    // make a standard network call with the server
    // but in case of offline move or network failure
    // return the response if is already know and less than 1 hour
    c8o.callJson(".getSimpleData",
            C8oLocalCache.PARAM, new C8oLocalCache(Priority.SERVER, 3600 * 1000)
        )
        .then((response: any, _) => {
            // do stuff
        });
    
    // Return the response if is already know and less than 180 seconds else call the server
    this.c8o.callJsonObject(".getSimpleData",
                {
                "__localCache": new C8oLocalCache(Priority.LOCAL, 180 * 1000)
                })
                .then((response)=>{
                // Do stuff 
                });

    // same sample but with parameters, also acting as cache keys
    this.c8o.callJsonObject(".getSimpleData",
                {
                "firstname": "John",
                    "lastname": "Doe",
                "__localCache": new C8oLocalCache(Priority.LOCAL, 180 * 1000)
                })
                .then((response)=>{
                // Do stuff 
                });
    // make a standard network call with the server
    // but in case of offline move or network failure
    // return the response if is already know and less than 1 hour
    this.c8o.callJsonObject(".getSimpleData",
                {
                "__localCache": new C8oLocalCache(Priority.SERVER, 3600 * 1000)
                })
                .then((response)=>{
                // Do stuff 
                });
    
    // Return the response if is already know and less than 180 seconds else call the server
    this.c8o.callJson(".getSimpleData",
                {
                "__localCache": new C8oLocalCache(Priority.LOCAL, 180 * 1000)
                })
                .then((response)=>{
                // Do stuff 
                });

    // same sample but with parameters, also acting as cache keys
    this.c8o.callJson(".getSimpleData",
                {
                "firstname": "John",
                    "lastname": "Doe",
                "__localCache": new C8oLocalCache(Priority.LOCAL, 180 * 1000)
                })
                .then((response)=>{
                // Do stuff 
                });
    // make a standard network call with the server
    // but in case of offline move or network failure
    // return the response if is already know and less than 1 hour
    this.c8o.callJson(".getSimpleData",
                {
                "__localCache": new C8oLocalCache(Priority.SERVER, 3600 * 1000)
                })
                .then((response)=>{
                // Do stuff 
                });
    

Using the Full Sync

Full Sync enables mobile apps to handle fully disconnected scenarios, still having data handled and controlled by back end business logic. See the presentation of the Full Sync architecture for more details.

Convertigo Client SDK provides a high level access to local data following the standard Convertigo Sequence paradigm. They differ from standard sequences by a fs:// prefix. Calling these local Full Sync requestable will enable the app to read, write, query and delete data from the local database:

  • fs://< database>.create creates the local database if not already exist
  • fs://< database>.view queries a view from the local database
  • fs://< database>.get reads an object from the local database
  • fs://< database>.post writes/update an object to the local database
  • fs://< database>.delete deletes an object from the local database
  • fs://< database>.all gets all objects from the local database
  • fs://< database>.sync synchronizes with server database
  • fs://< database>.replicate_push pushes local modifications on the database server
  • fs://< database>.replicate_pull gets all database server modifications
  • fs://< database>.reset resets a database by removing all the data in it Where fs://< database> is the name of a specific FullSync Connector in the project specified in the endpoint. The fs://< database> name is optional only if the default database name is specified with the method setDefaultDatabaseName on the C8oSetting.

An application can have many databases. On mobile (Android, iOS and Xamarin based) they are stored in the secure storage of the application. On Windows desktop application, they are stored in the user AppData/Local folder, without application isolation.

All platforms can specify a local database prefix that allows many local database copies of the same remote database. Use the method setFullSyncLocalSuffix on the C8oSetting.

    c8o.callJson("fs://base.reset").then(new C8oOnResponse<JSONObject>() { // clear or create the "base" database
        @Override
        public C8oPromise<JSONObject> run(JSONObject json, Map<String, Object> parameters) throws Throwable {
          // json content:
          // { "ok": true }
          return c8o.callJson("fs://base.post", // creates a new document on "base", with 2 key/value pairs
            "firstname", "John",
            "lastname", "Doe"
          );
        }
      }).then(new C8oOnResponse<JSONObject>() {
        @Override
        public C8oPromise<JSONObject> run(JSONObject json, Map<String, Object> parameters) throws Throwable {
          // json content:
          // {
          //   "ok": true,
          //   "id": "6f1b52df",
          //   "rev": "1-b0620371"
          // }
          return c8o.callJson("fs://base.get", "docid", json.getString("id")); // retrieves the complet document from its "docid"
        }
      }).then(new C8oOnResponse<JSONObject>() {
        @Override
        public C8oPromise<JSONObject> run(JSONObject json, Map<String, Object> parameters) throws Throwable {
          // json content:
          // {
          //   "lastname": "Doe",
          //   "rev": "1-b0620371",
          //   "firstname": "John",
          //   "_id": "6f1b52df"
          // }
          c8o.log.info(json.toString(2)); // output the document in the log
          return null;
        }
      });
    
    c8o.CallJson("fs://base.reset").Then((jObject, parameters) => // clear or create the "base" database
    {
    // json content:
    // { "ok": true }
    return c8o.CallJson("fs://base.post", // creates a new document on "base", with 2 key/value pairs
        "firstname", "John",
        "lastname", "Doe"
    );
    }).Then((jObject, parameters) =>
    {
    // json content:
    // {
    //   "ok": true,
    //   "id": "6f1b52df",
    //   "rev": "1-b0620371"
    // }
    return c8o.CallJson("fs://base.get", "docid", json["id"].ToString()); // retrieves the complet document from its "docid"
    }).Then((jObject, parameters) =>
    {
    // json content:
    // {
    //   "lastname": "Doe",
    //   "rev": "1-b0620371",
    //   "firstname": "John",
    //   "_id": "6f1b52df"
    // }
    c8o.Log.Info(json.ToString()); // output the document in the log
    return null;
    });
    
 
    c8o.callJson("fs://base.reset")?.then({ (json, parameters) -> (C8oPromise?) in
        // json content:
        // { "ok": true }
        return c8o.callJson("fs://base.post", // creates a new document on "base", with 2 key/value pairs
            parameters: "firstname", "John",
            "lastname", "Doe"
        )
    })?.then({ (json, parameters) -> (C8oPromise?) in
        // json content:
        // {
        //   "ok": true,
        //   "id": "6f1b52df",
        //   "rev": "1-b0620371"
        // }
        return c8o.callJson("fs://base.get",
            parameters: "docid", json["id"].stringValue) // retrieves the complet document from its "docid"
    })?.then({ (json, parameters) -> (C8oPromise?) in
        // json content:
        // {
        //   "lastname": "Doe",
        //   "rev": "1-b0620371",
        //   "firstname": "John",
        //   "_id": "6f1b52df"
        // }
        c8o.log.info(json.stringValue) // output the document in the log
        return nil
    })
    
    // clear or create the "base" database
    c8o.callJson("fs://base.reset")
        .then((response: any, parameters:Object) => {
            // response content:
            // { "ok": true }
            return c8o.callJson("fs://base.post", // creates a new document on "base", with 2 key/value pairs
                "firstname", "John",
                "lastname", "Doe"
            );
        })
        .then((response: any, parameters:Object) => {
                // response content:
                // {
                //   "ok": true,
                //   "id": "6f1b52df",
                //   "rev": "1-b0620371"
                // }
                return c8o.callJson("fs://base.get", "docid", response["id"])); // retrieves the complet document from its "docid"
        })
        .then((response: any, parameters:Object) => {
                // response content:
                // {
                //   "lastname": "Doe",
                //   "rev": "1-b0620371",
                //   "firstname": "John",
                //   "_id": "6f1b52df"
                // }
                c8o.log.info(json.stringify(response)); // output the document in the log
            return null;
        });
    
    // clear or create the "base" database
    c8o.callJson("fs://base.reset")
        .then((response: any, parameters:Object) => {
            // response content:
            // { "ok": true }
            return c8o.callJson("fs://base.post", // creates a new document on "base", with 2 key/value pairs
                "firstname", "John",
                "lastname", "Doe"
            );
        })
        .then((response: any, parameters:Object) => {
                // response content:
                // {
                //   "ok": true,
                //   "id": "6f1b52df",
                //   "rev": "1-b0620371"
                // }
                return c8o.callJson("fs://base.get", "docid", response["id"])); // retrieves the complet document from its "docid"
        })
        .then((response: any, parameters:Object) => {
                // response content:
                // {
                //   "lastname": "Doe",
                //   "rev": "1-b0620371",
                //   "firstname": "John",
                //   "_id": "6f1b52df"
                // }
                c8o.log.info(json.stringify(response)); // output the document in the log
            return null;
        });
    
    // clear or create the "base" database
    c8o.callJson("fs://base.reset")
        .then((response: any, parameters:Object) => {
            // response content:
            // { "ok": true }
            return c8o.callJson("fs://base.post",{
                "firstname", "John",
                "lastname", "Doe"}
            );
        })
        .then((response) => {
                // response content:
                // {
                //   "ok": true,
                //   "id": "6f1b52df",
                //   "rev": "1-b0620371"
                // }
                return c8o.callJson("fs://base.get", {"docid", response["id"]})); // retrieves the complet document from its "docid"
        })
        .then((response) => {
                // response content:
                // {
                //   "lastname": "Doe",
                //   "rev": "1-b0620371",
                //   "firstname": "John",
                //   "_id": "6f1b52df"
                // }
                c8o.log.info(json.stringify(response)); // output the document in the log
            return null;
        });
    

Replicating Full Sync databases

Full Sync has the ability to replicate mobile and Convertigo server databases over unreliable connections still preserving integrity. Data can be replicated in upload or download or both directions. The replication can also be continuous: a new document is instantaneously replicated to the other side.

The client SDK offers the progress and progressUI handlers to monitor the replication progression thanks to a C8oProgress instance. The then and thenUI handlers are triggered once the initiation replication is done. When triggered, this means that all documents are synced at this time but future documents can still be replicated in case of continuous replication.

    c8o.callJson("fs://base.replication_pull").then(new C8oOnResponse<JSONObject>() { // launches a database replication from the server to the device
        @Override
        public C8oPromise<JSONObject> run(JSONObject json, Map<String, Object> parameters) throws Throwable {
          // json content:
          // { "ok": true }
          // the documents are retrieved from the server and can be used
          return null;
        }
      }).progress(new C8oOnProgress() {
        @Override
        public void run(C8oProgress progress) throws Throwable {
          // this code runs after each progression event
          // progress.getTotal() is calculated and grows up then progress.getCurrent() increases to the total
          c8o.log.info("progress: " + progress);
        }
      });
    
    c8o.CallJson("fs://base.replication_pull").Then((jObject, parameters) => // launches a database replication from the server to the device
    {
    // json content:
    // { "ok": true }
    // the documents are retrieved from the server and can be used
    return null;
    }).Progress((progress) =>
    {
    // this code runs after each progression event
    // progress.Total is calculated and grows up then progress.Current increases to the total
    c8o.Log.Info("progress: " + progress);
    });
    
 
    c8o.callJson("fs://base.replication_pull")?.then({ (json, parameters) -> (C8oPromise?) in  // launches a database replication from the server to the device
        // json content:
        // { "ok": true }
        // the documents are retrieved from the server and can be used
        return nil
        
    })?.progress({ (progress) in
        // this code runs after each progression event
        // progress.Total is calculated and grows up then progress.Current increases to the total
        c8o.log.info("progress: " + progress.description)
    })
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above.

    this.c8o.callJson('.login')
    .then((response)=>{
        if(response == "ok"){
        // The progress can be handled only with C8oPromise,
        // replication_pull can also be sync or replication_push
        this.c8o.callJson('fs://base.replication_pull')
            .then((response)=>{
            // Do stuff with response
            })
            .progress((progress)=>{
            // Do stuff with progress
            });
        }
    });
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above.

    this.c8o.callJson('.login')
    .then((response)=>{
        if(response == "ok"){
        // The progress can be handled only with C8oPromise,
        // replication_pull can also be sync or replication_push
        this.c8o.callJson('fs://base.replication_pull')
            .then((response)=>{
            // Do stuff with response
            })
            .progress((progress)=>{
            // Do stuff with progress
            });
        }
    });
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above.

    // The progress can be handled only with C8oPromise,
    // replication_pull can also be sync or replication_push
    this.c8o.callJson('fs://base.replication_pull')
    .then((response)=>{
        // Do stuff with response
    })
    .progress((progress)=>{
        // Do stuff with progress
    });
    

A device cannot pull private documents or push any document without authentication. A session must be established before and the Convertigo server must authenticate the session (using the Set authenticated user step for example).

    c8o.callJson(".login", "user", user, "pass", pass).then(new C8oOnResponse<JSONObject>() { // launches a database replication from the server to the device
        @Override
        public C8oPromise<JSONObject> run(JSONObject json, Map<String, Object> parameters) throws Throwable {
          // to push documents from the device to the remote database, the session must be authenticated
          // sets the parameter "continuous" true to allow new documents to sync after the initial replication
          return c8o.callJson("fs://base.sync", "continuous", true);
        }
      }).then(new C8oOnResponse<JSONObject>() { 
        @Override
        public C8oPromise<JSONObject> run(JSONObject json, Map<String, Object> parameters) throws Throwable {
          // all the documents at the replication time are retrieved from the server and can be used
          // and local documents are pushed to the server
          return null;
        }
      }).progressUI(new C8oOnProgress() {
        @Override
        public void run(C8oProgress progress) throws Throwable {
          // this code runs after each progression event
          if (progress.isContinuous()) {
            if (progress.isPull) {
              toast("new incomming modification");
            } else {
              toast("local modification synced")
            }
          } else {
            if (progress.isPull) {
              incommingGauge(progress.getCurrent(), progress.getTotal());
            } else {
              outgoingGauge(progress.getCurrent(), progress.getTotal());
            }
          }
        }
      });
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above.
    c8o.CallJson(".login").Then((jObject, parameters) => // login
    {
    if(jObject == "ok"){
        // replication_pull can also be sync or replication_push
        c8o.CallJson("fs://base.replication_pull").Then((jObject, parameters) => // launches a database replication from the server to the device
        {
            // json content:
            // { "ok": true }
            // the documents are retrieved from the server and can be used
            return null;
        }).Progress((progress) =>
        {
            // this code runs after each progression event
            // progress.Total is calculated and grows up then progress.Current increases to the total
            c8o.Log.Info("progress: " + progress);
        });
    }
    }
    
 
    c8o.callJson(".login")?.then({ (json, parameters) -> (C8oPromise?) in
        if(json == "ok"){
            // replication_pull can also be sync or replication_push
            c8o.callJson("fs://base.replication_pull")?.then({ (json, parameters) -> (C8oPromise?) in  // launches a database replication from the server to the device
                // json content:
                // { "ok": true }
                // the documents are retrieved from the server and can be used
                return nil
                
            })?.progress({ (progress) in
                // this code runs after each progression event
                // progress.Total is calculated and grows up then progress.Current increases to the total
                c8o.log.info("progress: " + progress.description)
            })
        }
        return nil
    })
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above.

    // The progress can be handled only with C8oPromise
    this.c8o.callJsonObject('fs://base.replication_pull', {"continuous": true})
        .then((response)=>{
        // Do stuff with response
        })
        .progress((progress)=>{
        // Do stuff with progress
        })

    // this will cancel the previous replication
    this.c8o.callJsonObject('fs://base.replication_pull', {"cancel": true})
        .then((response)=>{
        // Do stuff with response
        })
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above.

    // The progress can be handled only with C8oPromise
    this.c8o.callJsonObject('fs://base.replication_pull', {"continuous": true})
        .then((response)=>{
        // Do stuff with response
        })
        .progress((progress)=>{
        // Do stuff with progress
        })

    // this will cancel the previous replication
    this.c8o.callJsonObject('fs://base.replication_pull', {"cancel": true})
        .then((response)=>{
        // Do stuff with response
        })
    
    // Assuming c8o is a C8o instance properly instanciated and initiated as describe above.

    this.c8o.callJson('.login')
    .then((response)=>{
        if(response == "ok"){
        // The progress can be handled only with C8oPromise,
        // replication_pull can also be sync or replication_push
        this.c8o.callJson('fs://base.replication_pull')
            .then((response)=>{
            // Do stuff with response
            })
            .progress((progress)=>{
            // Do stuff with progress
            });
        }
    });
    

Full Sync FS_LIVE request

Full Sync has the ability to re-execute your fs:// calls if the database is modified. The then or thenUI following a FS_LIVE parameter is re-executed after each database update. Database update can be local modification or remote modification replicated.

This allow you keep your UI synchronized with database documents.

A FS_LIVE parameter must have a string value, its liveid. The liveid allow to cancel a FS_LIVE request.

    c8o.callJson("fs://.view",
        "ddoc", "design",
        "view", "customers",
        C8O.FS_LIVE, "customers").thenUI(new C8oOnResponse<JSONObject>() { // launches a live view
    @Override
    public C8oPromise<JSONObject> run(JSONObject json, Map<String, Object> parameters) throws Throwable {
        // will be call now and after each database update
        updateCustomersUI(json);
        return null;
    }
    });
    
    // cancel the previous FS_LIVE request, can be on application page change for example
    c8o.cancelLive("customers");
    
    c8o.CallJson("fs://.view",
        "ddoc", "design",
        "view", "customers",
        C8O.FS_LIVE, "customers").ThenUI((jObject, parameters) => // launches a live view
    {
    // will be call now and after each database update
    UpdateCustomersUI(jObject);
    return null;
    });
    
    // cancel the previous FS_LIVE request, can be on application page change for example
    c8o.CancelLive("customers");
    
 
    c8o.callJson("fs://.view",
        parameters: "ddoc", "design",
        "view", "customers",
        C8O.FS_LIVE, "customers")?.thenUI({ (json, parameters) -> (C8oPromise?) in  // launches a live view
        // will be call now and after each database update
        updateCustomersUI(json)
    return nil
    })?
    
    // cancel the previous FS_LIVE request, can be on application page change for example
    c8o.cancelLive("customers")
    
    c8o.callJson("fs://.view",
        "ddoc", "design",
        "view", "customers",
        C8O.FS_LIVE, "customers")
        .then((response: any, _) => {  // launches a live view
            // will be call now and after each database update
            updateCustomersUI(json);
            return null;
        });
    
    // cancel the previous FS_LIVE request, can be on application page change for example
    c8o.cancelLive("customers");
    
    c8o.callJson("fs://.view",
        "ddoc", "design",
        "view", "customers",
        C8O.FS_LIVE, "customers")
        .then((response: any, _) => {  // launches a live view
            // will be call now and after each database update
            updateCustomersUI(json);
            return null;
        });
    
    // cancel the previous FS_LIVE request, can be on application page change for example
    c8o.cancelLive("customers");
    
    c8o.callJson("fs://.view",{
        "ddoc", "design",
        "view", "customers",
        C8O.FS_LIVE, "customers"})
        .then((response: any, _) => {  // launches a live view
            // will be call now and after each database update
            updateCustomersUI(json);
            return null;
        });
    
    // cancel the previous FS_LIVE request, can be on application page change for example
    c8o.cancelLive("customers");
    

Full Sync change listener

Sometimes, it’s nice to know what is changed in the database. The Full Sync change listener allow you to monitor what is changed through a JSON object with all details inside.

    C8oFullSyncChangeListener changeListener = new C8oFullSyncChangeListener() {
        @Override
        public void onChange(JSONObject changes) {
            checkChanges(changes);
        }
    };
    
    c8o.addFullSyncChangeListener("base", changeListener); // add this listener for the database "base" ; null or "" while use the default database.
    
    c8o.removeFullSyncChangeListener("base", changeListener); // remove this listener for the database "base" ; null or "" while use the default database.
    
    C8oFullSyncChangeListener changeListener = (changes) =>
    {
        CheckChanges(changes);
    };
    
    c8o.AddFullSyncChangeListener("base", changeListener); // add this listener for the database "base" ; null or "" while use the default database.
    
    c8o.RemoveFullSyncChangeListener("base", changeListener); // remove this listener for the database "base" ; null or "" while use the default database.
    
 
    let changeListener = C8oFullSyncChangeListener(handler: {(changes: JSON) -> () in
        checkChanges(changes)
    })
    
    c8o.addFullSyncChangeListener("base", changeListener) // add this listener for the database "base" ; null or "" while use the default database.
    
    c8o.removeFullSyncChangeListener("base", changeListener) // remove this listener for the database "base" ; null or "" while use the default database.
    
    let changeListener : C8oFullSyncChangeListener = new C8oFullSyncChangeListener((changes:Object)=>{
        checkChanges(changes);
    });
    
    c8o.addFullSyncChangeListener("base", changeListener); // add this listener for the database "base" ; null or "" while use the default database.
    
    c8o.removeFullSyncChangeListener("base", changeListener); // remove this listener for the database "base" ; null or "" while use the default database.
    
    let changeListener : C8oFullSyncChangeListener = new C8oFullSyncChangeListener((changes:Object)=>{
        checkChanges(changes);
    });
    
    c8o.addFullSyncChangeListener("base", changeListener); // add this listener for the database "base" ; null or "" while use the default database.
    
    c8o.removeFullSyncChangeListener("base", changeListener); // remove this listener for the database "base" ; null or "" while use the default database.
    
    this.c8o.addFullSyncChangeListener("databaseName", "anyID")
    .progress((change)=>{
        // triggered at each change on the given databaseName
        this.c8o.log.debug("progress addFullSyncChangeListener");
    })
    .then((resp)=>{
        //resp returns "ok" if the change listener has been correctly added
    })
    .fail((err)=>{
        // Throw any error
    })
    

Large File Transfer programming guide

Large file transfer service description

Convertigo SDK offers a large file transfer service able to download and to upload very large files from Convertigo server to the mobile device. This service is based on FullSync technology. File Transfer service is availble to SDK programmers as an API and uses a special Convertigo Backend project .CAR named lib_FileTransfer.car.

The whole file transfer must be done within a valid session with Convertigo Server identified by a setAuthenticatedUser step executed within a login sequence.

Here is how this service works for downloads:

  • Client must initialize the File Transfer API from a valid c8o object end point, setup monitors and call the start() method.
  • Then, the client app must first execute a custom project’s Sequence on Convertigo server using a standard c8o.callJson(“.customGetMyFile”, …) or callXml(“.customGetMyFile”, …)
  • On the Server, this custom sequence is responsible to get the file from any repository (File system, ECM, HTTP GET, SOAP Request, …) and to call the StoreLocalFileToDatabase Sequence available in the lib_FileTransfer.car project.
  • On Convertigo server, the StoreLocalFileToDatabase Sequence will slice the target file in to chunks and push each chunk in a special download FullSync database
  • Once the slicing is done, the StoreLocalFileToDatabase Sequence will return a TransferID to the calling sequence, and this sequence will return the TransferID to the client app.
  • The Client App will use the SDK file Transfer API to start the Transfer providing this TransferID.
  • The transfer will occur, by “syncing” the chunks. When all the chunks will be replicated, the service will automatically re-assemble the chunks to a transferred file and delete the chunks from the download database
  • A download progress call-back function will be called to monitor the file transfer status
  • If the network breaks, or if the app is killed while the transfer is still running, it will restart automatically when the FileTransfer start() method will be called

And for uploads:

  • Client must initialize the File Transfer API from a valid c8o object end point, setup monitors and call the start() method.
  • Client opens a stream from a resource such as a file or any other streamable resource
  • Then the client calls the Filetransfer.uploadFile() giving the stream and the name of the destination filename on Convertigo server
  • The file transfer starts by slicing the file on the mobile devices in chunks and inserts the chunks in a special download fullsync data base
  • When the file is sliced, the progress monitor call back is called to monitor the upload process
  • When all the chunks are transferred, the file will be available on the server on the destination file name given by the client
  • If the network breaks, or if the app is killed while the transfer is still running, it will restart automatically when the FileTransfer start() method will be called

Convertigo Backend project

As described in the general process, the FileTransfer service needs a Convertigo utility library packaged in the lib_FileTransfer.car project file. You will need to install this library in the Studio and deploy it to the target Convertigo Server. To install in Studio :

  • Use File->New->Project
  • Choose “Libraries->FileTransfer” library Be sure to have a working FullSync environment (CouchDB server installed and running, Convertigo FullSync properly configured…) before using the FileTransfer services

You will have to prepare in your Convertigo backend project a getMyFile sequence responsible for getting the file from a given repository. Within this sequence you will call the lib_FileTransfer.StoreLocalFileToDatabase sequence passing as filepath variable the path of the file you want to transfer to the mobile device. The lib_FileTransfer.StoreLocalFileToDatabase will return a TransferID you will be able to “source” and return from your getMyFile sequence.

Downloading a Large file

Here is some sample code to download a large file… Initializing the FileTransfer Service

    try {
        // We need a Convertigo Endpoint object
        App.c8o = new C8o(this,  "http://my.server.address/convertigo/projects/MyBackEndProject");
    
        // Create a New C8oFileTransfer object on this endpoint
        App.fileTransfer = new C8oFileTransfer(App.c8o);
    
        // Setup a Debug listener
        App.fileTransfer.raiseDebug(new EventHandler<C8oFileTransfer, String>() {
            @Override
            public void on(C8oFileTransfer source, String event) {
                App.c8o.log.info(event);
            }
        });
    
        ... Initialize the progress monitor .. see the "monitoring" part on how to do that....
    
        // Now we have setup all what is needed in our File Transfer Services. We will
        // start the FileTransfer engine. This will make sure no interrupted file transfers
        // are pending and if they are the transfers will be restarted automatically from
        // the last known good chunk
        App.fileTransfer.start();
    
    } catch (C8oException e) {
        e.printStackTrace();
    }
    
    try{
        // We need a Convertigo Endpoint object
        app.c8o = new C8o("http://my.server.address/convertigo/projects/MyBackEndProject");
    
        // Create a New C8oFileTransfer object on this endpoint
        app.fileTransfer = new C8oFileTransfer(app.c8o);
    
        // Setup a Debug listener
        app.fileTransfer.RaiseDebug += (sender, transferStatus) =>
            {
                app.c8o.RunUI(() =>
                {
                    app.c8o.Log.Info(transferStatus);
                });
            };
    
        ... Initialize the progress monitor .. see the "monitoring" part on how to do that....
    
        // Now we have setup all what is needed in our File Transfer Services. We will
        // start the FileTransfer engine. This will make sure no interrupted file transfers
        // are pending and if they are the transfers will be restarted automatically from
        // the last known good chunk
        app.fileTransfer.start();
    }
    catch (Exception e)
    {
    
    }
    
 
    do{
        // We need a Convertigo Endpoint object
        appDelegate.c8o = try C8o(endpoint: "http://my.server.address/convertigo/projects/MyBackEndProject")
    
        // Create a New C8oFileTransfer object on this endpoint
        appDelegate.c8oFileTransfer =  try C8oFileTransfer(c8o: appDelegate.c8o)
    
        // Setup a Debug listener
        appDelegate.c8oFileTransfer?.raiseDebug({ (source, event) in
                   self.appDelegate.c8o?.log.info(event)
               })
        ... Initialize the progress monitor .. see the "monitoring" part on how to do that....
    
        // Now we have setup all what is needed in our File Transfer Services. We will
        // start the FileTransfer engine. This will make sure no interrupted file transfers
        // are pending and if they are the transfers will be restarted automatically from
        // the last known good chunk
        appDelegate.c8oFileTransfer?.start()
    
    }
    catch let e as NSError{
    }
    

Calling Convertigo Server to request a File and get the TransferID

    // We need to Authenticate to the Convertigo server first. For that
    // We assume a call to a "Login" sequence has been done before.

    // Call the custom "RequestFile" sequence in our Convertigo server .. This sequence will
    // use the lib_FileTransfer.StoreLocalFileToDatabase to store our file to be transferred
    // in the transfer database as sliced chunks. This sample code uses callXml but we could
    // have used callJson just as well ...
    App.c8o.callXml(".RequestFile",
        "filename", file.name
    ).thenUI(new C8oOnResponse<Document>() {
        // When done...
        @Override
        public C8oPromise<Document> run(Document response, Map<String, Object> parameters) throws Throwable {
            // get the uuid returned, representing the TransferID.
            String uuid = App.xpath.evaluate("/document/uuid/text()", response);
            if (uuid == null) {
                // No uuid ? some thing went wrong...
                ... Do error stuff...
            }
            if (uuid != null) {
                // Ok we have an uuid so we can start a file transfer
                ... See next part showing how to start a file transfer ....
            }
            return null;
        }
    });
    
    // We need to Authenticate to the Convertigo server first. For that
    // We assume a call to a "Login" sequence has been done before.

    // Call the custom "RequestFile" sequence in our Convertigo server .. This sequence will
    // use the lib_FileTransfer.StoreLocalFileToDatabase to store our file to be transferred
    // in the transfer database as sliced chunks. This sample code uses callXml but we could
    // have used callJson just as well ...
    var doc = await app.c8o.CallXml(".RequestFile",
                    "filename", file.name
                ).Async();
    // When done...
    // get the uuid returned, representing the TransferID.
    string xml = doc.ToString();
    var uuid = doc.XPathSelectElement("/document/uuid");
    if(uuid == null){
        // No uuid ? some thing went wrong...
        ... Do error stuff...
    }
    if (uuid != null) {
        // Ok we have an uuid so we can start a file transfer
        ... See next part showing how to start a file transfer ....
    } 
    
 
    // We need to Authenticate to the Convertigo server first. For that
    // We assume a call to a "Login" sequence has been done before.

    // Call the custom "RequestFile" sequence in our Convertigo server .. This sequence will
    // use the lib_FileTransfer.StoreLocalFileToDatabase to store our file to be transferred
    // in the transfer database as sliced chunks. This sample code uses callXml but we could
    // have used callJson just as well ...
    appDelegate.c8o?.callXml(".RequestFile", parameters: "filename", file!.name
        ).then({ (response, parameters) -> (C8oPromise?) in
            // When done...
            let uuid : String? = response["document"]["uuid"].stringValue
            // get the uuid returned, representing the TransferID.
            if(uuid == nil){
                // No uuid ? some thing went wrong...
                ... Do error stuff...
            }
            else{
                // Ok we have an uuid so we can start a file transfer
                ... See next part showing how to start a file transfer ....
        }
            return nil
    })
    

Starting a file Transfer

    // We have retreived a TransferID by geeting the uuid element from the custom
    // RequestFile server sequence. Now use it to start the file transfer.
    // In this sample we build the path where the file will be stored once all the chunks will be synced. 
    App.fileTransfer.downloadFile(uuid, Environment.getExternalStorageDirectory() + "/" + uuid + "_" + file.name);
    
    // RequestFile server sequence. Now use it to start the file transfer.
    // In this sample we build the path where the file will be stored once all the chunks will be synced.
    await app.fileTransfer.DownloadFile(file.uuid, "C:\TMP\" + file.uuid + "_" + file.name);
    
 
    // RequestFile server sequence. Now use it to start the file transfer.
    // In this sample we build the path where the file will be stored once all the chunks will be synced.

    let path = (NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true))[0]

    try! self.appDelegate.c8oFileTransfer?.downloadFile(self.file!.uuid, filePath: path + "/" + self.file!.uuid + "_" + self.file!.name)
    

Monitoring FileTransfer Progress

    // We setup the progress monitor call back. This must be done before
    // the fileTransfer.start() method is called.
    App.fileTransfer.raiseTransferStatus(new EventHandler<C8oFileTransfer, C8oFileTransferStatus>() {
        @Override
        public void on(C8oFileTransfer source, final C8oFileTransferStatus event) {
            if (event.getState() == C8oFileTransferStatus.StateFinished) {
                // This means that the file transfer is finished; You will find the transferred file
                // in the path you requested when you called the downloadfile method
                    .. do what has to be done with the file ...
            } else {
                if (event.getState() == C8oFileTransferStatus.StateReplicate) {
                    // File is replicating. You can access some status information in the event object
                    progress += " " + event.getCurrent() + 
                                "/" + event.getTotal() + 
                                " (" + event.getProgress() + ")";
                }
            }
        }
    });
    
    // We setup the progress monitor call back. This must be done before
    // the fileTransfer.start() method is called.
    app.fileTransfer.RaiseTransferStatus += (sender, transferStatus) =>
    {
        app.c8o.RunUI(() =>
        {
            if (transferStatus.State == C8oFileTransferStatus.StateFinished) {
                // This means that the file transfer is finished; You will find the transferred file
                // in the path you requested when you called the downloadfile method
                .. do what has to be done with the file ...
            }
            else{
                if (transferStatus.State == C8oFileTransferStatus.StateReplicate) {
                    // File is replicating. You can 

    some status information in the event object
                    progress += " " + transferStatus.Current + "/" + transferStatus.Total + " (" + transferStatus.Progress + ")";
                }
            }
        }
    }
    
 
    // We setup the progress monitor call back. This must be done before
    // the fileTransfer.start() method is called.
    appDelegate.c8oFileTransfer?.raiseTransferStatus({ (source, event) in
        self.appDelegate.c8o?.runUI({
            if(event.state.description == C8oFileTransferStatus.stateFinished.description){
                // This means that the file transfer is finished; You will find the transferred file
                // in the path you requested when you called the downloadfile method
                .. do what has to be done with the file ...
            }
            else{
                if(event.state.description == C8oFileTransferStatus.stateReplicate.description){
                    // File is replicating. You can access some status information in the event object
                    progress += event.current.description + "/" + event.total.description + " (" + event.progress.description + ")"
                }
            }
                    
        })
    })
    

List current transfers

    // Walk over all existing transfers
    for (C8oFileTransferStatus transfer: App.fileTransfer.getAllFiletransferStatus()) {
        String uuid = transfer.getUuid();
        ...
    }
    
    // Walk over all existing transfers
    foreach (var transfer in (await app.fileTransfer.GetAllFiletransferStatus()))
    {
        var uuid = transfer.Uuid;
        ...
    }
    
 
    // Walk over all existing transfers
    for transfer in appDelegate.c8oFileTransfer!.getAllFiletransferStatus() {
        let uuid = transfer.uuid
        ...
    }
    

Cancel file transfers

    // Cancel all file transfers
    App.fileTransfer.cancelFiletransfers();

    // Cancel one file transfer by an uuid string
    App.fileTransfer.cancelFiletransfer(uuid);

    // Cancel one file transfer by a C8oFileTransferStatus
    App.fileTransfer.cancelFiletransfer(fileTransferStatus);
    
    // Cancel all file transfers
    await app.fileTransfer.CancelFiletransfers();

    // Cancel one file transfer by an uuid string
    await app.fileTransfer.CancelFiletransfer(uuid);

    // Cancel one file transfer by a C8oFileTransferStatus
    await app.fileTransfer.CancelFiletransfer(fileTransferStatus);
    
 
    // Cancel all file transfers
    appDelegate.c8oFileTransfer!.cancelFiletransfers()

    // Cancel one file transfer by an uuid string
    appDelegate.c8oFileTransfer!.cancelFiletransfer(uuid)

    // Cancel one file transfer by a C8oFileTransferStatus
    appDelegate.c8oFileTransfer!.cancelFiletransfer(fileTransferStatus)
    

API reference

Android

Dot Net

iOS

Changelog

============================ Version 2.1.0 | 2016-12-16 ============================

NEW FEATURES (1)

  • [Android + iOS + .NET] Add “live” and “changeListener” features

BUGS (2)

  • [Android] Fixed, remote calls now respects the Timeout setting.
  • [Android + iOS] Fixed the usage of .progress and .progressUI after a callXml: no more exception throw.

============================ Version 2.0.7 | 2016-10-12 ============================

NEW FEATURES (1)

  • [Android + iOS + .NET] New filetransfer methods to list and cancel transfers.

IMPROVEMENTS (1)

  • [.NET] Add replication options to C8oSettings.

============================ Version 2.0.6 | 2016-09-26 ============================

IMPROVEMENTS (1)

  • [Android + .NET] Use Couchbase-lite 1.3.1.

BUGS (1)

  • [Android + iOS + .NET] Fixed, deadlock if “cancel” a replication

============================ Version 2.0.5 | 2016-09-12 ============================

IMPROVEMENTS (2)

  • [Android + .NET] Use Couchbase-lite 1.3.0 for Android & .NET (iOS stick to CBL 1.2.1).
  • [Android + iOS + .NET] Add C8o settings to select the fullsync database storage engine and set the fullsync encryption key.

============================ Version 2.0.4 | 2016-08-16 ============================

NEW FEATURES (2)

  • [Android + iOS + .NET] Add the FileTransfer upload feature.
  • [iOS] New Swift SDK with support of the new C8OPromise API.

IMPROVEMENTS (3)

  • [Android + iOS + .NET] API harmonization.
  • [Android + iOS + .NET] fs://.post support plain Object as value (also for merge).
  • [iOS] Use of Alamofire 3.4.1 .

BUGS (2)

  • [Android] Fixed, deadlock in case of “call().sync()” in a background task
  • [Android] Fixed, for Android <= 4.3, documents from a FullSync call are now deep JSONObject.

============================ Version 2.0.3 | 2016-03-22 ============================

NEW FEATURES (2)

  • [Android + .NET] Add the FileTransfer download feature.
  • [Android + .NET] C8o.Log also send the current device UUID.

IMPROVEMENTS (2)

  • [Android + .NET] Handle array or collection C8O call parameters values as mutli-valued variables.
  • [Android + .NET] Allow to change local loglevel and re-enable remote logs.

============================ Version 2.0.2 | 2016-02-02 ============================

BUGS (2)

  • [.NET] Fixed, HTTPS connection with a valid certificate for a WPF application.
  • [Android] Fixed, fs://.post ignore the _id parameter.

============================ Version 2.0.1 | 2016-01-29 ============================

BUGS (1)

  • [Android + .NET] Fixed replication issues (end of replication not correctly detected).

============================ Version 2.0.0 | 2016-01-17 ============================

NEW FEATURES (4)

  • [.NET] Initial version.
  • [Android + .NET] Improve error handling.
  • [Android] Support of the new C8OPromise API.
  • [.NET] Support of the new C8OPromise API.

IMPROVEMENTS (1)

  • [Android + .NET] API harmonization.

BUGS (1)

  • [Android] Fixed, NPE using the local cache feature.

============================ Version 1.0.0 | 2015-10-01 ============================

NEW FEATURES (2)

  • [Android] Initial version.
  • [iOS] Initial version.