Actions are the main components you will use to create applications for Pepper. With actions, Pepper can:
Prerequisites
In order to understand how to use actions, you should be familiar with asynchronicity.
Create actions synchronously or asynchronously using builders.
Here are the steps to build an action:
QiContext
,build
or buildAsync
method to create the action.Call the build
method on the builder to create an action
synchronously:
// Build an action synchronously.
val say: Say = SayBuilder.with(qiContext) // Create a builder with the QiContext.
.withText("Hello!") // Specify the action parameters.
.build()
// 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.
Call the buildAsync
method on the builder to create an action
asynchronously:
// Build an action asynchronously.
val sayActionFuture: Future<Say> = SayBuilder.with(qiContext) // Create a builder with the QiContext.
.withText("Hello!") // Specify the action parameters.
.buildAsync()
// 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.
To execute a synchronous action, you use the run
method on it:
// Execute the action synchronously.
say.run()
// 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.
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.
val sayAsync: Say.Async = say.async()
// 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()
// Execute the asynchronous action.
sayAsync.run();
Or in one line:
// Execute the action asynchronously.
say.async().run()
// Execute the action asynchronously.
say.async().run();
As a result:
run
call.The choice between asynchronicity and synchronicity depends on your use case. Some use cases will be detailed below.
As you have seen above, actions can be performed synchronously or asynchronously.
Consequently, this allows you to do the following:
Now that you know how to execute an action, you will see how to act when this execution succeeds.
Use the run
method to run an action synchronously and get the action
result.
// Run the action synchronously and get the result.
val ignore = say.run()
// Act on success.
Log.i(TAG, "Say action finished with success.")
// 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.
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:
val sayFuture: Future<Void> = say.async().run()
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.
val sayFuture: Future<Void> = say.async().run()
// Chain a lambda to the future.
sayFuture.andThenConsume {
// Act on success.
Log.i(TAG, "Say action finished with success.")
}
// Run the action asynchronously.
Future<Void> sayFuture = say.async().run();
// Chain a lambda to the future.
sayFuture.andThenConsume(ignore -> {
// Act on success.
Log.i(TAG, "Say action finished with success.");
})
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 lambda is not executed on the UI thread, so if you want
to execute some code interacting with the UI in this lambda,
use the Qi.onUiThread
method.
This method simply transfers the execution of the lambda on the UI
thread.
sayFuture.andThenConsume(Qi.onUiThread(Consumer {
Toast.makeText(MainActivity.this, "Say action finished with success.", Toast.LENGTH_SHORT).show()
}))
sayFuture.andThenConsume(Qi.onUiThread((Consumer<Void>) ignore -> {
Toast.makeText(MainActivity.this, "Say action finished with success.", Toast.LENGTH_SHORT).show();
}));
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.
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)
}
try {
say.run();
Log.i(TAG, "Success");
} catch (Exception exception) {
Log.e(TAG, "Error", exception);
}
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:
val sayFuture = say.async().run()
sayFuture.thenConsume { future ->
if (future.isSuccess) {
Log.i(TAG, "Success")
} else if (future.hasError()) {
Log.e(TAG, "Error", future.error)
}
}
Future<Void> sayFuture = say.async().run();
sayFuture.thenConsume(future -> {
if (future.isSuccess()) {
Log.i(TAG, "Success");
} else if (future.hasError()) {
Log.e(TAG, "Error", future.getError());
}
});
Cancel an action by calling the requestCancellation
method on the
corresponding Future
:
// Execute the action asynchronously.
val sayFuture: Future<Void> = say.async().run()
// Cancel the action asynchronously.
sayFuture.requestCancellation()
// 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.
val sayFuture: Future<Void> = say.async().run()
// Chain the future with a lambda.
sayFuture.thenConsume { future ->
if (future.isSuccess) {
Log.i(TAG, "Success")
} else if (future.isCancelled) {
Log.i(TAG, "Cancelled")
}
}
// Cancel the action asynchronously.
sayFuture.requestCancellation()
// Execute the action asynchronously.
Future<Void> sayFuture = say.async().run();
// Chain the future with a lambda.
sayFuture.thenConsume(future -> {
if (future.isSuccess()) {
Log.i(TAG, "Success");
} else if (future.isCancelled()) {
Log.i(TAG, "Cancelled");
}
});
// Cancel the action asynchronously.
sayFuture.requestCancellation();
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.
To chain actions synchronously, execute them synchronously one after the other:
// Create the first action.
val sayHello: Say = SayBuilder.with(qiContext)
.withText("Hello!")
.build()
// Create the second action.
val sayPepper: Say = SayBuilder.with(qiContext)
.withText("I'm Pepper!")
.build()
// Run the first action synchronously.
sayHello.run()
// Run the second action synchronously.
sayPepper.run()
// 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.
The Future
class provides 2 methods to chain multiple actions asynchronously:
thenCompose
and andThenCompose
:
// Create the first action.
val sayHello: Say = SayBuilder.with(qiContext)
.withText("Hello!")
.build()
// Run the first action asynchronously.
val sayHelloFuture: Future<Void> = sayHello.async().run()
// Chain a lambda to the future.
sayHelloFuture.andThenCompose {
// Create the second action.
val sayPepper: Say = SayBuilder.with(qiContext)
.withText("I'm Pepper!")
.build()
// Run the second action asynchronously.
return sayPepper.async().run()
}
// 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 lambda to the future.
sayHelloFuture.andThenCompose(ignore -> {
// Create the second action.
Say sayPepper = SayBuilder.with(qiContext)
.withText("I'm Pepper!")
.build();
// Run the second action asynchronously.
return sayPepper.async().run();
});
If you want Pepper to perform multiple actions at the same time, execute them asynchronously one after the other:
// Create the first action.
val say: Say = SayBuilder.with(qiContext)
.withText("Hello!")
.build()
// Create the second action.
val animate: Animate = AnimateBuilder.with(qiContext)
.withAnimation(animation)
.build()
// Run the first action asynchronously.
say.async().run()
// Run the second action asynchronously.
animate.async().run()
// 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.