Skip to content

ridelinktechs/devconnect

Repository files navigation

DevConnect

A modern, cross-platform alternative to Reactotron and Flipper

Debug Flutter, React Native & Android apps — network, state, logs, storage, database — all in one beautiful desktop tool.

Platform Flutter React Native Android License: MIT

Features · Download · Quick Start · Why DevConnect? · SDKs


Why DevConnect?

If you've used Reactotron, Flipper, or Flutter DevTools — you know they're powerful but limited to one framework. DevConnect is a single desktop app that works with all of them.

DevConnect Reactotron Flipper Flutter DevTools
Flutter support ⚠️ plugin
React Native support
Android Native support
Network inspector
State debugging ✅ Redux/MobX/Zustand/BLoC/Riverpod/GetX ✅ Redux/MobX
Log viewer
Storage viewer
Database browser
Performance profiling ✅ FPS/CPU/Memory/Jank
Memory leak detection
Benchmark timing
Custom commands
Multi-device
Zero-config setup ✅ auto-detect ❌ manual ⚠️ ⚠️
macOS + Windows ⚠️ deprecated
Dark + Light theme
Active maintenance ⚠️ slow ❌ deprecated

TL;DR — One tool to replace Reactotron + Flipper + DevTools. Works with Flutter, React Native, and Android Native. Auto-detects everything.


Features

  • Network Inspector — HTTP request/response viewer with headers, body (tree + JSON), timing bar, copy as cURL, status badges
  • State Inspector — Real-time state change timeline with before/after diff for Redux, MobX, Zustand, Jotai, Valtio, XState, BLoC, Riverpod, GetX, Provider, ViewModel, StateFlow, LiveData
  • Console / Logs — Log viewer with level filters (debug/info/warn/error), search, tags, metadata, stack traces
  • Storage Viewer — Browse and monitor SharedPreferences, AsyncStorage, Hive, MMKV, SecureStorage, DataStore
  • Database Browser — SQLite, Drift, Room, Isar table viewer with SQL query editor
  • Performance Profiling — Real-time FPS, CPU, memory usage charts with jank frame detection
  • Memory Leak Detection — Detect undisposed controllers, streams, timers, growing collections with severity levels and stack traces
  • Benchmark — Performance timing with step markers
  • Custom Commands — Send commands from desktop to app and get results
  • Multi-Device — Connect multiple apps simultaneously, per-device filtering
  • All Events — Unified timeline of all events across features
  • Screenshot — Full-content screenshot capture of any detail panel
  • ADB Reverse — One-click USB connection for Android devices
  • Auto-detect — SDK auto-discovers desktop IP, zero configuration needed
  • Dual Theme — Dark and light mode

Download

Platform File Architecture
macOS DevConnect-macOS-v1.0.0-universal.dmg arm64 + x86_64
Windows DevConnect-Windows-v1.0.0.zip x64

Download from Releases.


Quick Start

Flutter — 2 lines

import 'package:devconnect_flutter/devconnect_flutter.dart';

void main() async {
  await DevConnect.initAndRunApp(
    appName: 'MyApp',
    runApp: () => runApp(const MyApp()),
  );
  // Done. Network + logs auto-captured.
}

React Native — 1 line

import { DevConnect } from 'devconnect-react-native';

await DevConnect.init({ appName: 'MyApp' });
// Done. fetch + XHR + console auto-captured.

Android Native — 1 line

// Application.onCreate()
DevConnect.init(context = this, appName = "MyApp")

That's it. Open DevConnect desktop, run your app, and everything appears.


Desktop App

Build from source

Download

Platform File Architecture
macOS DevConnect-macOS-v1.0.0-universal.dmg arm64 + x86_64
Windows DevConnect-Windows-v1.0.0.zip x64

Download from Releases.

Build from source

git clone https://github.com/ridelinktechs/devconnect.git
cd devconnect
flutter pub get
dart run build_runner build --delete-conflicting-outputs
flutter build macos --release   # macOS
flutter build windows --release # Windows
# Output: build/macos/Build/Products/Release/DevConnect.app

