# Avo Inspector Android SDK

## Quick Start Guide

Find the Quick Start Guide in our [GitHub repo](https://github.com/avohq/android-avo-inspector).

## Installation

We host the library on JitPack.io, so

add the following to the root build.gradle:

```groovy
allprojects {
    repositories {
      ...
      maven { url 'https://jitpack.io' }
    }
}
```

and in your module build.gradle:

```groovy
dependencies {
    debugImplementation 'com.github.avohq.android-avo-inspector:dev:TAG' // Includes the visual inspector, a tool useful to monitor your analytics calls when developing
    releaseImplementation 'com.github.avohq.android-avo-inspector:prod:TAG' // Does not include the visual inspector
}
```

Use the latest GitHub release tag to get the latest version of the library.

## ProGuard

If you are using ProGuard add the following line to your proguard rules file:

```proguard
-keep class app.avo.** { *; }
```

## Import

```java
import app.avo.inspector.AvoInspector;
import app.avo.inspector.AvoInspectorEnv;
```

```kotlin
import app.avo.inspector.AvoInspector
import app.avo.inspector.AvoInspectorEnv
```

## Initialization

Obtain the API key in Inspector tab in your [Avo.app](https://www.avo.app/welcome) workspace.

You will need to create an instance of `AvoInspector` with the constructor.

```java
public AvoInspector(@NonNull String apiKey, @NonNull Application application, @NonNull AvoInspectorEnv env, @Nullable Activity rootActivityForVisualInspector, @Nullable String publicEncryptionKey)
```

```kotlin
class AvoInspector(apiKey: String, application: Application, env: AvoInspectorEnv, rootActivityForVisualInspector: Activity?, publicEncryptionKey: String?)
```

> #### Parameters:
>
> - `String apiKey` - the API key you get in Inspector tab of your Avo workspace
> - `Application application` - reference to your Application class
> - `AvoInspectorEnv env` - current environment: development, staging or production
> - `Activity rootActivityForVisualInspector` - you root activity that will be used to show Visual Inspector, a view where you can track the reported schemas.
> - `String publicEncryptionKey` (optional) - public key used to encrypt property values for secure debugging in Inspector.

[More about the Visual Inspector below](#visual-inspector)

#### Property value validation

You can enable property value validation to validate property values against the [constraints defined in your tracking plan](https://www.avo.app/docs/data-design/avo-tracking-plan/properties.md#property-types-and-constraints), such as allowed values, regex patterns, and min/max ranges.

> 💡 Property value validation is available for version 2.4.0+ of the Android Inspector SDK. If you are using an older version, please update to the latest version.

To enable property value validation:

1. **Enable property value validation in settings** - Go to workspace settings in your Avo workspace and enable [property value validation](https://www.avo.app/docs/inspector/inspector-debugger.md#enabling-advanced-debugger-features).

To be able to [decrypt the property values](https://www.avo.app/docs/inspector/inspector-debugger.md#decrypting-property-values) in the Inspector Debugger, you need to generate a public/private key pair:

**Step 1: Generate encryption keys**

Run the following command in your terminal to generate a public/private key pair:

```bash
node -e "const { createECDH } = require('crypto'); const ecdh = createECDH('prime256v1'); ecdh.generateKeys(); console.log('Private Key:', ecdh.getPrivateKey('hex')); console.log('Public Key:', ecdh.getPublicKey('hex', 'compressed'));"
```

This will output:
- **Public key** - Used by Avo to encrypt property values
- **Private key** - Used by you to decrypt values in the Inspector Debugger

**Step 2: Store your private key securely**

Save your private key in a secure location like a password manager. You'll need this to [decrypt values in the Inspector Debugger](https://www.avo.app/docs/inspector/inspector-debugger.md#decrypting-property-values). Never share or expose your private key to a third party.

**Step 3: Add the public key to the Inspector initialization**

Pass the public key when initializing the `AvoInspector` instance. Refer to the [Initialization](#initialization) section above for the constructor signature.

> 🔒 Property values are encrypted end-to-end. Avo only stores encrypted values and cannot decrypt them. Only you can decrypt the values using your private key in the Inspector Debugger dashboard.

Learn more about [decrypting property values in the Inspector Debugger](https://www.avo.app/docs/inspector/inspector-debugger.md#decrypting-property-values) and the [property value issue types](https://www.avo.app/docs/inspector/issue-types-in-inspector.md#property-value-issues) that can be detected.

## Sending event schemas to Avo Inspector

This is the core of the **Avo Inspector SDK**.

Call \***\*one of the methods\*\*** in this section every time an event is tracked.

### Option 1

```java
@NonNull Map<String, AvoEventSchemaType> trackSchemaFromEvent(@NonNull String eventName, @Nullable JSONObject eventProperties);
// or
@NonNull Map<String, AvoEventSchemaType> trackSchemaFromEvent(@NonNull String eventName, @Nullable Map<String, ?> eventProperties);
```

```kotlin
fun trackSchemaFromEvent(eventName: String, eventProperties: JSONObject?): Map<String, AvoEventSchemaType>
// or
fun trackSchemaFromEvent(eventName: String, eventProperties: Map<String, *>?): Map<String, AvoEventSchemaType>
```

Extracts event schema from event properties represented by the second parameter (`JSONObject` or `Map<String, ?>`) and sends the schema to **Avo** for analysis.

> #### Parameters:
>
> - `String eventName` - event name, sometimes referred as event type.
> - `@Nullable JSONObject eventProperties` or `@Nullable Map<String, ?> eventProperties` - actual event properties, which will be converted to event schema on the device and the event schema will be sent to **Avo**. Resulting keys will be JSON fields keys and resulting values will be JSON fields values types converted to schema types.
>
> ##### Example format:
>
> ```java
> JSONObject eventProperties = new JSONObject();
> eventProperties.put("userId", 1337);
> eventProperties.put("emailAddress", "jane.doe@avo.app");
> eventProperties.put("key", "value");
> ```
>
> ```kotlin
> val eventProperties = JSONObject().apply {
>   put("userId", 1337)
>   put("emailAddress", "jane.doe@avo.app")
>   put("key", "value")
> }
>
> ```

> #### Return Type:
>
> - `@NonNull Map<String, AvoEventSchemaType>` containing event schema, so you can verify that conversion was correct.
>   Example format:
>
> ```java
> Map<String, AvoEventSchemaType> eventSchema = new HashMap<>();
> eventSchema.put("userId", new AvoEventSchemaType.AvoInt());
> eventSchema.put("emailAddress", new AvoEventSchemaType.AvoString());
> eventSchema.put("key", newAvoEventSchemaType.AvoString());
> ```
>
> ```kotlin
> val eventSchema = mutableMapOf<String, AvoEventSchemaType>().apply {
>   put("userId", AvoInt())
>   put("'emailAddress'", AvoString())
>   put("'key'", AvoString())
> }
> ```

### Option 2

```java
void trackSchema(@NonNull String eventName, @Nullable Map<String, AvoEventSchemaType> eventSchema);
```

```kotlin
void trackSchema(@NonNull String eventName, @Nullable Map<String, AvoEventSchemaType> eventSchema);
```

This method allows you to process the event schema before sending it.
It's handy to extract the schema from your event properties with `extractSchema(Object eventProperties)` (see below), process it and then provide it to this method.

> #### Parameters:
>
> - `@NonNull String eventName` - event name, also known as event type.
> - `@Nullable Map<String, AvoEventSchemaType> eventSchema` - actual event schema that will be sent to **Avo**.
>
> ##### Example format:
>
> ```java
> Map<String, AvoEventSchemaType> eventSchema = new HashMap<>();
> eventSchema.put("userId", new AvoEventSchemaType.AvoInt());
> eventSchema.put("emailAddress", new AvoEventSchemaType.AvoString());
> eventSchema.put("key", new AvoEventSchemaType.AvoString());
> ```
>
> ```kotlin
> val eventSchema = mutableMapOf<String, AvoEventSchemaType>().apply {
>    put("userId", AvoInt())
>    put("emailAddress", AvoString())
>    put("key", AvoString())
> }
> ```

See [this](https://www.avo.app/docs/reference/avo-inspector-sdks/overview.md#standalone-usage) for details about event schema structure and schema types.

## Other methods

### 1. Extract schema from event properties

```
java @NonNull Map<String, AvoEventSchemaType> extractSchema(@Nullable Object eventProperties);`kotlin
fun extractSchema(eventProperties: Any?): Map<String, AvoEventSchemaType>
```

This is the method used by `trackSchemaFromEvent` internally. Extracts event schema in form of `Map<String, AvoEventSchemaType>` from an event properties object.

> #### Parameters:
>
> - `@Nullable Object eventProperties` - event properties object. If it is an instance of `JSONObject` or `Map<?, ?>` it will be processed based on the containing key-value pairs, otherwise the event schema will be based on object fields.

> #### Return Type:
>
> - `@NonNull Map<String, AvoEventSchemaType>` containing event schema of the given event properties. Keys are event properties names and values are event properties types.
>
> ##### Example format:
>
> ```java
> Map<String, AvoEventSchemaType> eventSchema = new HashMap<>();
> eventSchema.put("userId", new AvoEventSchemaType.AvoInt());
> eventSchema.put("emailAddress", new AvoEventSchemaType.AvoString());
> eventSchema.put("key", newAvoEventSchemaType.AvoString());
> ```
>
> ```kotlin
> val eventSchema = mutableMapOf<String, AvoEventSchemaType>().apply {
>    put("userId", AvoInt())
>    put("'emailAddress'", AvoString())
>    put("'key'", AvoString())
> }
>
> ```

### 2. Print logs

```java
static void enableLogging(boolean enable); // static method on AvoInspector
```

```kotlin
fun enableLogging(enable: Boolean) // static method on AvoInspector
```

`enableLogging` controls printing of tracked event schemas and other helpful information to logcat. Enabled by default in development environments.

> #### Parameters:
>
> - `boolean enable` - sets whether **Avo Inspector SDK** will print logs to the logcat.

### 3. Control batching size

```java
static public void setBatchSize(int newBatchSize) // static method on AvoInspector
```

```kotlin
fun setBatchSize(newBatchSize: Int) // static method on AvoInspector
```

Enables manual control over events batching. Default batch size in production is 30, i.e. the library attempts to send event schemas to the server when it has 30 or more schemas.
In development batching is disabled by default.

> #### Parameters:
>
> - `int newBatchSize` - sets batch size.

### 4. Control batching interval

```java
static public void setBatchFlushSeconds(int newBatchFlushSeconds) // static method on AvoInspector
```

```kotlin
fun setBatchFlushSeconds(newBatchFlushSeconds: Int) // static method on AvoInspector
```

Enables manual control over events batching. Default production batch flush interval is 30 seconds, i.e. the library attempts to send event schemas to the server when 30 or more seconds pass, given there are unsent schemas.

> #### Parameters:
>
> - `int newBatchFlushSeconds` - sets batch flush time in seconds.

##  Using the Visual Inspector

Visual inspector is actually our [Mobile Debugger](https://www.avo.app/docs/reference/avo-debuggers/overview.md) integrated in Avo Inspector SDK.

Visual Inspector is enabled in development and staging environments by default.

### Show

```java
void showVisualInspector(Activity rootActivity, DebuggerMode visualInspectorMode);
```

```kotlin
fun showVisualInspector(rootActivity: Activity, visualInspectorMode: DebuggerMode)
```

### Hide

```java
void hideVisualInspector(Activity rootActivity);
```

```kotlin
fun hideVisualInspector(rootActivity: Activity)
```

### Get instance of `DebuggerManager`

```java
@Nullable Object getVisualInspector();
```

```kotlin
fun getVisualInspector(): Object?
```

In the `:dev` dependency the returned object is a nullable `DebuggerManager`. In the `:prod` dependency it always returns null.
See more about the DebuggerManager in it's [GitHub repo](https://github.com/avohq/android-analytics-debugger).

## Auto inspection with Segment SDK

Here is a code snippet you can use to easily integrate with **Segment**.
After registering the middleware schemas of all the events sent to **Segment** will automatically be sent to **Avo**, i.e. you don't need to call `trackSchemaFromEvent` or `trackSchema` methods.

```java
Middleware avoInspectorMiddleware = new Middleware() {
    @Override
    public void intercept(Chain chain) {
        BasePayload payload = chain.payload();
        if (payload.type() == BasePayload.Type.track) {
            TrackPayload trackPayload = (TrackPayload) payload;
            avoInspector.trackSchemaFromEvent(trackPayload.event(), trackPayload.properties());
        }
        chain.proceed(payload);
    }
};

Analytics analytics = new Analytics.Builder(getApplicationContext(), "SEGMENT_ANALYTICS_WRITE_KEY")
    .middleware(avoInspectorMiddleware)
    .build();
```

```kotlin
val avoInspectorMiddleware = Middleware { chain ->
    val payload = chain.payload()
    if (payload.type() == BasePayload.Type.track) {
        val trackPayload = payload as TrackPayload
        avoInspector.trackSchemaFromEvent(trackPayload.event(), trackPayload.properties())
    }
    chain.proceed(payload)
}

val analytics = Analytics.Builder(applicationContext, "SEGMENT_ANALYTICS_WRITE_KEY")
    .middleware(avoInspectorMiddleware)
    .build()
```

See [Segment android middleware docs](https://segment.com/docs/connections/sources/catalog/libraries/mobile/android/#middlewares) for more information.
