native_lens 0.2.0
native_lens: ^0.2.0 copied to clipboard
A Flutter Android capability intelligence SDK using Kotlin Platform Channels for native device diagnostics, runtime reports, and compatibility analysis.
example/lib/main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:native_lens/native_lens.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _nativeLensPlugin = NativeLens();
PlatformSummary? _platformSummary;
List<SystemFeature>? _systemFeatures;
List<NativeSensor>? _sensors;
DisplayInfo? _displayInfo;
List<MediaCodecCapability>? _mediaCodecs;
List<CameraCapability>? _cameraCapabilities;
PowerState? _powerState;
NetworkCapability? _networkCapability;
NetworkSpeedSample? _networkSpeedSample;
CompatibilitySummary? _compatibilitySummary;
DeviceOrientationInfo? _deviceOrientation;
StreamSubscription<NetworkCapability>? _networkCapabilitySubscription;
StreamSubscription<NetworkSpeedSample>? _networkSpeedSubscription;
StreamSubscription<DeviceOrientationInfo>? _deviceOrientationSubscription;
bool _isGeneratingReport = false;
bool _isAnalyzingCompatibility = false;
String? _errorMessage;
@override
void initState() {
super.initState();
initPlatformState();
listenToNetworkCapability();
listenToNetworkSpeed();
listenToDeviceOrientation();
}
@override
void dispose() {
_networkCapabilitySubscription?.cancel();
_networkSpeedSubscription?.cancel();
_deviceOrientationSubscription?.cancel();
super.dispose();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
PlatformSummary? platformSummary;
List<SystemFeature>? systemFeatures;
List<NativeSensor>? sensors;
DisplayInfo? displayInfo;
List<MediaCodecCapability>? mediaCodecs;
List<CameraCapability>? cameraCapabilities;
PowerState? powerState;
NetworkCapability? networkCapability;
String? errorMessage;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformSummary = await _nativeLensPlugin.getPlatformSummary();
systemFeatures = await _nativeLensPlugin.getSystemFeatures();
sensors = await _nativeLensPlugin.getSensors();
displayInfo = await _nativeLensPlugin.getDisplayInfo();
mediaCodecs = await _nativeLensPlugin.getMediaCodecs();
cameraCapabilities = await _nativeLensPlugin.getCameraCapabilities();
powerState = await _nativeLensPlugin.getPowerState();
networkCapability = await _nativeLensPlugin.getNetworkCapability();
_deviceOrientation = await _nativeLensPlugin.getDeviceOrientation();
} on PlatformException {
errorMessage = 'Failed to load NativeLens details.';
} on MissingPluginException {
errorMessage = 'NativeLens is not available on this platform.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformSummary = platformSummary;
_systemFeatures = systemFeatures;
_sensors = sensors;
_displayInfo = displayInfo;
_mediaCodecs = mediaCodecs;
_cameraCapabilities = cameraCapabilities;
_powerState = powerState;
_networkCapability = networkCapability;
_errorMessage = errorMessage;
});
}
void listenToNetworkCapability() {
_networkCapabilitySubscription = _nativeLensPlugin.networkCapabilityStream
.listen(
(NetworkCapability capability) {
if (!mounted) return;
setState(() {
_networkCapability = capability;
if (!capability.isConnected) {
_networkSpeedSample = _zeroNetworkSpeedSample();
}
});
},
onError: (Object error) {
// The one-shot network capability call still provides a fallback
// if the stream is unavailable in a test or unsupported platform.
},
);
}
void listenToDeviceOrientation() {
_deviceOrientationSubscription =
_nativeLensPlugin.deviceOrientationStream.listen(
(DeviceOrientationInfo orientation) {
if (!mounted) return;
setState(() {
_deviceOrientation = orientation;
});
},
onError: (Object error) {
// Orientation updates are optional and may not be supported on all devices.
},
);
}
void listenToNetworkSpeed() {
_networkSpeedSubscription = _nativeLensPlugin.networkSpeedStream.listen(
(NetworkSpeedSample sample) {
if (!mounted) return;
setState(() {
_networkSpeedSample = sample;
});
},
onError: (Object error) {
// The example can still show the one-shot capability sections if the
// stream is unavailable in a test or unsupported platform environment.
},
);
}
Future<void> generateFullReport() async {
setState(() {
_isGeneratingReport = true;
_errorMessage = null;
});
String? errorMessage;
try {
await _nativeLensPlugin.generateReport();
} on PlatformException {
errorMessage = 'Failed to generate NativeLens report.';
} on MissingPluginException {
errorMessage = 'NativeLens is not available on this platform.';
}
if (!mounted) return;
setState(() {
_isGeneratingReport = false;
_errorMessage = errorMessage;
});
}
Future<void> analyzeCompatibility() async {
setState(() {
_isAnalyzingCompatibility = true;
_errorMessage = null;
});
CompatibilitySummary? summary;
String? errorMessage;
try {
summary = await _nativeLensPlugin.analyzeCompatibility();
} on PlatformException {
errorMessage = 'Failed to analyze compatibility.';
} on MissingPluginException {
errorMessage = 'NativeLens is not available on this platform.';
}
if (!mounted) return;
setState(() {
_compatibilitySummary = summary;
_isAnalyzingCompatibility = false;
_errorMessage = errorMessage;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
home: Scaffold(
appBar: AppBar(title: const Text('NativeLens Example')),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: _buildBody(),
),
),
),
);
}
Widget _buildBody() {
final PlatformSummary? summary = _platformSummary;
final List<SystemFeature>? features = _systemFeatures;
final List<NativeSensor>? sensors = _sensors;
final DisplayInfo? displayInfo = _displayInfo;
final List<MediaCodecCapability>? mediaCodecs = _mediaCodecs;
final List<CameraCapability>? cameraCapabilities = _cameraCapabilities;
final PowerState? powerState = _powerState;
final NetworkCapability? networkCapability = _networkCapability;
final DeviceOrientationInfo? deviceOrientation = _deviceOrientation;
final NetworkSpeedSample? networkSpeedSample = _networkSpeedSample;
final CompatibilitySummary? compatibilitySummary = _compatibilitySummary;
if (_errorMessage != null) {
return Center(
child: Text(
_errorMessage!,
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
),
);
}
if (summary == null ||
features == null ||
sensors == null ||
displayInfo == null ||
mediaCodecs == null ||
cameraCapabilities == null ||
powerState == null ||
networkCapability == null) {
return const Center(child: CircularProgressIndicator());
}
final bool isNetworkConnected = networkCapability.isConnected;
final NetworkSpeedSample? visibleNetworkSpeedSample = isNetworkConnected
? networkSpeedSample
: _zeroNetworkSpeedSample();
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Card(
elevation: 2,
margin: const EdgeInsets.only(bottom: 16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('NativeLens Dashboard',
style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 6),
Text('${summary.manufacturer} ${summary.model}',
style: Theme.of(context).textTheme.bodyMedium),
],
),
),
ElevatedButton(
onPressed: _isGeneratingReport ? null : generateFullReport,
child: Text(_isGeneratingReport
? 'Generating...'
: 'Generate Report'),
),
const SizedBox(width: 8),
FilledButton.tonal(
onPressed: _isAnalyzingCompatibility ? null : analyzeCompatibility,
child: Text(_isAnalyzingCompatibility
? 'Analyzing...'
: 'Analyze'),
),
],
),
),
),
Wrap(
spacing: 12,
runSpacing: 12,
children: <Widget>[
_metricCard(
context,
title: 'Compatibility',
value: compatibilitySummary == null
? 'Waiting'
: '${compatibilitySummary.overallScore}',
progress: compatibilitySummary == null
? null
: (compatibilitySummary.overallScore / 100.0),
subtitle: compatibilitySummary?.overallLevel,
),
_metricCard(
context,
title: 'Sensors',
value: sensors.length.toString(),
),
_metricCard(
context,
title: 'Cameras',
value: cameraCapabilities.length.toString(),
),
_metricCard(
context,
title: 'Codecs',
value: mediaCodecs.length.toString(),
),
_metricCard(
context,
title: 'Battery',
value: '${powerState.batteryLevel}%',
progress: powerState.batteryLevel / 100.0,
),
_metricCard(
context,
title: 'Network',
value: isNetworkConnected ? networkCapability.transportType : 'Disconnected',
subtitle: isNetworkConnected ? 'Online' : 'Offline',
),
],
),
const SizedBox(height: 16),
_sectionCard(
title: 'Orientation',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_SummaryRow(
label: 'Orientation',
value: deviceOrientation?.orientationName ?? 'Unknown',
),
_SummaryRow(
label: 'Degrees',
value: deviceOrientation?.rotationDegrees.toString() ?? 'Unknown',
),
_SummaryRow(
label: 'Source',
value: deviceOrientation?.source ?? 'Unknown',
),
],
),
),
_sectionCard(
title: 'Network & App Traffic',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_SummaryRow(
label: 'Status',
value: isNetworkConnected ? 'Connected' : 'Disconnected',
),
_SummaryRow(
label: 'Download',
value: _formatSpeed(visibleNetworkSpeedSample?.rxBytesPerSecond),
),
_SummaryRow(
label: 'Upload',
value: _formatSpeed(visibleNetworkSpeedSample?.txBytesPerSecond),
),
],
),
),
const SizedBox(height: 24),
_sectionCard(
title: 'Platform Summary',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_SummaryRow(label: 'Manufacturer', value: summary.manufacturer),
_SummaryRow(label: 'Model', value: summary.model),
_SummaryRow(label: 'Android Release', value: summary.androidRelease),
],
),
),
const SizedBox(height: 16),
_sectionCard(
title: 'Power',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Battery', style: const TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 6),
LinearProgressIndicator(value: powerState.batteryLevel / 100.0),
],
),
),
const SizedBox(width: 12),
Text('${powerState.batteryLevel}%'),
],
),
),
],
),
),
const SizedBox(height: 24),
],
),
);
}
NetworkSpeedSample _zeroNetworkSpeedSample() {
return NetworkSpeedSample(
timestampMillis: DateTime.now().millisecondsSinceEpoch,
rxBytesPerSecond: 0,
txBytesPerSecond: 0,
rxKbps: 0,
txKbps: 0,
totalRxBytes: 0,
totalTxBytes: 0,
isSupported: true,
);
}
String _formatSpeed(int? bytesPerSecond) {
if (bytesPerSecond == null) {
return 'Waiting';
}
final double kiloBytesPerSecond = bytesPerSecond / 1024;
return '${kiloBytesPerSecond.toStringAsFixed(2)} KB/s';
}
}
Widget _metricCard(BuildContext context,
{required String title,
required String value,
double? progress,
String? subtitle}) {
return SizedBox(
width: 160,
child: Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title, style: const TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 8),
Text(value, style: Theme.of(context).textTheme.headlineSmall),
if (subtitle != null) ...[
const SizedBox(height: 8),
Text(subtitle, style: Theme.of(context).textTheme.bodySmall),
],
if (progress != null) ...[
const SizedBox(height: 8),
LinearProgressIndicator(value: progress),
],
],
),
),
),
);
}
Widget _sectionCard({required String title, required Widget child}) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title, style: const TextStyle(fontWeight: FontWeight.w700)),
const SizedBox(height: 8),
child,
],
),
),
);
}
class _SummaryRow extends StatelessWidget {
const _SummaryRow({required this.label, required this.value});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
width: 130,
child: Text(
label,
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
Expanded(child: Text(value)),
],
),
);
}
}
// Removed unused detailed rows and text sections after dashboard refactor.