Learn everything about the Unclouded SDK for Android.

This guide provides an overview of all concepts and techniques to get you started in no time.

Event Loop

The Unclouded framework is built around the concept of an event loop. This is the central entity that manages all communication and interactions. An event loop adopts event-driven communication and consists of an event queue that stores (incoming) events. These events are sequentially processed, one at a time. Furthermore, interactions between event loops are asynchronous, so all this means you don't have to worry about concurrency, deadlocks or data-level race conditions.

The Unclouded event loop can be accessed from the Unclouded class. This class is implemented as a singleton class, so only a single event loop instance can be obtained per application. To retrieve this instance, invoke Unclouded.getInstance().

Unclouded unclouded = Unclouded.getInstance();

Going Online

By default, when retrieving the Unclouded instance for the first time, the event loop is offline. This means that the corresponding device is not connected to the network and cannot be discovered by other devices in the network. To go online and to obtain a network address, invoke the goOnline method on the Unclouded instance. Going offline again can be established by invoking goOffline.

unclouded.goOnline(); 
unclouded.goOffline();

Network Listener

It is important to note that both goOnline as goOffline are asynchronous, non-blocking operations. This means that invoking these methods returns immediately in order to not hamper the application flow. The actual event of (dis)connecting to the network is scheduled by the event loop and executed at some later point in time. In order to be notified when this occurs, the goOnline method returns a Network object as the result of its invocation. By passing a NetworkListener instance to the Network object's whenever method, the NetworkListener is triggered in four cases:

  • isConnected(InetAddress ipAddress) is triggered after the device is online and obtained a network address.
  • isDisconnected(InetAddress ipAddress) is triggered after the device is offline from the network. The ip address that is returned is the address that was obtained when the device was online.
  • isConnectedTo(InetAddress ipAddress) is triggered after the device connects to another device in the network.
  • isDisconnectedFrom(InetAddress ipAddress) is triggered after some device in the network has disconnected from the network.

Note that all four methods are optional and not required to be implemented.

Network network = unclouded.goOnline();

network.whenever(new NetworkListener(){

  public void isConnected(InetAddress ipAddress){ /*...*/ };

  public void isDisconnected(InetAddress ipAddress){ /*...*/ };

  public void isConnectedTo(InetAddress ipAddress){ /*...*/ };

  public void isDisconnectedFrom(InetAddress ipAddress){ /*...*/ };

});

In case you want to manipulate the UI from within these listener handles, you should explicitly enforce this code to be executed in the UI Thread of your application. In Android, this can be easily done by wrapping your code in a Runnable action and pass this to the runOnUiThread method of the current Activity.

network.whenever(new NetworkListener(){

  public void isConnected(InetAddress ipAddress){
    runOnUiThread(new Runnable(){
      public void run(){
        TextView txtView = (TextView) findViewById(R.id.textView1);
        txtView.setText("I'm online at "+ipAddress.toString());               
      }
    });
  }

});

The NetworkListener can be cancelled by invoking the cancel method on the Network object.

network.cancel();

Services

Communication between devices occurs via services. A service consists of a type tag associated with some data element or object. The Unclouded instance is used to broadcast services to the network and to listen for services of other devices. The act of exporting a service to the network is called a `Service publication', while listening for services (of a certain type tag) is called a `Service subscription'.

Type Tags

A TypeTag can be seen as an identifier that is used during service publication or subscription. Type tags are important because they serve as a mechanism to filter certain services from the pool of services in the network. A type tag can be defined by creating a new instance of the TypeTag class and passing a string as identifier. A TypeTag instance provides no extra functionality rather than serving as a classification mechanism for services.

TypeTag HandshakeTypeTag = new TypeTag("HANDSHAKE");

Service Publication

Broadcasting a service to the network makes the service to become discoverable by other devices in the network. Two types of data can be passed:

  • Any Serializable object is passed by copy.
  • Any object implementing the UObject interface is passed by reference.

A service is broadcasted by means of the broadcast(typeTag, data) method of the Unclouded instance and takes as arguments the data object to be broadcasted along with the type tag where this data is going to be tagged with.

unclouded.broadcast(HandshakeTypeTag1, "Hello World");
unclouded.broadcast(HandshakeTypeTag2, 99);

