import 'package:flutter/material.dart'; import 'package:vflutter_ffi/vflutter_ffi.dart' as v; void main() => runApp(const ExampleApp()); class ExampleApp extends StatelessWidget { const ExampleApp({super.key}); @override Widget build(BuildContext context) => MaterialApp( title: 'vflutter_ffi', theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true), home: const HomePage(), ); } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { final _controller = TextEditingController(text: 'Flutter'); // Synchronous results are cheap enough to compute in build(); the async one // is not, so it is held here and refreshed on demand. String _async = '(not run)'; String _stress = '(not run)'; @override void dispose() { _controller.dispose(); super.dispose(); } Future _runAsync() async { final r = await v.greetAsync(_controller.text); setState(() => _async = r); } /// 20k round trips on the UI isolate. Fast enough not to jank, and proves /// the V side frees its temporaries under load. void _runStress() { final sw = Stopwatch()..start(); for (var i = 0; i < 20000; i++) { v.greet('stress$i'); } sw.stop(); setState(() => _stress = '20k calls in ${sw.elapsedMilliseconds} ms'); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('V → Flutter over dart:ffi')), body: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 520), child: ListView( padding: const EdgeInsets.all(24), shrinkWrap: true, children: [ _Row(label: 'v.add(20, 22)', value: '${v.add(20, 42 - 20)}'), const Divider(height: 32), TextField( controller: _controller, decoration: const InputDecoration( labelText: 'name', border: OutlineInputBorder(), ), onChanged: (_) => setState(() {}), ), const SizedBox(height: 16), _Row(label: 'v.greet(name)', value: v.greet(_controller.text)), const Divider(height: 32), _Row(label: 'v.greetAsync(name)', value: _async), const SizedBox(height: 8), FilledButton.tonal( onPressed: _runAsync, child: const Text('run on background isolate'), ), const Divider(height: 32), _Row(label: 'ownership stress', value: _stress), const SizedBox(height: 8), FilledButton.tonal( onPressed: _runStress, child: const Text('run 20k round trips'), ), ], ), ), ), ); } } class _Row extends StatelessWidget { const _Row({required this.label, required this.value}); final String label; final String value; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: theme.textTheme.labelMedium), const SizedBox(height: 4), SelectableText( value, style: theme.textTheme.titleMedium ?.copyWith(fontFamily: 'monospace', color: theme.colorScheme.primary), ), ], ); } }