Features

  • Console/Logs - real-time log viewer, level filters, search, clear
  • Network Inspector - request/response, headers, body, timing, copy cURL, copy response
  • State Inspector - state change timeline, before/after diff, snapshot + restore
  • Storage Viewer - SharedPreferences, AsyncStorage, Hive, MMKV, SecureStorage
  • Database Viewer - SQLite, Drift, Room, Isar with query editor
  • Performance Profiling - real-time FPS, CPU, memory charts with jank detection
  • Memory Leak Detection - severity-based leak viewer with stack traces and metadata
  • Benchmark - performance timing with steps
  • Custom Commands - send commands from desktop to app
  • Device Panel - connected devices with platform badge, OS version
  • ADB Reverse - one-click for Android USB
  • Port Config - change WebSocket port in Settings
  • Auto-detect Host - SDK auto-finds desktop IP
  • Dual Theme - dark / light

Flutter SDK

Install

# From pub.dev (after published)
flutter pub add devconnect_flutter

Or from GitHub:

# pubspec.yaml
dependencies:
  devconnect_flutter:
    git:
      url: https://github.com/ridelinktechs/devconnect.git
      path: client_sdks/devconnect_flutter

Init

import 'package:devconnect_flutter/devconnect_flutter.dart';

void main() async {
  // Auto-detect: captures all HTTP + logs automatically
  await DevConnect.initAndRunApp(
    appName: 'MyApp',
    runApp: () => runApp(const MyApp()),
  );
}

Config

await DevConnect.initAndRunApp(
  appName: 'MyApp',
  runApp: () => runApp(const MyApp()),
  appVersion: '1.0.0',
  host: null,                  // null = auto-detect, '192.168.1.100' = manual
  port: 9090,                  // default: 9090
  enabled: true,               // false = disable (production)
  autoInterceptHttp: true,     // auto-capture all HTTP (default: true)
  autoInterceptLogs: true,     // auto-capture print/debugPrint (default: true)
);

Manual Setup

initAndRunApp() already calls init() + httpOverrides() + runZoned() internally. Only use manual setup if you need to control each step separately (e.g., custom HttpOverrides, custom Zone, or init without runApp).

void main() async {
  // Step 1: Connect to DevConnect desktop (WebSocket)
  await DevConnect.init(appName: 'MyApp');

  // Step 2: Intercept ALL HTTP globally (http, Dio, Chopper, etc.)
  // Skip if you already have a custom HttpOverrides
  HttpOverrides.global = DevConnect.httpOverrides();

  // Step 3: Capture print/debugPrint logs via Zone + run app
  DevConnect.runZoned(() => runApp(const MyApp()));
}

Network

Auto-captured via HttpOverrides: http, Dio, Chopper, Retrofit, GraphQL, Firebase, OAuth2, gRPC-web, Image.network.

// Dio (optional, for extra detail)
dio.interceptors.add(DevConnect.dioInterceptor());

// GetX GetConnect
final connect = GetConnect();
connect.httpClient.addRequestModifier(DevConnect.getConnectModifier());
connect.httpClient.addResponseModifier(DevConnect.getConnectResponseModifier());

Logs

Auto-captured via Zone: print, debugPrint, logger, talker, logging, fimber, simple_logger.

// Manual
DevConnect.log('User logged in');
DevConnect.debug('Token refreshed', tag: 'Auth');
DevConnect.warn('Rate limit approaching');
DevConnect.error('Payment failed', stackTrace: StackTrace.current.toString());

// Tagged logger
final logger = DevConnect.logger('AuthService');
logger.info('Login success');

// Loggy
final printer = DevConnect.loggyPrinter();

State

// Riverpod
class DevConnectObserver extends ProviderObserver {
  @override
  void didUpdateProvider(ProviderBase p, Object? prev, Object? next, ProviderContainer c) {
    DevConnect.reportStateChange(
      stateManager: 'riverpod',
      action: '${p.name ?? p.runtimeType} updated',
      previousState: {'value': prev.toString()},
      nextState: {'value': next.toString()},
    );
  }
}

// main.dart
runApp(
  ProviderScope(
    observers: [DevConnectObserver()],
    child: const MyApp(),
  ),
);
// BLoC
class DevConnectBlocObserver extends BlocObserver {
  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    DevConnect.reportStateChange(
      stateManager: 'bloc',
      action: '${bloc.runtimeType} changed',
      previousState: {'state': change.currentState.toString()},
      nextState: {'state': change.nextState.toString()},
    );
  }
}

