Skip to content

Example

Pulseway
Pulseway

Let's consider you own an online book store and you would like to know in real time the current inventory, active orders and canceled orders. Here is the current code of the book store instance (this is an example, does not reflect a real book store). We have two base types: Book and Order and the main class BookShop:

public class Book
{
    public Book(string Name, decimal Price)
    {
        this.Name = Name;
        this.Price = Price;
    }
    public string Name;
    public decimal Price;
}
                    
public class Order
{
    public Order(string Client, Book Book)
    {
        this.Client = Client;
        this.Book = Book;
        LastID++;
        ID = LastID;
        PurchaseDate = DateTime.Now.AddMonths(StaticRandom.Next(12)).AddDays(StaticRandom.Next(30));
        Quantity = StaticRandom.Next(50);
        TransactionIdentifier = Guid.NewGuid().ToString();
    }
    public int ID;
    public string Client;
    public Book Book;
    public bool Canceled;
    public DateTime PurchaseDate;
    public int Quantity;
    public string TransactionIdentifier;
    public static int LastID = 0;
}
                
public class BookShop
{
    private readonly List<Order> OrderList = new List<Order>();
    private List<Book> BookList = new List<Book>();
    private List<string> ClientList = new List<string>();
    public BookShop()
    {
        BookList = DataBank.GenerateBooks();
        ClientList = DataBank.GenerateClientNames();
        new Thread(AddOrders).Start();
        Console.ReadLine();
    }
    private void AddOrders()
    {
        while (OrderList.Count != 50)
        {
            OrderList.Add(new Order(ClientList[OrderList.Count], BookList[OrderList.Count]));
            Thread.Sleep(10000);
        }
    }
    private int GetCanceledOrdersCount()
    {
        return OrderList.Count(n => n.Canceled == true);
    }
}
                

DataBank is used to generate random client and book names. The source can be found on the download link at the end of the page.

As soon as our bookstore is getting created every 10 seconds a new order will get generated using a random book and client. All we need to do now is implement Cloud API into our code.

First we need to add a reference to PulsewayCloud.dll which can be downloaded from here. See Figure A

Pulseway Figure A

Now we need to add: using MM.Monitor.Cloud; Right under the last using statement. Now we can start implementing the Cloud API.

Just like a plugin, Cloud API requires unique identifications (ID) for each PageItem and CommandItem so let's start by adding them as constants right below the ClientList The two PickListItems represent a container with Client and Book names that will be used with the PickListInput.

private readonly PickListItems ClientPickList = new PickListItems();
private readonly PickListItems BookPickList = new PickListItems();
private const int PAGE_LIST_ORDERS = 91;
private const int PAGE_LIST_CANCELED_ORDERS = 92;
private const int PAGE_LIST_BOOKS = 93;
private const int COMMAND_CANCEL_ORDER = 1;
private const int COMMAND_CLEAR_DATA = 2;
private const int COMMAND_RESET_ORDER = 3
    

The reason why our page ids start from 91 is because other page id's from 1 to 50 will represent the current order id for easier access. A noticeable difference between Cloud API and a plugin is that Cloud API uses events rather than invoking methods; however this doesn't make it more difficult to work with. We need to subscribe to API's events and the best place to do it is in the class constructor.

instance.OnDetailsRequest += OnDetailsRequest;
instance.OnPageRequest += OnPageRequest;
instance.OnCommandReceived += OnCommandReceived;
instance.OnPageCommandReceived += OnPageCommandReceived;
instance.OnExceptionOccured += OnExceptionOccured;
instance.OnDateTimeInputValueChanged += OnDateTimeInputChanged;
instance.OnNumericInputValueChanged += OnNumericInputValueChanged;
instance.OnTextInputValueChanged += OnTextInputValueChanged;
instance.OnPickListInputValueChanged += OnPickListInputValueChange;
    

Now we need to instruct the service that we are "online" and provide the authentication details. You can add this too into the constructor.

Service instance = new Service("Book Shop", "Cloud", "Running on " + Environment.MachineName, false);
instance.Start("username", "password", "apikey");
    

