Standalone integration using Xamarin

Introduction

In the following we will guide you through creating a simple Xamarin application for Android integrating Sygic 3D navigation engine with the standalone integration.
In this example we will develop a simple application, where you can type in a destination address and through the NAVIGATE button you can start navigation to that address. Prior to that it might be needed to open Sygic navigation app using the MAP button.

Prepare your working environment

For being able to compile your application you need to have Visual Studio (2013 or newer) extended with Xamarin.Android module on your computer. If you don't have it please follow Installation of Xamarin.Android for Windows.
As the key point you need to Download and Install SDK library on your computer.
You may want to install the Sygic navigation on your device in advance. Check Installation of navigation. You will definitely need it at later stages when you want to test your application on device.
Check Run Demo Example.

Create new project

Let's start Visual Studio (2013 or newer) and create the new solution through selecting New Project -> Visual C# -> Android -> Blank App (Android).
Let's define Solution name as MyXamarinDemo3D and Project name as App3D.
The project is automatically populated with several files, while these are relevant:

  • MainActivity.cs, which is the placeholder for the application coding
  • Resources/layout/Main.axml, which will define graphical look of application

The source code (MainActivity.cs) should be generated as follows:

using System;
using Android.App;
using Android.Widget;
using Android.OS;

namespace App3D
{
   public class MainActivity : Activity {

      protected override void onCreate(Bundle bundle) {
          base.onCreate(bundle);
          setContentView(Resource.Layout.Main);
      }
   }
}

The layout resource (Main.axml) should be generated as follows:

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

Include Sygic library

Including Sygic SDK library consists of four steps:

  • add new project named RemoteApiBinding within the existing solution
    through New Project -> Visual C# -> Android -> Binding Library (Android)
    The project will automatically create a project folder structure in the filesystem under the MyXamarinDemo3D solution exposing Jars folder
  • copy RemoteApi.aar from the downloaded SDK package to project's Jars folder
    The aar file is located within the SDK package inside the SDK/Lib/3d.ipc folder.
  • add RemoteApi.aar as an existing item into the Jars folder
    Set the library Properties as Build Action : EmbeddedJar
  • add RemoteApiBinding reference for the App3D project through Add Reference

After successful solution setup the project should have the following appearance in Visual Studio:

Define graphical look

We add three graphical elements into application, which is the EditText for inputting address, the Button btnNavigate for triggering navigation functions, and the Button btnMap for opening navigation if not started yet.
Thus we need to modify the default generated layout to the following form.

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

  <EditText
      android:id="@+id/editAddress"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_alignParentLeft="true"
      android:ems="10" requestFocus="true" />
  <Button
      android:id="@+id/btnMap"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Map" />
  <Button
      android:id="@+id/btnNavigate"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Navigate" />

</LinearLayout>

Code the application

The application will be written solely in the class MainActivity. We demonstrate the development in the following eight steps.

1. Include Sdk Library

Let's add the using clauses for Sygic library to the head of the code so that necessary Api functions can be used later on.

using System;
using Android.App;
using Android.Widget;
using Android.OS;
using Com.Sygic.Sdk.Remoteapi;
using Com.Sygic.Sdk.Remoteapi.Events;
using Com.Sygic.Sdk.Remoteapi.Model;

2. Define application framework using Sygic callback mechanism

Let's extend the original MainActivity class with the following code, which is the typical framework to pick when starting to write an application using Sygic API.

public class MainActivity : Activity, IApiCallback
{
    private Api api;

    public void OnEvent(int p0, string p1)
    {
    }

    public void OnServiceConnected()
    {
    }

    public void OnServiceDisconnected()
    {
    }

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.Main);
        api = Api.Init(this, ..., ..., this);
    }
}

The activity class needs to contain the private instance of Sygic Api (api).
With the applicaton start (the application basically starts at OnCreate) we need to call the Api.Init function, which binds API with the defined callback and with Sygic navigation.
The Api callback is established through the 4th parameter of the Api.init function call. Here we use the trick that we let the MainActivity class inherit from the api callback interface (IApiCallback), which allows defining 3 mandatory functions of the callback (onServiceConnected, onServiceDisconnected, onEvent) directly within the MainActivity class.

3. Bind API to Sygic Navigation engine

The binding means filling the appropriate application and service identificators into Api.Init function. In this example we use the identificator prefix com.sygic.fleet, which corresponds to Sygic Professional Navigation.

api = Api.Init(this, "com.sygic.fleet", "com.sygic.fleet.SygicService", this);

