Getting started with Android

A getting started guide for deepstream with Android

This page needs to be improved/might be out of date! Raise a PR if you feel like adding a few details or totally revamping it.

This guide will take you through getting started on Android with deepstream’s three core concepts: Records, Events and RPCs.

Start your deepstream server

Connect to deepstream and log in

The first thing to do is create a new Android application and include the following line in your build.gradle file.

compile 'io.deepstream:deepstream.io-client-java:2.0.4'

Because we’ll be needing to pass the same client between activities in our app, we can use the built-in DeepstreamFactory to create a client and keep a reference to it.

In your MainActivity you’ll need to do the following:

DeepstreamFactory factory = DeepstreamFactory.getInstance();
DeepstreamClient client = factory.getClient("<Your app url">);
client.login();

From here, whenever we have a reference to our factory we can do factory.getClient() and get the same client back. Our MainActivity is just a basic Activity with buttons pointing to our three pages EventActivity, RpcActivity and RecordActivity. So we’ll ignore that for now and jump right into events.

Event (publish-subscribe)

[[glossary]]

| Events are deepstream's publish-subscribe mechanism. Clients and backend processes can subscribe to event-names (sometimes also called “topics” or “channels”) and receive messages published by other endpoints.

| Events are non-persistent, one-off messages. For persistent data, please use records.

| Events, aka Pub/Sub, allows communication using a Publish-Subscribe pattern. A client/server emits an event, which is known as publishing and all connected (subscribed) clients/servers are triggered with the event's payload if any. This is a common pattern, not just in realtime systems, but software engineering generally.

The event API is very simple and we’ll be using it to transfer data between two devices. The Android specific components we need are an EditText for input, a Button for sending the data, and a TextView to display this data.

To send the data in the EditText, we can do the following:

submitButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String eventPayload = inputField.getText().toString();
        client.event.emit("test-event", eventPayload);
        inputField.setText("");
    }
});

And to display any incoming data in our TextView, we can do:

client.event.subscribe("test-event", new EventListener() {
    @Override
    public void onEvent(String s, final Object o) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                outputField.setText((String) o);
            }
        });
    }
});

After this, we should have something that looks like the following:

event-gif

Records (data-sync)

[[glossary]] | Records are the documents in deepstream’s realtime datastore. A record is identified by a unique id and can contain any kind of JSON data. Clients and backend processes can create, read, write, update and observe the entire record as well as paths within it. Any change is immediately synchronized amongst all connected subscribers. | Records can be arranged in lists and collections and can contain references to other records to allow for the modelling of relational data structures. | You can learn more about records in the records tutorial.

To sync data between our devices, we’ll be using a Record with two fields, firstname and lastname. We also need two EditText fields in our Activity.

The first thing want to do is get a reference to our Record, with the Java SDK it looks like this:

Record record = client.record.getRecord("test-record");
record.setMergeStrategy(MergeStrategy.REMOTE_WINS);

Next, we want to add a TextWatcher on the input fields, so that whenever new data is entered, the Record will be updated with these changes. To update the Record data, we’ll be using the Record.set(String path, Object data) method.

firstnameInputField.addTextChangedListener(new CustomTextChangedWatcher("firstname"));
lastnameInputField.addTextChangedListener(new CustomTextChangedWatcher("lastname"));

With our CustomTextChangedWatcher just being the following:

 private class CustomTextChangedWatcher implements TextWatcher {

    private String field;

    CustomTextChangedWatcher(String recordField) {
        this.field = recordField;
    }
    @Override
    public void afterTextChanged(Editable s) {
        if (s.toString().length() == 0) {
            return;
        }
        record.set(field, s.toString());
    }
}

After this, we just need to subscribe to the firstname and lastname fields and update the EditText’s whenever they change. This is similar to the above code snippet where we’re just wrapping the RecordPathChangedCallback in a class and keeping a reference to something (in this case the corresponding EditText).

record.subscribe("firstname", new CustomRecordPathChangedCallback(firstnameInputField), true);
record.subscribe("lastname", new CustomRecordPathChangedCallback(lastnameInputField), true);

Where the CustomRecordPathChangedCallback is just the following:

private class CustomRecordPathChangedCallback implements RecordPathChangedCallback {
    private EditText field;
    CustomRecordPathChangedCallback(EditText editTextField) {
        this.field = editTextField;
    }
    @Override
    public void onRecordPathChanged(String recordName, String path, final JsonElement data) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (data.isJsonNull()) {
                    return;
                }
                field.setText(data.getAsString());
                // this line just moves the cursor to the end of the text
                field.setSelection(field.getText().length());
            }
        });
    }
}

Finally, we should have something that looks like this:

record-gif

RPCs (request-response)

[[glossary]] | Remote Procedure Calls are deepstream's request-response mechanism. Clients and backend processes can register as “providers” for a given RPC, identified by a unique name. Other endpoints can request said RPC. | deepstream will route requests to the right provider, load-balance between multiple providers for the same RPC, and handle data-serialisation and transport.

Our simple demo app has one function, and that is to make a string of characters upper case. We do this by providing a method to-uppercase , and then calling that method with some string.

We need a few elements for this to work in our app, namely:

-Button submitButton; the button we click to call the RPC

-CheckBox provideCheckBox; a check box to say whether we’re providing the RPC

-EditText inputField; the text field to enter our data

-TextView outputField; the text field to display the result of our method

Our code for providing the method is simple, if the box is checked we want to provide the RPC, otherwise we’ll unprovide it.

public void toggleProvide(View view) {
    if (provideCheckBox.isChecked()) {
        client.rpc.provide("to-uppercase", new RpcRequestedListener() {
            @Override
            public void onRPCRequested(String name, Object data, RpcResponse response) {
                String uppercaseResult = data.toString().toUpperCase();
                response.send(uppercaseResult);
            }
        });
    } else {
        client.rpc.unprovide("to-uppercase");
    }
}

When we click the button, we’ll just get whatever is in the EditText and try make it upper case.

public void makeToUppercase(View view) {
    String data = inputField.getText().toString();
    final RpcResult result = client.rpc.make("to-uppercase", data);
    if (result.success()) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                outputField.setText(result.getData().toString());
            }
        });
    } else {
        Toast.makeText(this, "Error making RPC", Toast.LENGTH_LONG).show();
    }
}

Keep in mind that if there is no RPC provider, the RPC won’t be able to be completed and will return a NO_RPC_PROVIDER error. That being said, it should look like this:

rpc-gif