// main.dart
Bloc.observer = DevConnectBlocObserver();
// Provider / ChangeNotifier
class MyModel extends ChangeNotifier {
  String _name = '';
  set name(String val) {
    final prev = _name;
    _name = val;
    notifyListeners();
    DevConnect.reportStateChange(
      stateManager: 'provider',
      action: 'name changed',
      previousState: {'name': prev},
      nextState: {'name': val},
    );
  }
}
// GetX
class MyController extends GetxController {
  final count = 0.obs;

  void increment() {
    final prev = count.value;
    count.value++;
    DevConnect.reportStateChange(
      stateManager: 'getx',
      action: 'increment',
      previousState: {'count': prev},
      nextState: {'count': count.value},
    );
  }
}
// MobX
final counter = Observable(0);
autorun((_) {
  DevConnect.reportStateChange(
    stateManager: 'mobx',
    action: 'counter changed',
    nextState: {'counter': counter.value},
  );
});
// Signals
final observer = DevConnect.signalsObserver();
observer.observe(mySignal, 'counterSignal');

Storage

// SharedPreferences
final reporter = DevConnect.sharedPreferencesReporter();
reporter.reportWrite('token', 'abc123');
reporter.reportRead('token', 'abc123');
reporter.reportDelete('token');

// Hive
final hiveReporter = DevConnect.hiveReporter();
hiveReporter.reportWrite('settings', {'darkMode': true});

// flutter_secure_storage
final secureReporter = DevConnect.secureStorageReporter();
secureReporter.reportWrite('token', 'secret');
secureReporter.reportRead('token', '***');

// MMKV
final mmkvReporter = DevConnect.mmkvReporter();
mmkvReporter.reportWrite('key', 'value');
mmkvReporter.reportRead('key', 'value');

Database

// Drift
final driftReporter = DevConnect.driftReporter();
driftReporter.reportQuery('SELECT * FROM users', results);

// Drift auto-intercept
final executor = DevConnect.driftQueryExecutor(innerExecutor);

// Isar
final isarReporter = DevConnect.isarReporter();
isarReporter.reportQuery('User', results);
isarReporter.reportPut('User', {'name': 'John'});

Performance Profiling

// Report FPS
DevConnect.reportPerformanceMetric(
  metricType: 'fps',
  value: 58.5,
  label: 'Main Thread FPS',
);

// Report memory usage (MB)
DevConnect.reportPerformanceMetric(
  metricType: 'memory_usage',
  value: 142.3,
  label: 'Dart Heap',
);

// Report CPU usage (%)
DevConnect.reportPerformanceMetric(
  metricType: 'cpu_usage',
  value: 35.2,
);

// Report jank frame (build time in ms)
DevConnect.reportPerformanceMetric(
  metricType: 'jank_frame',
  value: 32.1,
  label: 'Slow build in ListView',
);

Available metric types: fps, frame_build_time, frame_raster_time, memory_usage, memory_peak, cpu_usage, jank_frame.

Memory Leak Detection

// Report undisposed controller
DevConnect.reportMemoryLeak(
  leakType: 'undisposed_controller',
  severity: 'warning',
  objectName: 'AnimationController',
  detail: 'AnimationController not disposed in ProfileScreen',
  retainedSizeBytes: 2048,
  stackTrace: StackTrace.current.toString(),
);

// Report growing collection
DevConnect.reportMemoryLeak(
  leakType: 'growing_collection',
  severity: 'critical',
  objectName: 'eventCache',
  detail: 'List grows unbounded — 15000 items, expected < 100',
  retainedSizeBytes: 1200000,
  metadata: {'currentSize': 15000, 'maxExpected': 100},
);

Available leak types: undisposed_controller, undisposed_stream, undisposed_timer, undisposed_animation_controller, widget_leak, growing_collection, custom.

Severity levels: info, warning, critical.

Benchmark

DevConnect.benchmarkStart('loadHome');
await fetchUser();
DevConnect.benchmarkStep('loadHome');
await fetchPosts();
DevConnect.benchmarkStop('loadHome');

Custom Commands

