Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 208 additions & 11 deletions client_sdks/devconnect-android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,65 @@ Debug your Android app with [DevConnect Manage Tool](https://github.com/ridelink

## Install

The SDK is published to **Maven Central** as `io.github.buivietphi:devconnect-android`.
`mavenCentral()` is usually already in your repository list, but if you've
stripped it down, add it back:

```gradle
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
google()
mavenCentral()
}
}

// app/build.gradle.kts
dependencies {
implementation("com.github.ridelinktechs.devconnect-manage-kit:devconnect-manage-android:v1.0.0")
implementation("io.github.buivietphi:devconnect-android:1.0.0")
}
```

## Runtime dependencies

The AAR does **not** bundle its runtime dependencies — Gradle AAR
consumption does not pull transitive `implementation` deps. You must
declare the following in your `app/build.gradle` if you use the matching
features (skip any line for features you don't use):

```gradle
dependencies {
// Always required (SDK's own implementation deps).
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("org.json:json:20260522")
implementation("org.jetbrains.kotlin:kotlin-reflect:2.2.0")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.11.0")

// Required only if you wire the OkHttp interceptor (compileOnly in the SDK).
implementation("com.squareup.okhttp3:okhttp:4.12.0")

// Required only if you call DevConnect.stateObserver().observe(...) manually.
// (autoViewModelDiscovery does NOT need these — it reflects directly.)
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.11.0")
}
```

If you forget `kotlin-reflect`, the app crashes on first Activity resume
with `NoClassDefFoundError: kotlin/reflect/full/KClasses` (the
`ViewModelAutoDiscoverer` uses `KClass.memberProperties` reflection).

## Quick Start

The fastest way to wire DevConnect is `installForApp()` — one call turns on
all auto-wiring flags: ANR detection, ViewModel state auto-discovery,
auto-intercepted logs and HTTP, performance, memory-leak and benchmark monitors.

```kotlin
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
DevConnect.init(
DevConnect.installForApp(
context = this,
appName = "MyApp",
enabled = BuildConfig.DEBUG,
Expand All @@ -36,8 +74,87 @@ class MyApp : Application() {
}
```

Java callers reach the Kotlin `object` singleton via `DevConnect.INSTANCE`:

```java
DevConnect.INSTANCE.installForApp(
/* context = */ this,
/* appName = */ "MyApp",
/* appVersion = */ BuildConfig.VERSION_NAME,
/* host = */ null,
/* port = */ 9090,
/* enabled = */ BuildConfig.DEBUG,
/* versionCode = */ String.valueOf(BuildConfig.VERSION_CODE)
);
```

### Recommended: `DevConnectJava` facade

Calling every entry point through `DevConnect.INSTANCE` and passing every
default argument positionally gets tedious. The SDK ships with a
Java-friendly facade — `com.devconnect.DevConnectJava` — that exposes
every public method as a plain `static` and overloads the most useful
default-argument combinations:

```java
import com.devconnect.DevConnectJava;

public class MyApplication extends Application {
@Override public void onCreate() {
super.onCreate();
DevConnectJava.installForApp(this, "MyApp", BuildConfig.DEBUG);
}
}
```

The full Java reference lives in
[`docs/java-usage.md`](docs/java-usage.md). The short version:

```java
// Lifecycle
DevConnectJava.installForApp(this, "MyApp", BuildConfig.DEBUG);
DevConnectJava.isConnected();
DevConnectJava.disconnect();

// Network — add to OkHttpClient.Builder (Retrofit, Firebase, OAuth2…)
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(DevConnectJava.okHttpInterceptor())
.build();

// Logs
DevConnectJava.log("User logged in", "AuthService");
DevConnectJava.error("Network failed", "AuthService", stackTrace);
DevConnectJava.sendLog("info", "Custom", "MyTag", null);

// Storage reporters
SharedPrefsReporter sp = DevConnectJava.sharedPrefsReporter();
sp.reportWrite("token", "abc");

// Custom command (Java-friendly functional interface)
DevConnectJava.registerCommand("clearCache", args -> {
// … do work …
return java.util.Collections.singletonMap("cleared", true);
});

// Reload override (skip the default Activity.recreate)
DevConnectJava.setOnReloadRequest(() -> {
// wipe in-memory state, then trigger your own reload
});
```

`installForApp` looks for OkHttp and Timber on the classpath and prints a
one-time hint (via `android.util.Log`) telling you how to wire them. The
SDK does not auto-wire Retrofit/Timber — see [Wiring OkHttp / Retrofit](#wiring-okhttp--retrofit)
and [Wiring Timber](#wiring-timber) below.

If you need finer control over which auto-* flags are enabled, call
[`init`](#config) directly instead.

## Config

Use `init()` when you need fine-grained control over which auto-* flags
are turned on:

```kotlin
DevConnect.init(
context = this,
Expand All @@ -47,6 +164,12 @@ DevConnect.init(
port = 9090, // default: 9090
enabled = BuildConfig.DEBUG, // false in release
autoInterceptLogs = true, // true = auto-capture println()
autoInterceptHttp = true, // true = auto-capture HttpURLConnection
autoPerformance = true, // true = auto-start performance monitor
autoMemoryLeak = true, // true = auto-start memory leak detection
autoBenchmark = true, // true = auto-start benchmark collector
autoAnrWatchdog = true, // true = auto-start the main-thread ANR watchdog
autoViewModelDiscovery = true, // true = auto-discover StateFlow/LiveData on ViewModels
)
```

Expand All @@ -64,18 +187,41 @@ DevConnect.init(

### Network

#### Wiring OkHttp / Retrofit

The SDK cannot auto-wire your `OkHttpClient` — you build it inside a DI
module (Hilt, Koin, Dagger), and the SDK has no hook to reach it. Add
`DevConnect.okHttpInterceptor()` once in the same `Builder` chain and
every Retrofit / OkHttp / Glide / Coil / Firebase call goes through the
inspector:

```kotlin
// OkHttp (captures Retrofit, Firebase, OAuth2, Glide, Coil)
val client = OkHttpClient.Builder()
.addInterceptor(DevConnect.okHttpInterceptor())
.build()

// Retrofit (Hilt / Dagger module)
@Provides @Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.API_BASE_URL)
.client(client)
.addConverterFactory(MoshiConverterFactory.create())
.build()
```

#### Wiring Ktor

```kotlin
// Ktor
val client = HttpClient {
install(DevConnect.ktorPlugin())
}
```

If you skip the wiring step, `installForApp` prints a single logcat line
pointing back to this section on startup.

### Logs

```kotlin
Expand All @@ -85,21 +231,39 @@ import com.devconnect.interceptors.DCLog as Log
Log.d("MyTag", "Hello") // -> Logcat + DevConnect
Log.e("MyTag", "Error", exception)

// Timber
// Kermit (KMP)
Logger.addLogWriter(DevConnect.kermitWriter())

// Napier (KMP)
Napier.base(DevConnect.napierAntilog())
```

#### Wiring Timber

The SDK does not auto-plant a Timber tree — `Timber.plant()` is an
explicit action in your `Application.onCreate()` and the SDK can't safely
do it for you. Plant a `Tree` that forwards to `DevConnect.sendLog(...)`:

```kotlin
class DevConnectTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
DevConnectTimberHelper.log(priority, tag, message, t)
}
}
Timber.plant(DevConnectTree())

// Kermit (KMP)
Logger.addLogWriter(DevConnect.kermitWriter())

// Napier (KMP)
Napier.base(DevConnect.napierAntilog())
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Timber.plant(DevConnectTree())
Timber.plant(Timber.DebugTree()) // optional: also keep logcat
DevConnect.installForApp(this, "MyApp", enabled = BuildConfig.DEBUG)
}
}
```

If you skip the Timber wiring, `installForApp` prints a single logcat
line pointing back to this section on startup.

### State

```kotlin
Expand All @@ -111,6 +275,39 @@ observer.observe(lifecycleScope, viewModel.state, "UserState")
observer.observe(viewLifecycleOwner, viewModel.userLiveData, "UserLiveData")
```

#### Auto-discovery

When `autoViewModelDiscovery = true` (the default for `installForApp`),
the SDK installs an `ActivityLifecycleCallbacks` hook that walks every
`ViewModelStore` for every Activity/Fragment in your app and reflects on
its `StateFlow`/`LiveData` properties. You don't need to call
`stateObserver().observe(...)` per ViewModel — the SDK does it for you.

Turn it off if you only want to expose a small subset of state:

```kotlin
DevConnect.init(
context = this,
appName = "MyApp",
enabled = BuildConfig.DEBUG,
autoViewModelDiscovery = false,
)
DevConnect.stateObserver().observe(lifecycleScope, viewModel.userState, "UserState")
```

### Crash & ANR detection

When `autoAnrWatchdog = true` (the default for `installForApp`), a
daemon thread pings the main `Looper` every 500 ms and reports an
`anr` `performance_metric` event the moment the main thread is blocked
for ≥6 seconds. The event payload includes the first 20 frames of the
main thread's stack trace.

The watchdog runs entirely on the JVM — no NDK, no native signal
handlers. C++/JNI crashes are not covered; report them via
`ErrorMonitor.reportNativeCrash(signal, stackTrace)` from your own
signal handler if you need them.

### Storage

Supports: SharedPreferences, DataStore, MMKV, Realm, ObjectBox, SQLDelight.
Expand Down Expand Up @@ -228,4 +425,4 @@ DevConnect.init(context = this, appName = "MyApp", enabled = BuildConfig.DEBUG)

## License

MIT - by [ridelinktechs](https://github.com/ridelinktechs)
MIT - by [buivietphi](https://github.com/buivietphi)
Loading
Loading