Running Actions on Pepper

Actions are the main components you will use to create applications for Pepper. With actions, Pepper can:

  • talk,
  • listen,
  • move,
  • perform animations,
  • engage humans,
  • chat,
  • turn his gaze somewhere,
  • localize himself in an environment,
  • take pictures.

Prerequisites

In order to understand how to use actions, you should be familiar with asynchronicity.

Create an action

Create actions synchronously or asynchronously using builders.

Here are the steps to build an action:

  • create an action builder with the QiContext,
  • pass the action parameters to the builder,
  • call the build or buildAsync method to create the action.

Synchronously

Call the build method on the builder to create an action synchronously:

// Build an action synchronously.
Say say = SayBuilder.with(qiContext) // Create a builder with the QiContext.
                    .withText("Hello!") // Specify the action parameters.
                    .build();

Warning

Do not create actions synchronously on the UI thread or it will throw a NetworkOnMainThreadException. This mechanism prevents the UI thread to be blocked.

Asynchronously

Call the buildAsync method on the builder to create an action asynchronously:

// Build an action asynchronously.
Future<Say> sayActionFuture = SayBuilder.with(qiContext) // Create a builder with the QiContext.
                                        .withText("Hello!") // Specify the action parameters.
                                        .buildAsync();

The reference on the action is a Future instance.

To learn more about the Future class, see Chaining operations.

Execute an action

Synchronously

To execute a synchronous action, you use the run method on it:

// Execute the action synchronously.
say.run();

Warning

Do not call the run method on the UI thread or it will throw a NetworkOnMainThreadException. This mechanism prevents the UI thread to be blocked.

Asynchronously

Each action provides an Async interface which allows you to create an asynchronous action. To create an asynchronous action, call the async method on the action:

// Create an asynchronous action.
Say.Async sayAsync = say.async();

An asynchronous action is executed when you call the run method on it:

// Execute the asynchronous action.
sayAsync.run();

Or in one line:

// Execute the action asynchronously.
say.async().run();

As a result:

  • the thread is not blocked when the action runs,
  • the action execution may not be finished immediately after the run call.

The choice between asynchronicity and synchronicity depends on your use case. Some use cases will be detailed below.

Manage the action execution

As you have seen above, actions can be performed synchronously or asynchronously.

Consequently, this allows you to do the following:

  • execute some code when an action execution succeeds,
  • handle potential errors during an action execution,
  • handle an action cancellation.

Get an action result / Act on an action success

Now that you know how to execute an action, you will see how to act when this execution succeeds.

Synchronously

Use the run method to run an action synchronously and get the action result.

// Run the action synchronously and get the result.
Void ignore = say.run();
// Act on success.
Log.i(TAG, "Say action finished with success.");

Note

Here the result type is Void because the Say does not have any value to return. Other actions have a meaningful value that you are able to use.

Asynchronously

To act on an asynchronous action execution, you need to keep a reference on it. For an asynchronous action, the run method returns that reference for you:

Future<Void> sayFuture = say.async().run();

The Future class provides the andThenConsume method that allows you to get the value contained in the Future and consume it:

// Run the action asynchronously.
Future<Void> sayFuture = say.async().run();

// Chain a callback to the future.
sayFuture.andThenConsume(new Consumer<Void>() {
    @Override
    public void consume(Void ignore) throws Throwable {
        // Act on success.
        Log.i(TAG, "Say action finished with success.");
    }
});

For more details about the Consumer class, see Consumer.

Note

You may want to run your actions asynchronously if you are on the UI thread. For example, you might want Pepper to talk when you click on a button.

The consume method is not executed on the UI thread, so if you want to execute some code interacting with the UI in a Consumer, use the Qi.onUiThread method. This method simply transfers the execution of the consume method on the UI thread.

