widget_rtlsdr
Immersive, gqrx/CubicSDR-inspired
widget library for RTL-SDR on Android/Flutter, built on top of
core_rtlsdr
(source), which is
itself built on driver_rtlsdr
(source).
core_rtlsdr gives you tuning/streaming/demod/gain/squelch/stereo/RDS/
spectrum/recording/scan/presets as plain ChangeNotifier controllers, but
deliberately ships no UI — its own example/ is intentionally minimal
Material widgets, just enough to prove the controllers are sufficient on
their own. widget_rtlsdr is that UI layer: a big digit frequency readout,
an FFT scope and waterfall styled like a real receiver, themed control
panels, and a settings navigation that adapts from a phone to a tablet —
all driven by the same controllers, so this package never touches
driver_rtlsdr or dart:ffi directly either.
What this package provides
- Display widgets:
FrequencyReadout(odometer-style digit tuner — drag or scroll a digit to change just that place, carries cascade into neighbors automatically, same interaction as gqrx's tuning display),SpectrumScope(FFT line/fill plot with a bottom frequency axis and a shaded demod-passband overlay around the tuned frequency, tap/drag to retune),WaterfallView(scrolling spectrogram — rendered as a singleCanvas.drawVerticesmesh regardless of how many bins/rows are on screen, so it stays smooth at the spectrum controller's ~25 fps default),SignalMeter(dBFS bar with a squelch threshold tick),ModeSelector(iteratesDemodMode.values, so it stays correct asdriver_rtlsdradds demodulation modes). - Control panels, one per
core_rtlsdrcontroller — each a self-contained, independently usableRtlSdrPanelsection:GainPanel,SquelchPanel,StereoRdsPanel,RecordingPanel,ScanPanel,PresetsPanel,StatsPanel,UsbStatusBanner. - Theming:
RtlSdrTheme/RtlSdrThemeData(.dark/.light) — a near-black "receiver bezel" palette plus a configurable waterfall colormap (defaultWaterfallColormap, a gqrx-style dark-blue→cyan→green→ yellow→red ramp). Every widget falls back to.darkif used without an ancestorRtlSdrTheme. RtlSdrImmersiveScreen: the full instrument view — meter, tuner, scope, waterfall — filling the screen.- Distributed settings navigation — the piece that answers "where do
gain/squelch/scan/presets/etc. actually live in my app?":
RtlSdrSettingsSectionis a plain, navigation-agnostic(id, title, icon, builder)unit. The same section list can be shown:- behind
RtlSdrImmersiveScreen's settings button viaRtlSdrSettingsScreen— a drill-down list pushing each section as its own full-screen subscreen on a phone, or a master-detail split (list + content, no navigation) on a tablet/wide window, switching automatically at a configurable breakpoint; - as a fixed side panel next to the scope on very wide windows
(automatic in
RtlSdrImmersiveScreenabovesideSettingsBreakpoint); - individually, anywhere in a host app's own navigation/router, via
RtlSdrSettingsSection.pageRoute()/RtlSdrSettingSectionScreen.
- behind
What this package deliberately does NOT provide
- A DI/state-management framework: every widget takes its controller
(
RadioController,ScanController, ...) as a plain constructor parameter — wire them up withprovider,riverpod, or nothing at all, same stancecore_rtlsdrtakes onRtlSdrDriver.example/usesprovider, but that's the example's choice, not a requirement. - FFI or Android-platform code of its own: it depends only on
core_rtlsdr's controllers.NativeRtlSdrDriver(the real, Android-only driver) and theUsbState/UsbChannelpermission flow arecore_rtlsdr/driver_rtlsdrconcerns — see their docs.
Installation
dependencies:
widget_rtlsdr: ^0.0.1
widget_rtlsdr depends on core_rtlsdr
(pulled in transitively), which itself depends on
driver_rtlsdr, which is
Android-only. See core_rtlsdr's README for the two things your app needs
beyond pubspec.yaml (a minSdk = 26 bump and a USB auto-open intent
filter) — nothing extra is needed for this package on top of that.
Usage
import 'package:core_rtlsdr/core_rtlsdr.dart';
import 'package:widget_rtlsdr/widget_rtlsdr.dart';
Minimal: just the immersive screen
Once you have a RadioController (see core_rtlsdr's README for the USB
permission flow + NativeRtlSdrDriver wiring):
RtlSdrTheme(
data: RtlSdrThemeData.dark(),
child: RtlSdrImmersiveScreen(radio: radio),
)
With distributed settings
RtlSdrImmersiveScreen(
radio: radio,
settingsSections: [
RtlSdrSettingsSection(
id: 'gain',
title: 'Gain',
icon: Icons.tune,
builder: (context) => GainPanel(radio: radio),
),
if (radio.demodMode.supportsSquelch)
RtlSdrSettingsSection(
id: 'squelch',
title: 'Squelch',
icon: Icons.volume_off,
builder: (context) => SquelchPanel(radio: radio),
),
RtlSdrSettingsSection(
id: 'scan',
title: 'Band scan',
icon: Icons.search,
builder: (context) => ScanPanel(radio: radio, scan: scan, presets: presets),
),
// ...PresetsPanel, RecordingPanel, StereoRdsPanel, StatsPanel
],
)
Resize the window (or run on a tablet vs. a phone) to see the same section list switch between a drill-down list of full-screen subscreens and a side-by-side master-detail split — no extra code either way.
A section on its own, in your app's own navigation
Navigator.of(context).push(gainSection.pageRoute());
See example/ for a complete, runnable app: the same UsbState/
UsbChannel permission flow and NativeRtlSdrDriver composition root as
core_rtlsdr's own example, rendered through this package's widgets.
Testing without hardware
Every widget here takes a controller, not a driver — so build/test them
against core_rtlsdr's FakeRtlSdrDriver
(package:core_rtlsdr/testing.dart) exactly like core_rtlsdr's own test
suite does, no dongle/emulator/Android device needed:
final radio = RadioController(FakeRtlSdrDriver());
await tester.pumpWidget(MaterialApp(home: Scaffold(body: GainPanel(radio: radio))));
See this package's own test/ for the full pattern, including
UsbStatusBanner exercised against every UsbState/UsbConnectionStatus
value (UsbState is plain Dart with no FFI, so it's host-testable too).
License
GPLv2-or-later — see LICENSE. driver_rtlsdr links librtlsdr
(GPLv2), which requires any software using it to be distributed under the
GPL; this package (and core_rtlsdr) inherit that requirement down the
dependency chain.
Libraries
- widget_rtlsdr
- Immersive, gqrx-inspired widget library for RTL-SDR on Flutter, built
entirely on top of
core_rtlsdr'sChangeNotifiercontrollers — never ondriver_rtlsdr/dart:ffidirectly, so every widget here also works againstcore_rtlsdr/testing.dart'sFakeRtlSdrDriverwith no Android device or dongle at all (see this package'sexample/).