Skip to content

6. Getting a "real time" activity to work

yoh-there edited this page Dec 28, 2020 · 6 revisions

General form of a screen activity

In it's most bare form you need to create an activity Class that extends CanZE Activity Class and implements a FieldListener. You should provide the following code:

  • an onCreate method which calls it's super and setContentView with the desired layout;
  • an initListeners method which populates the poller with the fields you want to query using calles to addField;
  • an onFieldUpdateEvent method which is called when a field is updated.

Note that graphs widgets that are defined in your layout are completely self-populating. You do not need to do any of the above to make them work.

Displaying data

When programming an activity that needs to update the UI real time, there are a few things "Android" that you need to keep in mind.

First, literally everything you do that requires significant time (as in, a few milliseconds), some sort of time out or sleeping, which basically means everything that interfaces with another device or is waiting for something to happen, needs to run in a separate thread. This ensures your Android UI does not lock up. For a basic CanZE Activity, the poller already is running in a separate thread and you don't need to worry about it. Only when you stop the poller and interactively query the device you need to define and run your own separate threads. Here is how to do it:

    new Thread(new Runnable() {
        @Override
        public void run() {
            doSomething();
        }
    }).start();

The code in doSomething will run on it's own while this code block simply finishes. It is now safe to do i.e. Thread.Sleep() or some massive calculation in doSomething without freezing your device.

Second and unfortunately, updating the UI is not trivial anymore now in doSomething. Anything on the UI needs to be done through the UI thread. To do this, you have to update the UI in a yet again separate thread, the already running global UI thread. As an example, here is the code that is called by doSomething or your onFieldsUpdateEvent to append a string to a TextView. Note that textview is a private Textview defined in the overall Class we're working on and it represents the field on the screen we are appending.

private void appendResult (String str) {
    final String localStr = str; // a final String is needed in method run.
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            textView.append(localStr);
        }
    });
}

This little piece of code ensures the appending is quickly represented on the UI, instead of the UI being updated only when all other code finishes.

The usual structure of an onFieldUpdateEvent method usually is defining a Runnable on the UI thread, find out what field just received it's value., a case to switch to the appropriate piece of code, finally a set or append of one or more elements in the UI.

Starting a normal thread and running something on the global UI thread both look quite similar, but they aren't. A normal thread is created and then started with it's .start() method. Forgetting to start the thread is an easy mistake to make. Running something on the global UI thread is done with runOnUiThread. No starting needed (or possible); it's already running.

Note: as you can imagine, the updates that are to be processed by the global UI thread are internally queued, processed, and then the screen is updated. It is possible to overload this queue processing as an edge case. An example of this is the AllData activity that appends lines at a high speed to a growing and already very long text view. To mitigate this issue we compile updates per 100 lines and then let the UI thread append that block in one operation. This lowers the rendering load substantially and lets the UI thread not fall behind. Other solutions are possible such as programmed UI updates.