DevConnect.registerCommand('clearCache', (args) {
  return {'cleared': true};
});

State Snapshot + Restore

// Send a state snapshot to desktop
DevConnect.sendStateSnapshot(
  stateManager: 'riverpod',
  state: {'counter': 42, 'user': userMap},
);

// Handle state restore from desktop
DevConnect.onStateRestore = (state) {
  ref.read(appStateProvider.notifier).restore(state);
};

Connection Management

// Check connection status
if (DevConnect.isConnected) {
  print('Connected to DevConnect desktop');
}

// Disconnect
await DevConnect.disconnect();

Navigation

MaterialApp(navigatorObservers: [DevConnect.navigationObserver()])
GoRouter(observers: [DevConnect.navigationObserver()])

React Native SDK

Install

# From npm (after published)
yarn add devconnect-react-native
# or
npm install devconnect-react-native

Or from GitHub:

yarn add github:ridelinktechs/devconnect#main

Init

import { DevConnect } from 'devconnect-react-native';

await DevConnect.init({ appName: 'MyApp' });
// Auto-captures: fetch, XHR, console.log/warn/error

Config

await DevConnect.init({
  appName: 'MyApp',
  appVersion: '1.0.0',
  host: undefined,            // undefined = auto-detect, '192.168.1.100' = manual
  port: 9090,                 // default: 9090
  enabled: __DEV__,           // false in production
  autoInterceptFetch: true,
  autoInterceptXHR: true,
  autoInterceptConsole: true,
});

Network

Auto-captured: fetch, XHR, axios, got, ky, superagent, apisauce, Apollo, urql, TanStack Query, SWR, RTK Query, ofetch, wretch, redaxios, Firebase, OAuth2.

// Axios (optional, for extra tagging)
import { setupAxiosInterceptor } from 'devconnect-react-native';
setupAxiosInterceptor(axios);

Logs

Auto-captured: console.log, console.debug, console.info, console.warn, console.error, console.trace. Also auto-captures: consola, debug, tslog, signale (they use console internally).

// Manual
DevConnect.log('User logged in');
DevConnect.debug('Debug info', 'Auth');
DevConnect.warn('Warning');
DevConnect.error('Error', 'Tag', stackTrace);

// react-native-logs
import { devConnectTransport } from 'devconnect-react-native';
const log = logger.createLogger({ transport: [consoleTransport, devConnectTransport] });

// loglevel
import { patchLoglevel } from 'devconnect-react-native';
patchLoglevel(log);

// pino
import { pinoDevConnectTransport } from 'devconnect-react-native';
const logger = pino({}, pinoDevConnectTransport());

// winston
import { winstonDevConnectTransport } from 'devconnect-react-native';

// bunyan
import { bunyanDevConnectStream } from 'devconnect-react-native';

// Any custom logger
import { wrapLogger } from 'devconnect-react-native';
const wrapped = wrapLogger(myLogger, 'myLoggerName');

State

// Redux - Classic createStore
import { createStore, applyMiddleware, compose } from 'redux';
import { devConnectReduxMiddleware } from 'devconnect-react-native';
import { DevConnect } from 'devconnect-react-native';

let store;
if (__DEV__) {
  store = createStore(
    rootReducer,
    compose(applyMiddleware(thunkMiddleware, devConnectReduxMiddleware))
  );
  // Allow desktop to dispatch actions into the app
  DevConnect.connectReduxStore(store);
} else {
  store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
}
// Redux Toolkit
import { configureStore } from '@reduxjs/toolkit';
import { devConnectReduxMiddleware } from 'devconnect-react-native';
import { DevConnect } from 'devconnect-react-native';

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefault) =>
    __DEV__
      ? getDefault().concat(devConnectReduxMiddleware)
      : getDefault(),
});

if (__DEV__) DevConnect.connectReduxStore(store);
// MobX
import { spy } from 'mobx';
import { setupMobxSpy } from 'devconnect-react-native';

if (__DEV__) {
  setupMobxSpy(spy); // reports all observable changes
}
// Zustand
import { create } from 'zustand';
import { devConnectMiddleware } from 'devconnect-react-native';

