flutter_android_bridge 0.3.0
flutter_android_bridge: ^0.3.0 copied to clipboard
Flutter Android Bridge is a Flutter package that provides a bridge to interact with Android's native functionalities. This package allows Flutter applications to communicate with Android's package man [...]
import 'dart:io' show Platform;
import 'package:ffmpeg_kit_extended_flutter/ffmpeg_kit_extended_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_android_bridge/library.dart';
import 'android_mirror_view.dart';
void main() async {
await FFmpegKitExtended.initialize();
runApp(const MirrorApp());
}
class MirrorApp extends StatelessWidget {
const MirrorApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Android Screen Mirror',
theme: ThemeData.dark(useMaterial3: true),
home: const MirrorPage(),
);
}
}
class MirrorPage extends StatefulWidget {
const MirrorPage({super.key});
@override
State<MirrorPage> createState() => _MirrorPageState();
}
class _MirrorPageState extends State<MirrorPage> {
final _adbPathController = TextEditingController(
text:
'${Platform.environment['HOME']}/Library/Android/sdk/platform-tools/adb',
);
final _addressController = TextEditingController(text: '192.168.1.112:5555');
final _mirrorKey = GlobalKey<AndroidMirrorViewState>();
FlutterAndroidClient? _client;
bool _connecting = false;
bool _running = false;
bool _recordEnabled = false;
String? _status;
String get _recordPath =>
'${Platform.environment['HOME']}/android_mirror_capture.h264';
Future<void> _connect() async {
setState(() {
_connecting = true;
_status = null;
});
try {
final adb = FlutterAndroidBridge(_adbPathController.text.trim());
final client = adb.newClient(_addressController.text.trim());
final connected = await client.connect();
if (!connected) {
throw StateError('Could not connect to ${_addressController.text}');
}
setState(() {
_client = client;
_status = 'Connected';
});
} catch (e) {
setState(() => _status = 'Connect failed: $e');
} finally {
if (mounted) setState(() => _connecting = false);
}
}
Future<void> _start() async {
final recordFile = _recordEnabled ? _recordPath : null;
await _mirrorKey.currentState?.start(recordFile: recordFile);
if (mounted) {
setState(() {
_running = true;
_status = recordFile != null ? 'Recording to $recordFile' : 'Mirroring';
});
}
}
Future<void> _stop() async {
await _mirrorKey.currentState?.stop();
if (mounted) {
setState(() {
_running = false;
_status = _recordEnabled
? 'Saved recording to $_recordPath'
: 'Stopped';
});
}
}
@override
void dispose() {
_adbPathController.dispose();
_addressController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final client = _client;
return Scaffold(
appBar: AppBar(title: const Text('Android Screen Mirror')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
controller: _adbPathController,
decoration: const InputDecoration(
labelText: 'adb path',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: TextField(
controller: _addressController,
decoration: const InputDecoration(
labelText: 'device address (ip:port or serial)',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 12),
FilledButton(
onPressed: _connecting ? null : _connect,
child: _connecting
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Connect'),
),
],
),
const SizedBox(height: 12),
Row(
children: [
FilledButton.icon(
onPressed: client != null && !_running ? _start : null,
icon: const Icon(Icons.play_arrow),
label: const Text('Start mirror'),
),
const SizedBox(width: 12),
OutlinedButton.icon(
onPressed: _running ? _stop : null,
icon: const Icon(Icons.stop),
label: const Text('Stop'),
),
const SizedBox(width: 12),
// Recording is chosen up front (before Start), so the file
// captures the initial keyframe and stays decodable.
Tooltip(
message: 'Record the session to $_recordPath',
child: FilterChip(
avatar: Icon(
_recordEnabled
? Icons.fiber_manual_record
: Icons.radio_button_unchecked,
color: _recordEnabled ? Colors.red : null,
),
label: const Text('Record'),
selected: _recordEnabled,
// Locked while running: can't toggle mid-stream.
onSelected: _running
? null
: (v) => setState(() => _recordEnabled = v),
),
),
const SizedBox(width: 16),
if (_status != null)
Expanded(
child: Text(
_status!,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.white70),
),
),
],
),
const SizedBox(height: 16),
Expanded(
child: Container(
color: Colors.black,
alignment: Alignment.center,
child: client == null
? const Text('Connect to a device to begin')
: AndroidMirrorView(
key: _mirrorKey,
client: client,
debug: true,
),
),
),
],
),
),
);
}
}