IviService

@Target(allowedTargets = [AnnotationTarget.CLASS])
annotation class IviService(    val serviceId: String,     val importedEnums: Array<KClass<*>> = [],     val traceEvents: Boolean = true,     val multipleInstances: Boolean = false)

An annotation required for IVI service interfaces.

The annotation processor will generate various interfaces and classes for each interface annotated with this annotation, namely:

  • <Interface>Api: Frontend interface for the IVI service.

  • <Interface>Base: Abstract base class for the IVI service implementation.

  • Various interfaces and classes to achieve the communication between the <Interface>Api and the IVI service implementation.

IVI service interfaces can contain:

  • Inner data classes. For each inner data class the serialization and deserialization will be provided.

  • Properties. Each property is mirrored by the <Interface>Api class as a LiveData<T> property, where T is the type of the property being mirrored. For more information on LiveData see the Android documentation

All properties mirrored by the <Interface>Api are read-only.

Properties can be annotated with the IviSetting annotation. This exposes the property as a setting. See IviSetting for details.

  • Functions. When a function is annotated with the IviServiceFun annotation, the function is exposed in the <Interface>Api class. Each function is exposed in the <Interface>Api class in two variants:

  1. Asynchronous, like fun <name>Async(..., onResult = null). Where onResult, when not null, is invoked when the service function execution completes with the result or when the execution fails.

  2. A suspendable function, like suspend fun co<Name>(...): <ReturnType>. This allows the function to be called from a Kotlin coroutine. The co<Name> function throws an exception when the execution fails.

  • Event listener interfaces. This is provided by means of annotating an inner interface with the IviServiceEventListener annotation. Each function in this class is an event callback. Each event callback is propagated to event listeners registered to the <Interface>Api class.

Note: The annotated inner interface class name needs to have the 'Listener' suffix and all event callback member functions need to be prefixed with on. The annotation processor will fail otherwise.

To support inter-process communication between the IVI service and the <Interface>Api, all used types in the IVI service interface need to be supported by the android.os.Parcel or the androidx.versionedparcelable.VersionedParcelable classes, with the following additions:

  • Maps can use any supported type as the key type. So it is not limited to String.

  • Classes inheriting android.os.Parcelable can be optionally annotated with the @Parcelize annotation.

  • Set collections. Do not use them when the ordering of the collection items is important.

  • Enum types. All enum types defined in the same packages as the IVI service interface are automatically registered. This includes inner enum types. All other imported enum types need to be registered as part of the IviService annotation.

  • MirrorableMap. Only the changes to the map are sent to all clients.

  • IviDataSource. An IVI data source can be used to expose a data set to clients without requiring the full data set to be in memory. It also allows data to be queried and sorted at the service side.

  • Short and Char.

  • EnumSet.

  • Locale.

The generated code will assume that any imported type implements the android.os.Parcelable or the androidx.versionedparcelable.VersionedParcelable interface. A strong preference should be given towards regular Parcelable, as the use of VersionedParcelable is much slower.

Custom types may require code to opt-in to experimental API if the type is annotated with a RequiresOptIn annotation. The service interface must forward the opt-in requirement to its clients by annotating properties, functions or the interface with the same annotation. Do not use OptIn in this case. The only valid use case for an OptIn annotation in a service interface is when the interface uses an experimental IVI service annotation.

For consistent naming, an interface that is annotated with the IviService annotation must have a name that has the Service suffix. It also needs to have a companion object, which can be empty. The companion object is required to allow extension functions, such as <Interface>.createApi(), to be generated.

The service interface, properties, functions, event listener interfaces and event listener interface functions can be annotated with annotation types that are annotated with RequiresOptIn. Any other custom annotation types are not supported.

Example

@IviService(
serviceId = "org.example.services.foo"
)
interface FooService {

@IviServiceEventListener
interface GroupedEventsListener {
fun onSomethingHappened()
fun onSomethingHappenedWithArguments(int: Int, data: Data)
}

@IviServiceEventListener
interface SingleEventListener {
fun onEpoch()
}

@Parcelize
data class Data(val foo: Int, val bar: String) : Parcelable

val intProp: Int
val dataProp: Data

@IviServiceFun
suspend fun doSomethingWithReturnValue(): Int

@IviServiceFun
suspend fun returnSomeData(): Data

@IviServiceFun
suspend fun doSomethingWithoutReturnValue()

@IviServiceFun
suspend fun testDefaultArgument(argument: Int = 42)

companion object
}

The <Interface>Api class in the example has the following public members:

class FooService<Api> {
val serviceAvailable: LiveData<Boolean>

val intProp: LiveData<Int>
val dataProp: LiveData<FooService.Data>

fun doSomethingWithReturnValueAsync(
onResult: ((result: IviServiceFunResult<Int?>) -> Unit)? = null
)
suspend fun coDoSomethingWithReturnValue(): Int

fun returnSomeDataAsync(
argument: Int,
onResult: ((result: IviServiceFunResult<ExampleService.Data?>) -> Unit)? = null
)
suspend fun coReturnSomeData(argument: Int): ExampleService.Data

fun doSomethingWithoutReturnValueAsync(
onResult: ((result: IviServiceFunResult<Unit?>) -> Unit)? = null
)
suspend fun coDoSomethingWithoutReturnValue() {

fun testDefaultArgumentAsync(
argument: Int,
onResult: ((result: IviServiceFunResult<Unit?>) -> Unit)? = null
)
suspend fun coTestDefaultArgument(argument: Int)
fun testDefaultArgumentAsync(
onResult: ((result: IviServiceFunResult<Unit?>) -> Unit)? = null
)
suspend fun coTestDefaultArgument()

fun addGroupedEventListener(
lifecycleOwner: LifecycleOwner,
listener: FooService.GroupedEventsListener
)
fun removeGroupedEventListener(listener: FooService.GroupedEventsListener)
fun addSingleEventListener(
lifecycleOwner: LifecycleOwner,
listener: FooService.SingleEventListener
)
fun removeSingleEventListener(listener: FooService.SingleEventListener)
}

The <Interface>Api class instance can be created by calling the <Interface>.createApi() extension function. The service can be optionally registered in the application; then <Interface>.createApiOrNull() can be used instead.

Constructors

Link copied to clipboard
fun IviService(    serviceId: String,     importedEnums: Array<KClass<*>> = [],     traceEvents: Boolean = true,     multipleInstances: Boolean = false)

Properties

Link copied to clipboard
val importedEnums: Array<KClass<*>>

An array of enum types that are imported. The code generator will otherwise assume the imported type is an android.os.Parcelable type.

Link copied to clipboard
val multipleInstances: Boolean = false

Denotes this interface as a multiple instance interface. If this set to true, the service implementation can be started for each IVI instance. If false the service will run as a global instance.

Link copied to clipboard
val serviceId: String

A unique service identifier.

Link copied to clipboard
val traceEvents: Boolean = true

By default, trace events are generated for all property changes, IVI service member functions and events. This can be disabled by setting this to false. It may be useful for a logging service to avoid log duplications.