The last parameter in the constructor represents the offline notification switch. If it's set to true you will receive a notification when the instance goes offline.

Replace username, password and apikey with your details. You can have the API key for your account (for free) on demand at: support@pulseway.com.
This is how your constructor should look like:

public BookShop()
{
    BookList = DataBank.GenerateBooks();
    ClientList = DataBank.GenerateClientNames();
    RegeneratePickLists();
    instance.OnDetailsRequest += OnDetailsRequest;
    instance.OnPageRequest += OnPageRequest;
    instance.OnCommandReceived += OnCommandReceived;
    instance.OnPageCommandReceived += OnPageCommandReceived;
    instance.OnExceptionOccured += OnExceptionOccured;
    instance.OnDateTimeInputValueChanged += OnDateTimeInputChanged;
    instance.OnNumericInputValueChanged += OnNumericInputValueChanged;
    instance.OnTextInputValueChanged += OnTextInputValueChanged;
    instance.OnPickListInputValueChanged += OnPickListInputValueChange;
    instance.Configure("Book Shop", "Cloud", "Running on " + Environment.MachineName, false);
    instance.Start("username", "password", "apikey");
    new Thread(AddOrders).Start();
    Console.ReadLine();
}
    

Notice the new RegeneratePickLists method added right above the event subscriptions. This method is used to pre-create PickListItems that will be used when an Order Detail page is loaded. Here is the code:

private void RegeneratePickLists()
{
    BookPickList.Clear();
    ClientPickList.Clear();
    for (int i = 0; i < BookList.Count; i++)
        BookPickList.Add(new PickListItem(i + 1, BookList[i].Name, BookList[i].Price.ToString()));
    for (int i = 0; i < ClientList.Count; i++)
        ClientPickList.Add(new PickListItem(i + 1, ClientList[i]));
}
    
All we need to do now is implement the subscribers.

void OnDetailsRequest()

This event gets triggered when a client requests the "Groups" container that contains your data See Figure B. This is a perfect place for a main menu where you can add "PageItems" that lead to Lists of items, detailed pages or commands, "CommandItems" that usually affect important parts of your application and "SimpleItems" that display important parameters of your application. In our example we will display the total amount of books in our inventory, total orders and how many canceled orders. Then we 3 pages which represent lists of books, current orders and canceled orders. Finally we want to add a button that will delete all books, and orders and regenerate all the data.

Pulseway Figure B

private void OnDetailsRequest()
{
        Groups container = new Groups();
        Group stats = new Group("Statistics");
        stats.Items.Add(new SimpleItem("Book Count: ", BookList.Count.ToString()));
        stats.Items.Add(new SimpleItem("Orders Count: ", OrderList.Count.ToString()));
        stats.Items.Add(new SimpleItem("Canceled Orders Count: ", GetCanceledOrdersCount().ToString()));
        Group pages = new Group("Lists");
        pages.Items.Add(new PageItem(PAGE_LIST_BOOKS, "List Books"));
        pages.Items.Add(new PageItem(PAGE_LIST_ORDERS, "List Orders"));
        pages.Items.Add(new PageItem(PAGE_LIST_CANCELED_ORDERS, "List Canceled Orders"));
        Group actions = new Group("Actions");
        actions.Items.Add(new CommandItem(COMMAND_CLEAR_DATA, "Reset DataBank"));
        container.Add(stats);
        container.Add(pages);
        container.Add(actions);
        instance.SetDetails(container);
}
    

The last line is how you return the "Groups" container to the mobile client. With Client API you only returned it to the method, here you need to call SetDetails method.

void OnPageRequest(int pageId, string mobileDeviceIdentifier)

OnPageRequest is called when a client asks for the contents of a Page. Unlike the ClientAPI here you get the mobileDeviceIdentifier so that you can add another layer of security where you can create access levels based on device identifiers. On our example we just list our books, orders, canceled orders if it matches their page IDs and if not we display the order details page and we take our OrderID from thePageID. See Figure C

Pulseway Figure C

