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
{
    private String name;
    private Double price;
    public Book(String name, Double price) 
    {
        super();
        this.setName(name);
        this.setPrice(price);
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return name;
    }
    public void setPrice(Double price)
    {
        this.price = price;
    }
    public Double getPrice() 
    {
        return price;
    }
}
                    
public class Order 
{
    private final int id;
    private final String client;
    private final Book book;
    private boolean canceled;
    private static int lastID = 0;
    private int Quantity; 
    private String TransactionIdentifier; 
    private Date PurchaseDate; 
    public Order(String client, Book book) 
    {
        super();
        this.client = client;
        this.book = book;
        id = ++lastID;
        Random generator = new Random(); 
        Quantity = generator.nextInt(50);
        TransactionIdentifier = java.util.UUID.randomUUID().toString(); 
        PurchaseDate = new Date(); 
    }
    public int getId() 
    {
        return id;
    }
    public String getClient() 
    {
        return client;
    }
    public Book getBook() 
    {
        return book;
    }
    public Date getPurchaseDate() 
    {
        return PurchaseDate;
    }
    public int getQuantity() 
    {
        return Quantity;
    }
    public String getTransactionIdentifier() 
    {
        return TransactionIdentifier;
    }
    public void setCanceled(boolean canceled) 
    {
        this.canceled = canceled;
    }
    public void setQuantity(int Quantity) 
    {
        this.Quantity = Quantity;
    }
    public void setTransactionIdentifier(String TransactionIdentifier) 
    {
        this.TransactionIdentifier = TransactionIdentifier;
    }
    public void setPurchaseDate(Date date) 
    {
        this.PurchaseDate = date;
    }
    public void setBook(Book book) 
    {
        this.book = book;
    }
    public void setClient(String client) 
    {
        this.client = client;
    }
    public boolean isCanceled() 
    {
        return canceled;
    }
    public static void initializeIds() 
    {
        lastID = 0;
    }
}
                
public class BookShop
{
    private final ArrayList orderList = new ArrayList ();
    private ArrayList bookList = new ArrayList ();
    private ArrayList clientList = new ArrayList ();
    public BookShop() 
    {
        bookList = DataBank.generateBooks();
        clientList = DataBank.generateClientNames();
        regeneratePickLists();
        new Thread() {
            public void run() {
            addOrders();
            };
        }.start();
    }
    private void addOrders() 
    {
        while (orderList.size() != 50) {
            orderList.add(new Order(clientList.get(orderList.size()), bookList.get(orderList.size())));
            try {
            Thread.sleep(10000);
            } catch (InterruptedException e) {
            trace(e);
            }
        }
    }
    private int getCanceledOrdersCount() {
        int result = 0;
        for (Order co : orderList) {
            if (co.isCanceled()) {
            result++;
            }
        }
        return result;
    }
} 
                

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 pulseway-cloud.jar which can be downloaded from here.

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.

private final static int PAGE_LIST_ORDERS = 91;
private final static int PAGE_LIST_CANCELED_ORDERS = 92;
private final static int PAGE_LIST_BOOKS = 93;
private final static int COMMAND_CANCEL_ORDER = 1;
private final static int COMMAND_CLEAR_DATA = 2;
private final static 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.

Service.getInstance().setDetailsRequestHandler(new MyDetailsRequestHandler());
Service.getInstance().setPageRequestHandler(new MyPageRequestHandler());
Service.getInstance().setCommandReceivedHandler(new MyCommandReceivedHandler());
Service.getInstance().setPageCommandReceivedHandler(new MyPageCommandReceivedHandler());
Service.getInstance().setExceptionOccurredHandler(new MyExceptionOccurredHandler());
Service.getInstance().setInputValueChangedHandler(new InputEvents());
 

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