sayFuture.andThenConsume(Qi.onUiThread(new Consumer<Void>() {
    @Override
    public void consume(Void ignore) throws Throwable {
        Toast.makeText(MainActivity.this, "Say action finished with success.", Toast.LENGTH_SHORT).show();
    }
}));

Handle errors

When you execute an action, keep in mind that an error can occur. The way to handle it depends upon the way you execute the action.

Synchronously

When you call the run method on a synchronous action, a RuntimeException can be thrown. If this occurs in the onRobotFocusGained method, its execution stops at the run method call.

Manage the error using a try-catch block:

try {
    say.run();
    Log.i(TAG, "Success");
} catch (Exception exception) {
    Log.e(TAG, "Error", exception);
}

Asynchronously

If you choose to execute an action asynchronously, use the thenConsume method to handle a potential error.

The Future class provides the hasError method to check if the Future contains an error:

Future<Void> sayFuture = say.async().run();
sayFuture.thenConsume(new Consumer<Future<Void>>() {
    @Override
    public void consume(Future<Void> future) throws Throwable {
        if (future.isSuccess()) {
            Log.i(TAG, "Success");
        } else if (future.hasError()) {
            Log.e(TAG, "Error", future.getError());
        }
    }
});

Cancel actions

Cancel an action by calling the requestCancellation method on the corresponding Future:

// Execute the action asynchronously.
Future<Void> sayFuture = say.async().run();
// Cancel the action asynchronously.
sayFuture.requestCancellation();

If you use the thenConsume method to chain the Future, you can check if the Future is cancelled with the isCancelled method:

// Execute the action asynchronously.
Future<Void> sayFuture = say.async().run();

// Chain the future with a consumer.
sayFuture.thenConsume(new Consumer<Future<Void>>() {
    @Override
    public void consume(Future<Void> future) throws Throwable {
        if (future.isSuccess()) {
            Log.i(TAG, "Success");
        } else if (future.isCancelled()) {
            Log.i(TAG, "Cancelled");
        }
    }
});

// Cancel the action asynchronously.
sayFuture.requestCancellation();

Use multiple actions

Now that you have seen how to act on an action execution, we will cover a common use case: how to use multiple actions together.

Chain multiple actions

Synchronously

To chain actions synchronously, execute them synchronously one after the other:

// Create the first action.
Say sayHello = SayBuilder.with(qiContext)
                         .withText("Hello!")
                         .build();

// Create the second action.
Say sayPepper = SayBuilder.with(qiContext)
                          .withText("I'm Pepper!")
                          .build();

// Run the first action synchronously.
sayHello.run();
// Run the second action synchronously.
sayPepper.run();

If the first action succeeds, the second one will be executed.

Asynchronously

The Future class provides 2 methods to chain multiple actions asynchronously: thenCompose and andThenCompose:

// Create the first action.
Say sayHello = SayBuilder.with(qiContext)
                         .withText("Hello!")
                         .build();

// Run the first action asynchronously.
Future<Void> sayHelloFuture = sayHello.async().run();

// Chain a function to the future.
sayHelloFuture.andThenCompose(new Function<Void, Future<Void>>() {
    @Override
    public Future<Void> execute(Void ignore) throws Throwable {
        // Create the second action.
        Say sayPepper = SayBuilder.with(qiContext)
                                  .withText("I'm Pepper!")
                                  .build();

        // Run the second action asynchronously.
        return sayPepper.async().run();
    }
});

For more details about the Function class, see Function.

Run actions simultaneously

If you want Pepper to perform multiple actions at the same time, execute them asynchronously one after the other:

// Create the first action.
Say say = SayBuilder.with(qiContext)
                    .withText("Hello!")
                    .build();

// Create the second action.
Animate animate = AnimateBuilder.with(qiContext)
                                .withAnimation(animation)
                                .build();

// Run the first action asynchronously.
say.async().run();
// Run the second action asynchronously.
animate.async().run();

Note

Some actions cannot be performed simultaneously. For example, the robot cannot run multiple Say instances at the same time.