private void OnPageRequest(int pageId, string mobileDeviceIdentifier)
{
    Groups container = new Groups();
    Group contents = null;
    switch (pageId)
    {
        case PAGE_LIST_BOOKS:
            Console.WriteLine("Page List Books hit");
            contents = new Group("Book List");
            foreach (Book b in BookList)
                contents.Items.Add(new SimpleItem(b.Name, b.Price.ToString()));
            break;
        case PAGE_LIST_ORDERS:
            Console.WriteLine("Page List Orders hit");
            contents = new Group("Order List");
            foreach (Order o in OrderList)
                contents.Items.Add(new PageItem(o.ID, String.Format("Client: {0}\nBook: {1}\nPrice: {2}\nCanceled: {3}",o.Client, o.Book.Name, o.Book.Price, o.Canceled)));
            break;
        case PAGE_LIST_CANCELED_ORDERS:
            Console.WriteLine("Page List Canceled Orders hit");
            contents = new Group("Canceled Order List");
            foreach (Order co in OrderList.Where(q => q.Canceled == true))
                contents.Items.Add(new PageItem(co.ID, String.Format("Client: {0}\nBook: {1}\nPrice: {2}\nCanceled: {3}",co.Client, co.Book.Name, co.Book.Price, co.Canceled)));
            break;
        default:
            Console.WriteLine("Page Order Details hit");
            contents = new Group("Order Details");
            Order current = OrderList.Single(q => q.ID == pageId);
            contents.Items.Add(new PickListInputItem("client." + pageId, "Client: " + current.Client, ClientPickList));
            contents.Items.Add(new PickListInputItem("book." + pageId, "Book: " + current.Book.Name, BookPickList));
            contents.Items.Add(new SimpleItem("Book Price: ", current.Book.Price.ToString(), SimpleItemStyle.INFORMATION));
            contents.Items.Add(new DateTimeInputItem(pageId.ToString(), "Order Date: ", current.PurchaseDate.ToString("dd/MM/yyyy H:mm:ss")));
            contents.Items.Add(new NumberInputItem(pageId.ToString(), "Quantity: ", current.Quantity.ToString()));
            contents.Items.Add(new TextInputItem(pageId.ToString(), "Transaction: ", current.TransactionIdentifier));
            if (!current.Canceled)
            {
                contents.Items.Add(new SimpleItem("Canceled: ", current.Canceled.ToString(), SimpleItemStyle.INFORMATION));
                contents.Items.Add(new CommandItem(COMMAND_CANCEL_ORDER, "Cancel Order"));
            }
            else
            {
                contents.Items.Add(new SimpleItem("Canceled: ", current.Canceled.ToString(), SimpleItemStyle.WARNING));
                contents.Items.Add(new CommandItem(COMMAND_RESET_ORDER, "Reset Order"));
            }
            break;
    }
    container.Add(contents);
    instance.SetPageDetails(pageId, container);
}
    

SetPageDetails is required to send "Groups" container back to the client. Don't forget to add it! As you can see we check if pageId belongs to one of our list pages and if not we will assume it's an Order.

void OnCommandReceived(int commandId, string mobileDeviceIdentifier)

Just like a plugin Cloud API has two Command handlers, one of a Page Command for a root level Command (Commands that originated from "OnDetailsRequest"). In our case we only got one root level command, Clear Data.

private void OnCommandReceived(int commandId, string mobileDeviceIdentifier)
{
    if (commandId == COMMAND_CLEAR_DATA)
    {
        BookList = DataBank.GenerateBooks();
        ClientList = DataBank.GenerateClientNames();
        OrderList.Clear();
        Order.LastID = 0;
    }
}
    

void OnPageCommandReceived(int pageId, int commandId, string mobileDeviceIdentifier)

Now we need to implement our Cancel Order and Reset Order as Page Commands because they get created inside the Order Details page. These commands just change the Canceled field in our Order.

