refactor: depend on an Iperf3Runner interface instead of the service

The presentation layer imported Iperf3Service directly, so the UI was welded
to the MethodChannel transport: the view model named it, and the screen
constructed one. Dependency direction was already inward-only, but the UI
depended on an implementation rather than a contract.

Iperf3Runner states that contract in the domain layer in five members, in pure
Dart with no Flutter import, and Iperf3Service implements it. The view model
and screen now take an Iperf3Runner; main.dart is the only file that names a
concrete one, which makes it the composition root.

Two consequences worth having. The UI can be driven against an in-memory
runner with no MethodChannel, native library or device involved. And a test
double implements five stable members rather than tracking every public member
the transport happens to expose — the previous double, forced to implement the
concrete class, broke twice while this branch's work was going on.

Also drops flutter/foundation from the data layer: debugPrint gives way to
dart:developer, leaving flutter/services (where MethodChannel lives) as that
layer's only Flutter dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 19:56:03 +09:00
parent defd64a073
commit 314a3ea059
5 changed files with 85 additions and 30 deletions
+11 -6
View File
@@ -1,17 +1,22 @@
import 'package:flutter/material.dart';
import 'package:iperf3gui/data/services/iperf3_service.dart';
import 'package:iperf3gui/domain/services/iperf3_runner.dart';
import 'package:iperf3gui/ui/features/test_run/test_run_screen.dart';
/// Application entry point.
/// Application entry point, and the composition root.
///
/// iperf3 is now compiled as a native shared library (libiperf3jni.so)
/// and accessed via JNI — no binary extraction or chmod needed.
/// This is the only place that names a concrete [Iperf3Runner]. Everything
/// above the data layer depends on the interface, so swapping the transport —
/// or running the UI against a fake — touches nothing but this file.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const Iperf3GuiApp());
runApp(Iperf3GuiApp(runner: Iperf3Service()));
}
class Iperf3GuiApp extends StatelessWidget {
const Iperf3GuiApp({super.key});
const Iperf3GuiApp({required this.runner, super.key});
final Iperf3Runner runner;
@override
Widget build(BuildContext context) {
@@ -21,7 +26,7 @@ class Iperf3GuiApp extends StatelessWidget {
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: const TestRunScreen(),
home: TestRunScreen(runner: runner),
);
}
}