// Wrap your store creator with devConnectMiddleware
const useStore = create(
  devConnectMiddleware(
    (set) => ({
      count: 0,
      name: '',
      increment: () => set((s) => ({ count: s.count + 1 })),
      setName: (name: string) => set({ name }),
    }),
    'CounterStore' // label shown in DevConnect desktop
  )
);
// Jotai
import { atom, createStore } from 'jotai';
import { watchAtom } from 'devconnect-react-native';

const countAtom = atom(0);
const store = createStore();

if (__DEV__) {
  // Watches atom and reports every value change
  const unsub = watchAtom(store, countAtom, 'countAtom');
  // unsub() to stop watching
}
// Valtio
import { proxy } from 'valtio';
import { watchValtio } from 'devconnect-react-native';

const state = proxy({ count: 0, user: { name: '' } });

if (__DEV__) {
  watchValtio(state, 'AppState'); // reports all proxy mutations
}
// XState
import { interpret } from 'xstate';
import { devConnectXStateInspector } from 'devconnect-react-native';

const service = interpret(toggleMachine);

if (__DEV__) {
  // Reports every state transition (event, from, to, context)
  service.onTransition(devConnectXStateInspector('ToggleMachine'));
}

service.start();

Storage

// AsyncStorage
import { DevConnectAsyncStorage } from 'devconnect-react-native';
DevConnectAsyncStorage.patchInPlace(AsyncStorage);

// MMKV
import { DevConnectMMKV } from 'devconnect-react-native';
DevConnectMMKV.wrap(storage);

Performance Profiling

// Report FPS
DevConnect.reportPerformanceMetric({
  metricType: 'fps',
  value: 58.5,
  label: 'JS Thread FPS',
});

// Report memory usage (MB)
DevConnect.reportPerformanceMetric({
  metricType: 'memory_usage',
  value: 142.3,
  label: 'JS Heap',
});

// Report CPU usage (%)
DevConnect.reportPerformanceMetric({
  metricType: 'cpu_usage',
  value: 35.2,
});

// Report jank frame (ms)
DevConnect.reportPerformanceMetric({
  metricType: 'jank_frame',
  value: 32.1,
  label: 'Slow render in FlatList',
});

Memory Leak Detection

// Report undisposed subscription
DevConnect.reportMemoryLeak({
  leakType: 'undisposed_stream',
  severity: 'warning',
  objectName: 'UserDataSubscription',
  detail: 'EventEmitter listener not removed in ProfileScreen',
  retainedSizeBytes: 2048,
  stackTrace: new Error().stack,
});

// Report growing collection
DevConnect.reportMemoryLeak({
  leakType: 'growing_collection',
  severity: 'critical',
  objectName: 'eventCache',
  detail: 'Array grows unbounded — 15000 items, expected < 100',
  retainedSizeBytes: 1200000,
  metadata: { currentSize: 15000, maxExpected: 100 },
});

Tagged Logger

const logger = DevConnect.logger('AuthService');
logger.log('User logged in');
logger.debug('Token refreshed');
logger.warn('Session expiring');
logger.error('Login failed', error.stack);

Manual Network Reporting

// When auto-interception is disabled or for custom transports
const requestId = 'req-123';

DevConnect.reportNetworkStart({
  requestId,
  method: 'POST',
  url: 'https://api.example.com/data',
  headers: { 'Content-Type': 'application/json' },
  body: { name: 'John' },
});

// After response
DevConnect.reportNetworkComplete({
  requestId,
  method: 'POST',
  url: 'https://api.example.com/data',
  statusCode: 200,
  startTime: 1711180800000,
  responseBody: { success: true },
});

Benchmark

DevConnect.benchmark('loadUserData');
await fetchUser();
DevConnect.benchmarkStep('loadUserData', 'fetched user');
await fetchPosts();
DevConnect.benchmarkStop('loadUserData');

Custom Commands

DevConnect.registerCommand('clearCache', () => {
  AsyncStorage.clear();
  return { success: true };
});

State Snapshot + Restore

DevConnect.sendStateSnapshot('redux', store.getState());
DevConnect.onStateRestore((state) => {
  store.dispatch({ type: 'RESTORE_STATE', payload: state });
});

Connection Management

// Check connection status
if (DevConnect.isConnected()) {
  console.log('Connected to DevConnect desktop');
}