class Counter implements UObject {
  private int n;
  public Counter(){
    n = 0;
  }

  public int increase(){
    n++;
    return n;
  }

  public int getValue(){
    return n;
  }

};

Counter counter = new Counter();
unclouded.broadcast(HandshakeTypeTag3, counter);

Cancel Service Publication

Sometimes you might only want to publish a service for a limited time. To cancel a service publication, store the ServicePublication objects that is returned upon exporting the object and invoke its cancel method to stop the publication. This will cause the service to disconnect from the network, such that devices that already discovered the service receive a disconnection notification, while new devices cannot discover the service anymore.

ServicePublication publication 
        = unclouded.broadcast(HandshakeTypeTag, "Hello World");
publication.cancel();

Service Subscription

To listen for services of a certain type tag, the event loop's whenever method can be used. This method takes a TypeTag and a ServiceListener as arguments. The type tag serves as a filter to listen only for services of a certain type in the network. The ServiceListener takes a type parameter that indicates the type of data that is expected upon discovering a service matching the given type tag. It triggers notifications when the service is discovered for the first time, when it disconnects and subsequently when it reconnects. Hence, a ServiceListener supports three callback methods: isDiscovered, isDisconnected and isReconnected. For each listener, at least the isDiscovered method should be implemented. The isDisconnected and isReconnected methods are optional.

unclouded.whenever(HandshakeTypeTag1, new ServiceListener<String>(){
  public void isDiscovered(String value){
    // value is "Hello World"
  }
});

unclouded.whenever(HandshakeTypeTag2, new ServiceListener<Integer>(){
  public void isDiscovered(Integer value){
    // value is 99
  }
});

It is important to note that the whenever method will discover all possible services associated with the given type tag. In the case it is sufficient to only discover a single service, the when method can be used alternatively. This method takes exactly the same arguments, though the difference is that it will only discover a single service. In case more services are available, only the first discovered service will cause a trigger.

Discovering a Remote Reference

Discovering a service holding a UObject involves a remote reference because the object is actually passed by reference. This means that the UObject is not copied upon transmission as in the case of serializable data. A UObject is useful in the case that a certain state should be accessed and modified by multiple devices across the network. Since a UObject is only passed by reference, it remains in sync at all times and processes method invocations from other devices asynchronously.

To interact with a service holding a UObject, a ServiceListener with the type parameter RemoteReference should be passed to the when or whenever method of the Unclouded instance. The actual remote reference is returned as an argument to the listener' callback methods. A remote reference can only be used to interact asynchronously with the referenced object. To invoke a method on this object, invoke the asyncInvoke method on the remote reference. This method takes the method name of the method to invoke as argument (in the form of a String) and will immediately return a Promise object (non-blocking). The invocation is transmitted to the device that hosts the object and upon reception, it is scheduled in its event queue.

In order to receive the return value of this method invocation, the Promise object that was returned upon calling asyncInvoke should be captured. This Promise object can be used to obtain the return value of the initialized asynchronous method invocation. By setting a PromiseListener instance on this Promise object via its when method, the isResolved callback method is triggered as soon as the return value is returned. This value is of type T and is passed as argument during the callback. In case the asynchronous method invocation results in an exception, the optional isRuined callback method is triggered.

unclouded.when(HandshakeTypeTag3, new ServiceListener<RemoteReference>(){

  isDiscovered(RemoteReference remoteReference){
    Promise promise = remoteReference.asyncInvoke("increase");
    promise.when(new PromiseListener<Integer>(){

      public void isResolved(Integer value){
        // counter = value
      }

      public void isRuined(){
        // Oops, something wrong happened!
      }

    });
  }

});

Note that arguments of an asynchronous method invocation can be passed as additional arguments of the asyncInvoke method. These arguments will be passed by copy and should therefore be serializable.

Cancel Service Subscription

In certain scenario's, it might occur that you are not interested anymore in certain services and want to cancel your service subscription. This can be established by capturing the ServiceSubscription object that is returned upon calling the when or whenever method on the unclouded instance. By invoking cancel() on this object, the service subscription is canceled. This implies that no notifications are triggered anymore for the service(s) that was(were) discovered by this subscription.

ServiceSubscription subscription = unclouded.whenever(typeTag, listener);
subscription.cancel();