If the example should work with another version of navigation the identificators will need to change. The available identificators are:

  • com.sygic.fleet for Sygic Professional Navigation
  • com.sygic.truck for Sygic 3D Truck Navigation (please note it will cease to support SDK in future)
  • com.sygic.taxi for Sygic 3D Taxi
  • com.sygic.drive for Sygic 2D Fleet

4. Define API opening and closing operations

The opening of API interface is implemented with calling api.Connect at the application start right after Api.Init as follows:

protected void OnCreate(Bundle savedInstanceState) {
        base.OnCreate(savedInstanceState);
        setContentView(Resource.Layout.Main);
        // ...
        api = Api.Init(this, "com.sygic.fleet", "com.sygic.fleet.SygicService", this);
        api.Connect();
    }

and api.RegisterCallback, which typically resides in OnServiceConnected

public void OnServiceConnected() {
        try
        {
            api.RegisterCallback();
        }
        catch (RemoteException ex)
        {
        }
    }

Next, we want to close API with an application exit, and the typical choice is to place api.UnregisterCallback and api.Disconnect inside the application's onDestroy method as follows:

protected override void OnDestroy()
    {
        try
        {
            api.UnregisterCallback();
        }
        catch (RemoteException ex)
        {
        }
        api.Disconnect();
        base.OnDestroy();
    }

5. Open up navigation app

We will use the button btnMap for opening navigation app if it is not running yet. Simple call of api.Show function takes care of it. It starts navigation in case it has not been started yet, while in case it has been already running, it just puts navigation app into foreground. The result of this call is the generation of the event EventAppStarted inside the OnEvent callback shortly in case the navigation app was just started. The whole code can be defined within OnCreate through the button click delegate function.
We will also reuse the EditText widget for messaging of the possible error occurrence.

    protected override void OnCreate(Bundle bundle)
    {
        ...

            edit = FindViewById<EditText>(Resource.Id.editAddress);

            btnMap = FindViewById<Button>(Resource.Id.btnMap);
            btnMap.Click += delegate
            {
                try
                {
                    api.Show(false);
                }
                catch (Exception e)
                {
                    edit.Text = "api show exception";
                }
            };

            ...

    }

6. Invoke navigation function

We will call the navigation function on the button click (btnNavigate) by defining the callback of the button click. Inside the callback function we will read the user typed address of the EditText widget, apply the gecocoding API function getLocationFromAddress, and finally use it with StartNavigation API function. The whole code can be defined within OnCreate.
We will also reuse the EditText widget for messaging of the possible error occurrence.
The successful navigation function call requires the navigation app is already running. This is the developer's responsibility to program a correct flow for opening and possibly closing navigation app. In this example we apply the strategy that the button click is only enabled when navigation app is running. And second, as a final safety we catch possible exceptions of the startNavigation call.

    protected override void OnCreate(Bundle bundle)
    {
        ...

        Button button = FindViewById<Button>(Resource.Id.btnNavigate);
        button.Click += delegate
        {
        string address = edit.Text.ToString();
        Position pos = ApiLocation.LocationFromAddress(address, false, true, 0);
        var wp = new WayPoint(address, pos.GetX(), pos.GetY());
            api.Show(false);
                try
                {
                    ApiNavigation.StartNavigation(wp, ApiNavigation.MessageRouteEnable, false, 0);
                }
                catch (NavigationException ex)
                {
                    edit.Text = ex.Message + ex.Code;
                }
                catch (Exception ex)
                {
                    edit.Text = "low level exception";
                }
       };

       ...
    }

7. Add navigation event capture

Through the OnEvent functionWe we can retrieve various events from navigation and then react on it as desired.
In this example we will catch the event of route calculation being finished (EventRouteComputed) and we will react on it with flashing the message "Computation finished" using Android Toast.
Next events to capture are EventAppStarted and EventAppExit, which we will use to control the btnNavigate button availability (being disabled or enabled) to avoid trying to call navigation functions while navigation app is not running.
The adaptation within the OnEvent function is about filtering out an appropriate event and then execute needed functions as follows:

    public void OnEvent(int eventcode, string data)
    {
        switch (eventcode)
        {
            case ApiEvents.EventRouteComputed:
        {
            RunOnUiThread(() => Toast.MakeText(this, "Computation finished", ToastLength.Short).Show());
            break;
        }
                case ApiEvents.EventAppStarted:
                case ApiEvents.EventAppExit:                
                {
                    RunOnUiThread(() => UpdateButtons() );
                    break;
                }
        }
    }