String computername = "Unknown";
try {
    computername = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
    trace(e);
}
Service.getInstance().configure("Book Shop", "Cloud", "Running on " + computername, false);
Service.getInstance().start(""username", "password", "apikey");    
 
The last parameter in the "Configure" method represents 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();
String computername = "Unknown"; 
try {
    computername = InetAddress.getLocalHost().getHostName(); 
} catch (UnknownHostException e) { 
    System.out.println(e);
}
Service.getInstance().configure("Book Shop", "Cloud", "Running on " + computername, false);
String username = "Pulseway Username";
String password = "Pulseway Password";
String apiKey = "Pulseway API Key / API Token";
Service.getInstance().setDetailsRequestHandler(new MyDetailsRequestHandler());
Service.getInstance().setPageRequestHandler(new MyPageRequestHandler());
Service.getInstance().setCommandReceivedHandler(new MyCommandReceivedHandler());
Service.getInstance().setPageCommandReceivedHandler(new MyPageCommandReceivedHandler());
Service.getInstance().setExceptionOccurredHandler(new MyExceptionOccurredHandler());
Service.getInstance().setInputValueChangedHandler(new InputEvents());
Service.getInstance().start(username, password, apikey); 
new Thread() {
    public void run() {
        addOrders();
    };
}.start();
   
All we need to do now is implement the subscribers.

interface DetailsRequestHandler

The onDetailsRequest() handler's method gets called when a client requests the "Groups" container that contains your data See Figure A. 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 A

class MyDetailsRequestHandler implements DetailsRequestHandler {
    @Override
    public void onDetailsRequest() {
        Groups container = new Groups();
        Group stats = new Group("Statistics");
        stats.getItems().add( new SimpleItem("Book Count: ", Integer.toString(bookList .size())));
        stats.getItems().add( new SimpleItem("Orders Count: ", Integer.toString(orderList .size())));
        stats.getItems().add( new SimpleItem("Canceled Orders Count: ", Integer .toString(getCanceledOrdersCount())));
        Group pages = new Group("Lists");
        pages.getItems().add(new PageItem(PAGE_LIST_BOOKS, "List Books"));
        pages.getItems().add(new PageItem(PAGE_LIST_ORDERS, "List Orders"));
        pages.getItems().add( new PageItem(PAGE_LIST_CANCELED_ORDERS, "List Canceled Orders"));
        Group actions = new Group("Actions");
        actions.getItems().add( new CommandItem(COMMAND_CLEAR_DATA, "Reset DataBank"));
        container.add(stats);
        container.add(pages);
        container.add(actions);
        Service.getInstance().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.

interface PageRequestHandler

onPageRequest(int pageId, String mobileDeviceIdentifier) 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 B.

Pulseway Figure B

class MyPageRequestHandler implements PageRequestHandler {
    @Override
    public void onPageRequest(int pageId, String mobileDeviceIdentifier) {
        Groups container = new Groups();
        Group contents = null;
        switch (pageId) {
            case PAGE_LIST_BOOKS:
            contents = new Group("Book List");
            for (Book b : bookList) {
                contents.getItems().add(new SimpleItem(b.getName(), priceFormatter.format(b .getPrice())));
            }
            break;
            case PAGE_LIST_ORDERS:
            contents = new Group("Order List");
            for (Order o : orderList) {
                contents.getItems().add( new PageItem(o.getId(), orderToString(o)));
            }
            break;
            case PAGE_LIST_CANCELED_ORDERS:
            contents = new Group("Canceled Order List");
            for (Order co : orderList) {
                if (co.isCanceled()) {
                    contents.getItems().add( new PageItem(co.getId(), orderToString(co)));
                }
            }
            break;
            default:
                contents = new Group("Order Details");
                Order current = getOrderWithId(pageId);
                contents.getItems().add(new PickListInputItem("client." + Integer.toString(pageId),	"Client Name: ", current.getClient(), clientPickList)); 
                contents.getItems().add(new PickListInputItem("book." + Integer.toString(pageId), "Book Name: ", current.getBook().getName(), bookPickList)); 
                contents.getItems().add(new SimpleItem("Book Price: ", priceFormatter.format(current.getBook().getPrice()), SimpleItemStyle.INFORMATION)); 
                contents.getItems().add(new DateTimeInputItem(Integer.toString(pageId), "Order Date: ", current.getPurchaseDate().toString()));
                contents.getItems().add(new NumberInputItem(Integer.toString(pageId), "Quantity: ", Integer.toString(current.getQuantity()))); 
                contents.getItems().add(new TextInputItem(Integer.toString(pageId), "Transaction: ", current.getTransactionIdentifier())); 
                contents.getItems().add(new SimpleItem("Canceled: ", current.isCanceled() ? "Yes" : "No", current.isCanceled() ? SimpleItemStyle.WARNING : SimpleItemStyle.INFORMATION)); 
                if (!current.isCanceled())
                    contents.getItems().add( new CommandItem(COMMAND_CANCEL_ORDER, "Cancel Order"));
                else
                    contents.getItems() .add(new CommandItem(COMMAND_RESET_ORDER, "Reset Order"));
            break;
            }
        container.add(contents);
        Service.getInstance().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.

interface CommandReceivedHandler

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

class MyCommandReceivedHandler implements CommandReceivedHandler {
    @Override
    public void onCommandReceived( int commandId, String mobileDeviceIdentifier) {
        if (commandId == COMMAND_CLEAR_DATA) {
            bookList = DataBank.generateBooks();
            clientList = DataBank.generateClientNames();
            orderList.clear();
            regeneratePickLists();
            Order.initializeIds();
        }
    }
}
    

interface PageCommandReceivedHandler

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.

class MyPageCommandReceivedHandler implements PageCommandReceivedHandler {
    @Override
    public void onPageCommandReceived( int pageId, int commandId, String mobileDeviceIdentifier) {
        if (commandId == COMMAND_CANCEL_ORDER) {
            getOrderWithId(pageId).setCanceled(true);
        } else if (commandId == COMMAND_RESET_ORDER) {
            getOrderWithId(pageId).setCanceled(false);
        }
    }
}
    

interface ExceptionOccurredHandler

If anything unusual happens with Cloud API will report to our instance so that logging and apropiate actions can occur. In our case we only print it to the console.

class MyExceptionOccurredHandler implements ExceptionOccurredHandler {
    @Override
    public void onExceptionOccurred(Exception ex) {
        System.out.println(ex.toString());
    }
}  
     

void onDateTimeInputValueChanged(String id, com.mmsoftdesign.cloud.model.DateTime value)

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.

@Override
public void onDateTimeInputValueChanged(String id, DateTime value) {
    getOrderWithId(Integer.parseInt(id)).setPurchaseDate(new Date(value.getYear() - 1900, value.getMonth(), value.getDay(), value.getHour(), value.getMinutes(), 0)); 
}
   

void onTextInputValueChanged(String id, String value)

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

@Override
public void onTextInputValueChanged(String id, String value) {
    getOrderWithId(Integer.parseInt(value)).setTransactionIdentifier(value); 
}
   

onNumericInputValueChanged(String id, int value)

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.

@Override
public void onNumericInputValueChanged(String id, int value) {
    getOrderWithId(Integer.parseInt(id)).setQuantity(value); 
}   

onPickListInputValueChanged(String id, int pickedItemId)

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.

@Override
public void onPickListInputValueChanged(String id, int pickedItemId) {
    String[] s = id.split("\\.");
    if (s[0].equals("book"))
        getOrderWithId(Integer.parseInt(s[1])).setBook(bookList.get(pickedItemId - 1));
    else if (s[0].equals("client"))
        getOrderWithId(Integer.parseInt(s[1])).setClient(clientList.get(pickedItemId - 1));
}
  

onDateInputValueChanged(String id, com.mmsoftdesign.cloud.model.Date value)

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

@Override
public void void onDateInputValueChanged(String id, Date value) {
    System.out.println("Got date: " + Integer.toString(value.getYear()) + " " + Integer.toString(value.getMonth()) + " " + Integer.toString(value.getDay())); 
}
    

onTimeInputValueChanged(String id, Time value)

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

@Override
public void onTimeInputValueChanged(String id, Time value) {
    System.out.println("Got time: " + Integer.toString(value.getHour()) + " " + Integer.toString(value.getMinutes())); 
}