The "100% Dart" Myth in Production Mobile Engineering
One of the most common architectural mistakes teams make when adopting Flutter is assuming that every feature should be written purely in Dart. While Flutter's rendering engine and pub.dev ecosystem handle 90% of standard CRUD and UI workflows effortlessly, forcing high-frequency hardware interactions—such as raw stylus ink prediction, custom WebRTC media tracks, or USB serial communication—through pure Dart abstractions often introduces unacceptable latency.
Where Pure Dart Hits a Bottleneck
In a recent interactive whiteboard project deployed on large Android touch panels, relying purely on standard Flutter gesture arenas resulted in a noticeable 70.7ms stylus stroke lag. Why? Because touch events had to travel from the Android OS input pipeline, across the Flutter engine boundary, through Dart's gesture disambiguation arena, and back into the render tree before a single pixel appeared on screen.
The Hybrid Solution: Native Kotlin + Flutter UI
Instead of fighting the framework, the cleanest architectural decision is knowing when to drop down into native Kotlin via MethodChannel and EventChannel:
Let Native Handle Raw Hardware Interrupts: By capturing motion events and rendering predictive hardware-accelerated overlays directly on the Android SurfaceView layer in Kotlin, we reduced stroke latency from 70.7ms down to 8.7ms (an 88% reduction).
Use Flutter for State Orchestration and Cross-Platform Sync: Once a stroke is completed, the coordinate array is passed over a binary messenger channel to Flutter, which manages the Undo/Redo stack, BLoC state, and WebSocket broadcasting to 300+ participants.
Example: Clean Binary Messenger Bridge in Kotlin
class StylusLatencyPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "com.inboard/stylus_accel")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"enablePredictiveOverlay" -> {
val strokeWidth = call.argument<Double>("width") ?: 3.0
// Trigger native Android front-buffered rendering
result.success(true)
}
else -> result.notImplemented()
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
Final Verdict
Choosing Flutter shouldn't mean abandoning native Android engineering. The strongest mobile architectures treat Flutter as a supercharged UI and business-logic layer while unapologetically leveraging native Kotlin whenever sub-10ms hardware performance is on the line.
Top comments (0)