// Disconnect
DevConnect.disconnect();

Android Native SDK

Install

// From Maven Central (after published)
dependencies {
    implementation("com.ridelink:devconnect-android:1.0.0")
}

Or from JitPack (GitHub):

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

// app/build.gradle.kts
dependencies {
    implementation("com.github.ridelinktechs.devconnect:devconnect-android:v1.0.0")
}

Or AAR file from Releases:

dependencies {
    implementation(files("libs/devconnect-android-1.0.0.aar"))
}

Init

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        DevConnect.init(
            context = this,
            appName = "MyApp",
        )
    }
}

Config

DevConnect.init(
    context = this,
    appName = "MyApp",
    appVersion = "1.0.0",
    host = null,                    // null = auto-detect, "192.168.1.100" = manual
    port = 9090,                    // default: 9090
    enabled = BuildConfig.DEBUG,    // false in release
    autoInterceptLogs = true,       // auto-capture println() (default: false)
)

Network

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

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

// Volley
val stack = object : HurlStack() {
    override fun createConnection(url: URL): HttpURLConnection {
        return DevConnectHttpURLConnection.wrap(super.createConnection(url))
    }
}

Logs

// Drop-in replacement for android.util.Log
// Change: import android.util.Log -> import com.devconnect.interceptors.DCLog as Log
// All existing Log.d/i/w/e calls will send to both Logcat AND DevConnect
import com.devconnect.interceptors.DCLog as Log

Log.d("MyTag", "Hello")       // -> Logcat + DevConnect
Log.e("MyTag", "Error", exception)
Log.w("MyTag", "Warning")
// Timber - add DevConnect tree alongside DebugTree
import com.devconnect.interceptors.DevConnectTimberHelper

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

// Application.onCreate()
Timber.plant(Timber.DebugTree())    // keep normal logcat
Timber.plant(DevConnectTree())       // add DevConnect reporting
// Intercept all println() calls
DevConnectLogInterceptor.interceptSystemOut()
// Kermit (KMP) - add DevConnect log writer
import co.touchlab.kermit.Logger
Logger.addLogWriter(DevConnect.kermitWriter())
// Napier (KMP) - set DevConnect as antilog
import io.github.aakira.napier.Napier
Napier.base(DevConnect.napierAntilog())
// Manual logging
DevConnect.sendLog("info", "User logged in", tag = "Auth")
DevConnect.sendLog("error", "Payment failed", tag = "Payment", stackTrace = e.stackTraceToString())

State

// ViewModel + StateFlow
class MyViewModel : ViewModel() {
    private val _state = MutableStateFlow(UserState())
    val state = _state.asStateFlow()

    fun updateName(name: String) {
        val prev = _state.value
        _state.value = prev.copy(name = name)
        DevConnectViewModelObserver.reportStateUpdate(
            viewModelName = "MyViewModel",
            action = "updateName",
            previousState = mapOf("name" to prev.name),
            nextState = mapOf("name" to name),
        )
    }
}
// Auto-observe StateFlow (reports every change automatically)
val observer = DevConnect.stateObserver()
observer.observe(viewLifecycleOwner.lifecycleScope, viewModel.state, "UserState")
// Auto-observe LiveData
val observer = DevConnect.stateObserver()
observer.observe(viewLifecycleOwner, viewModel.userLiveData, "UserLiveData")

Storage

// SharedPreferences
val reporter = DevConnect.sharedPrefsReporter()
reporter.reportWrite("token", "abc123")
reporter.reportRead("token", "abc123")
reporter.reportDelete("token")

// DataStore
val dsReporter = DevConnect.dataStoreReporter()
dsReporter.reportWrite("darkMode", true)
dsReporter.reportRead("darkMode", true)

// MMKV
val mmkvReporter = DevConnect.mmkvReporter()
mmkvReporter.reportWrite("key", "value")
mmkvReporter.reportRead("key", "value")

Database

// Room
val roomReporter = DevConnect.roomReporter()
roomReporter.reportQuery("SELECT * FROM users", results)
roomReporter.reportInsert("users", rowId)

Performance Profiling

// Report FPS
DevConnect.reportPerformanceMetric(
    metricType = "fps",
    value = 58.5,
    label = "Main Thread FPS"
)

