Stubs
The stubs feature generates:
- Fully instantiable implementation classes for each API interface
- A starting point for your business logic
- Property storage with change detection and listener notification
- Async operation support via
CompletableFutureandExecutorService
The stub implementations extend the abstract base classes generated by the api feature and provide complete, working implementations that you can customize.
For cross-process communication on Android, see the android feature. The Android service uses stubs as its default backend.
The stubs module is an Android library (com.android.library plugin) and requires the Android SDK to build. It cannot be used in pure Java (non-Android) projects.
File overview
With our example API definition:
Hello World API (click to expand)
schema: apigear.module/1.0
name: io.world
version: "1.0.0"
interfaces:
- name: Hello
properties:
- { name: last, type: Message }
operations:
- name: say
params:
- { name: msg, type: Message }
- { name: when, type: When }
return:
type: int
signals:
- name: justSaid
params:
- { name: msg, type: Message }
enums:
- name: When
members:
- { name: Now, value: 0 }
- { name: Soon, value: 1 }
- { name: Never, value: 2 }
structs:
- name: Message
fields:
- { name: content, type: string }
The following file structure is generated in the ioWorld_impl module:
📂ioWorld/ioWorld_impl
┣ 📜build.gradle
┗ 📂src/main/java/ioWorld/ioWorld_impl
┗ 📜HelloService.java
Implementation
HelloService extends AbstractHello and provides a complete implementation of the IHello interface. It serves as a local backend that can be used directly or as the backend for the android service adapter.
Properties
Properties are stored as private fields with change detection in the setter:
public class HelloService extends AbstractHello {
private Message m_last = new Message();
@Override
public void setLast(Message last) {
if ((m_last != null && !m_last.equals(last))
|| (m_last == null && last != null)) {
m_last = last;
onLastChanged(m_last);
}
}
private void onLastChanged(Message newValue) {
fireLastChanged(newValue);
}
@Override
public Message getLast() {
return m_last;
}
}
The setter:
- Checks if the value actually changed using
equals() - Updates the stored value
- Calls
onLastChanged(), which notifies all listeners via the inheritedfireLastChanged()
Operations
Synchronous operations are generated with stub implementations that return default values:
@Override
public int say(Message msg, When when) {
return 0;
}
Fill in your business logic by modifying the method body.
Async Operations
Async operations are implemented using CompletableFuture backed by a single-thread ExecutorService:
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
@Override
public CompletableFuture<Integer> sayAsync(Message msg, When when) {
return CompletableFuture.supplyAsync(
() -> say(msg, when),
executor);
}
The async variant wraps the synchronous implementation, so you only need to implement the sync method. Override sayAsync if you need custom async behavior.
Signals
To emit a signal from your implementation, use the inherited fire methods:
Message msg = new Message("Something happened");
fireJustSaid(msg);
This notifies all registered event listeners. When the service runs behind the android adapter, the signal is automatically forwarded to all IPC clients.
Ready State
The stub service marks itself as ready immediately on construction:
public HelloService() {
fire_readyStatusChanged(true);
}
@Override
public boolean _isReady() {
return isServiceReady;
}
Usage
Service side
The stubs serve as the default backend for the android service. Use HelloServiceStarter to manage the service lifecycle — it creates the backend, starts the Android service, and returns the backend instance:
// Start the service and get the backend instance
HelloService backend = (HelloService) HelloServiceStarter.start(getApplicationContext());
backend.addEventListener(this);
// Interact with the backend directly (changes are forwarded to IPC clients)
backend.setLast(new Message("Hello from service"));
Stop the service when your activity or application is done:
// In onDestroy() or equivalent
backend.removeEventListener(this);
HelloServiceStarter.stop(getApplicationContext());
The starter also exposes a ServiceLifecycleListener interface to receive connection and binding-death events.
Client side
From another app, bind to the service using HelloClient (from the android feature). The client implements the same IHello interface, so your application code works identically regardless of whether the backend is local or remote:
HelloClient client = new HelloClient(getApplicationContext(), "main");
client.bindToService("com.example.serviceapp");
// Wait for the connection to be established
client.addEventListener(new IHelloEventListener() {
@Override
public void on_readyStatusChanged(boolean isReady) {
if (isReady) {
// Now safe to use the client
client.setLast(new Message("Hello from client"));
}
}
@Override
public void onLastChanged(Message newValue) {
// Received property change from service
}
@Override
public void onJustSaid(Message msg) {
// Received signal from service
}
});
// Later
client.unbindFromService();
See the android feature documentation for the full service/client architecture.
Thread Safety
The listener collection (inherited from AbstractHello) uses ConcurrentHashMap.newKeySet(), which is safe for concurrent add/remove/iterate. However, the ExecutorService runs async operations on a separate thread. If your operation implementation accesses shared state, ensure proper synchronization.
Gradle Module
The ioWorld_impl module is an Android library (com.android.library plugin) that depends on the ioWorld_api module. It requires the Android SDK for logging (android.util.Log).