8. Manage application consistency

We define the function UpdateButtons(), through which we will demonstrate handling the state of this application example to keep consistency with navigation state.
The function checks whether the navigation app is running or not using Api.IsApplicationRunning. In case the navigation is not running we disable the button btnNavigate to prevent calls of the startNavigation function when navigation is not run. In general you might want to protect many API calls with this mechanism.
The function UpdateButtons() is in our example called at multiple places, i.e. wherever there is the possibility the navigation state changes (navigation is started or exit). The places are: OnEvent function receives events, OnServiceConnected and OnServiceDisconnected.

public bool UpdateButtons()
        {
            bool isRun = false;
            try
            {
                isRun = Api.IsApplicationRunning(1000);
                if (isRun)
                {
                    btnNavigate.Enabled = true;
                }
                else
                {
                    btnNavigate.Enabled = false;
                }
            }
            catch (Exception e)
            {
                isRun = false;
            }
            return isRun;
        }

Build and deploy application

Building the application is simple as clicking on Run button in Visual Studio.
The result of the build process is generation of MyXamarinDemo3D.apk, which just needs to be copied to your Android device.
You also need to copy and install Sygic Navigation to device. Please note in this example it should be Sygic Professional Navigation.
For binding to other versions of navigation please change the application identificator inside the Api.Init function.
If you have not done that yet please follow Installation of navigation.

Full sample reference

using System;
using Android.App;
using Android.Widget;
using Android.OS;
using Com.Sygic.Sdk.Remoteapi;
using Com.Sygic.Sdk.Remoteapi.Events;
using Com.Sygic.Sdk.Remoteapi.Model;
using Com.Sygic.Sdk.Remoteapi.Exception;

namespace App3D
{
    [Activity(Label = "App3D", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity, IApiCallback
    {
        private Api api;

        private Button btnNavigate;
        private Button btnMap;
        private EditText edit;

        public void OnEvent(int eventcode, string data)
        {
            switch (eventcode)
            {
                case ApiEvents.EventRouteComputed:
                    {
                        RunOnUiThread(() => Toast.MakeText(this, "Computation finished", ToastLength.Short).Show());
                        break;
                    }
                case ApiEvents.EventAppStarted:
                case ApiEvents.EventAppExit:                
                    {
                        RunOnUiThread(() => UpdateButtons() );
                        break;
                    }
            }
        }

        public void OnServiceConnected()
        {
            try
            {
                api.RegisterCallback();
            }
            catch (RemoteException ex)
            {
                edit.Text = "api register callback exception";
            }
        }

        public void OnServiceDisconnected()
        {
             UpdateButtons();
        }

        public bool UpdateButtons()
        {
            bool isRun = false;
            try
            {
                isRun = Api.IsApplicationRunning(1000);
                if (isRun)
                {
                    btnNavigate.Enabled = true;
                }
                else
                {
                    btnNavigate.Enabled = false;
                }
            }
            catch (Exception e)
            {
                isRun = false;
            }
            return isRun;
        }

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Main);

            edit = FindViewById<EditText>(Resource.Id.editAddress);

            btnMap = FindViewById<Button>(Resource.Id.btnMap);
            btnMap.Click += delegate
            {
                try
                {
                    api.Show(false);
                }
                catch (Exception e)
                {
                    edit.Text = "api show exception";
                }
            };

            Button button = FindViewById<Button>(Resource.Id.btnNavigate);
            button.Click += delegate
            {
                string address = edit.Text.ToString();
                Position pos = ApiLocation.LocationFromAddress(address, false, true, 0);
                var wp = new WayPoint(address, pos.GetX(), pos.GetY());
                api.Show(false);
                try
                {
                    ApiNavigation.StartNavigation(wp, ApiNavigation.MessageRouteEnable, false, 0);
                }
                catch (NavigationException ex)
                {
                    edit.Text = ex.Message + ex.Code;
                }
                catch (Exception ex)
                {
                    edit.Text = "low level exception";
                }
            };

            api = Api.Init(this, "com.sygic.fleet", "com.sygic.fleet.SygicService", this);
            api.Connect();
        }

        protected override void OnDestroy()
        {
            try
            {
                api.UnregisterCallback();
            }
            catch (RemoteException ex)
            {
                // Exception during SDK callback unregistration
            }
            api.Disconnect();
            base.OnDestroy();
        }
    }
}