// Report memory usage (MB)
DevConnect.reportPerformanceMetric(
    metricType = "memory_usage",
    value = 142.3,
    label = "Heap Used"
)

// Report CPU usage (%)
DevConnect.reportPerformanceMetric(
    metricType = "cpu_usage",
    value = 35.2
)

// Report jank frame (ms)
DevConnect.reportPerformanceMetric(
    metricType = "jank_frame",
    value = 32.1,
    label = "Slow render in RecyclerView"
)

Memory Leak Detection

// Report undisposed listener (integrates well with LeakCanary)
DevConnect.reportMemoryLeak(
    leakType = "undisposed_stream",
    severity = "warning",
    objectName = "LocationListener",
    detail = "LocationManager listener not removed in MapsActivity",
    retainedSizeBytes = 4096
)

// Report Activity leak (e.g. from LeakCanary)
DevConnect.reportMemoryLeak(
    leakType = "widget_leak",
    severity = "critical",
    objectName = "DetailActivity",
    detail = "Activity retained after onDestroy",
    stackTrace = leakTrace.toString()
)

// Report growing collection
DevConnect.reportMemoryLeak(
    leakType = "growing_collection",
    severity = "critical",
    objectName = "eventCache",
    detail = "ArrayList grows unbounded — 15000 items",
    retainedSizeBytes = 1200000,
    metadata = mapOf("currentSize" to 15000, "maxExpected" to 100)
)

Benchmark

DevConnect.benchmarkStart("loadHome")
fetchUser()
DevConnect.benchmarkStep("loadHome")
fetchPosts()
DevConnect.benchmarkStop("loadHome")

Custom Commands

DevConnect.registerCommand("clearCache") { args ->
    mapOf("cleared" to true)
}

State Snapshot + Restore

// Send a state snapshot to desktop
DevConnect.sendStateSnapshot("viewmodel", mapOf("user" to userMap))

// Handle state restore from desktop
DevConnect.onStateRestore = { state ->
    viewModel.restoreState(state)
}

Connection Management

// Check connection status
if (DevConnect.isConnected()) {
    Log.d("DC", "Connected to DevConnect desktop")
}

// Disconnect
DevConnect.disconnect()

Real Device Connection

Auto-detect (default)

SDK tries these addresses in order:

  1. localhost (iOS simulator, macOS)
  2. 10.0.2.2 (Android emulator)
  3. 10.0.3.2 (Genymotion)
  4. Scan local network subnet

Manual IP

Check your desktop IP in Settings page (click to copy), then:

await DevConnect.init(appName: 'MyApp', host: '192.168.1.5');

Android USB

In desktop Settings > Android Device (USB), click "Run ADB Reverse".

Or manually: adb reverse tcp:9090 tcp:9090


Architecture

  • Desktop: Flutter Desktop (macOS/Windows) + Riverpod + go_router + Freezed
  • Protocol: JSON over WebSocket (default port 9090)
  • SDKs: Flutter (pub.dev / git), React Native (npm / git), Android (Maven / JitPack / AAR)

Contributing

Contributions welcome! Feel free to open issues or pull requests.

git clone https://github.com/ridelinktechs/devconnect.git
cd devconnect
flutter pub get
dart run build_runner build --delete-conflicting-outputs
flutter run -d macos

Related Projects & Alternatives

Looking for mobile debugging tools? Here's how DevConnect compares:

  • Reactotron — Great for React Native + Redux, but no Flutter/Android support. DevConnect covers all three.
  • Flipper — Facebook's extensible debugger, now deprecated. DevConnect is actively maintained.
  • Flutter DevTools — Official Flutter debugging, but no React Native or Android Native. DevConnect adds cross-platform support.

Searching for: reactotron alternative, flipper replacement, flutter debugging tool, react native debugger, android debug inspector, mobile app debugger, cross-platform debugging, network inspector, state debugger, redux devtools mobile? DevConnect is built for you.


License

MIT - by buivietphi


DevConnect — Debug Flutter, React Native & Android apps from one desktop tool.

A modern alternative to Reactotron, Flipper, and platform-specific debugging tools.

Releases

No releases published

Packages

 
 
 

Contributors