private void OnPageCommandReceived(int pageId, int commandId, string mobileDeviceIdentifier)
{
    if (commandId == COMMAND_CANCEL_ORDER)
            OrderList.Single(q => q.ID == pageId).Canceled = true;
    else if (commandId == COMMAND_RESET_ORDER)
            OrderList.Single(q => q.ID == pageId).Canceled = false;
}
    

void OnExceptionOccured(Exception ex)

If anything unusual happens with Cloud API will report to our instance so that logging and apropiate actions can occur. In our case we host our application inside a Console Application and we only print it to the screen.

private void OnExceptionOccured(Exception ex)
{
    Console.WriteLine(ex.Message);
}
    

void OnDateTimeInputChanged(string inputId, MM.Monitor.Cloud.DateTime inputValue)

OnDateTimeInputChanged occurs when you submit a new value on a DateTimeInput. You will receive the inputId you specified and the new value. In Book Shop example this will represent the Purchase Date of an Order.

private void OnDateTimeInputChanged(string inputId, MM.Monitor.Cloud.DateTime inputValue)
{
    Console.WriteLine(String.Format("DateTimeInput Changed: ID: {0} Value: {1}", inputId, inputValue));
    OrderList.Single(q => q.ID == Int32.Parse(inputId)).PurchaseDate = new System.DateTime(inputValue.Year, inputValue.Month, inputValue.Day, inputValue.Hour, inputValue.Minute, 0);
}
    

void OnTextInputValueChanged(string inputId, string inputValue)

OnTextInputValueChanged will receieve the new value of your text input. In Book Shop example this will represent the Transaction ID of an Order.

private void OnTextInputValueChanged(string inputId, string inputValue)
{
    Console.WriteLine(String.Format("TextInput Changed: ID: {0} Value: {1}", inputId, inputValue));
    OrderList.Single(q => q.ID == Int32.Parse(inputId)).TransactionIdentifier = inputValue;
}

void OnNumericInputValueChanged(string inputId, int inputValue)

OnNumericInputValueChanged will only accept numbers, very useful because it actually shows a digit only keyboard on the mobile device. In Book Shop example this will represent the Quantity of and Order.

private void OnExceptionOccured(Exception ex)
{
    Console.WriteLine(String.Format("NumericInput Changed: ID: {0} Value: {1}", inputId, inputValue));
    OrderList.Single(q => q.ID == Int32.Parse(inputId)).Quantity = inputValue;
}

void OnPickListInputValueChange(string inputId, int pickListItemId)

PickListInputs are very simillar to an enumeration. The items have an Identifier, a Title and a Subtitle. In Book Shop example we use PickListInputs for Book and Client changes in an Order. We added "book." and "client." prefixes to inputID in order to split the inputs between book and client changes.

private void OnPickListInputValueChange(string inputId, int pickListItemId)
{
    Console.WriteLine(String.Format("PickListInputValue Changed: ID: {0} Item ID: {1}", inputId, pickListItemId - 1));
    string[] values = inputId.Split('.');
    if (values[0] == "book")
        OrderList.Single(q => q.ID == Int32.Parse(values[1])).Book = BookList[pickListItemId - 1];
    else if (values[0] == "client")
        OrderList.Single(q => q.ID == Int32.Parse(values[1])).Client = ClientList[pickListItemId - 1];
}

void OnDateInputChanged(string inputId, MM.Monitor.Cloud.Date inputValue)

Unlike DateTimeInputChanged this input type will only return a date value without the time. This method is not used in the BookShop example.

private void OnDateInputChanged(string inputId, MM.Monitor.Cloud.Date inputValue)
{
Console.WriteLine(String.Format("DateInput Changed: ID: {0} Value: {1}", inputId, inputValue));
}

void OnTimeInputChanged(string inputId, MM.Monitor.Cloud.Time inputValue)

Similar to OnDateInputChanged this input type will only return a time value without the date. This method is not used in the BookShop example.

private void OnTimeInputChanged(string inputId, MM.Monitor.Cloud.Time inputValue)
{
    Console.WriteLine(String.Format("TimeInput Changed: ID: {0} Value: {1}", inputId, inputValue));
}