Performant cross-platform development using Flutter
A view on Flutter's motion, rendering, performance and web interop.
Hej, jeg er Den med fletning | The one with the braid
- Dart developer in healthcare
- [matrix]
- Interested in FreeBSD, Linux, RISC architectures
- Started Flutter back in 2018
Hvad er Flutter?
- Dart platform
- Flutter engine
- Foundation library
- Design-specific widgets
- Dev tools
Why Dart & Flutter
- natively compiles -> performant
- single code base
- adopts to platform
- development experience
	- getting rid of legacy patterns
 
History of Dart & Flutter
- Dart - initially released 2011
- Aimed to replace JS with a type safe language
	- Dart JS interop still part of Dart core eight years later
- UX nightmare
- semi-implemented API
- everything type dynamic -> we got JS with Dart syntax uwu
- Absolutely failed
 
- Flutter announced in 2015
- More and more platforms added
Flutter heads-up
Flutter at a glace
- Mostly UI framework
- UI elements called Widget
- Widgets nested into tree
- Shipping high-level Widgets and basic layout Widgets
nuqneH tera'gngpu' - the Klingon hello world
void main() { runApp(const Text('Heghlu\'meH QaQ jajvam.')); }
StatelessWidget
- immutable rendering object
- completely built around given surrounding parameters
	- constraining layout
- constructor parameters
 
- state inherited from next stateful widget
StatefulWidget
- mutable, modifiable rendering object
- rendered based on own and surrounding parameters
	- state change call
- layout change
		- user-defined: constructor changes
 
 
- state stored in a mutable companion class
- capable of "self modification"
Beautiful and performant code
- use stateless widgets wherever possible
- check didUpdateWidget in order to minimize builds
- address state from properly scoped state container / controller
- use Keys to avoid unnecessary re-builds
Useful patterns
go_router
- page routing
- navigation
- hooks into MaterialApp.router -> navigator provider
- extensible
	- transitions
- page builder
 
final GoRouter _router = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      path: '/',
      builder: (BuildContext context, GoRouterState state) {
        return const HomeScreen();
      },
      routes: <RouteBase>[
        GoRoute(
          path: 'details',
          builder: (BuildContext context, GoRouterState state) {
            return const DetailsScreen();
          },
        ),
      ],
    ),
  ],
);
return MaterialApp.router(
  routerConfig: _router,
);Storage
- JSON is not a storage.
Storage solutions for Dart & Flutter
- SQLite - the boring one
- Hive - the native one
- Isar - the rusty one
- Shared Preferences - JSON is not a storage.
Hive - native Dart object store
- type adapters
- listenable
- different backends
	- binary writer
- Indexed DB
- web worker
 
- encryption
import 'package:hive/hive.dart';
part 'person.g.dart';
@HiveType(typeId: 1)
class Person {
  @HiveField(0)
  String name;
  @HiveField(1)
  int age;
  @HiveField(2)
  List<Person> friends;
}How does rendering work under the hood?
Graphics from: https://docs.google.com/presentation/d/1cw7A4HbvM_Abv320rVgPVGiUP2msVs7tfGbkgdrTy0I/edit#slide=id.gbb3c3233b_0_162




Optimizing performance
Key feature : Keys
- Communicating StatefulWidget to preserve State
- Prevent rebuilding on StreamBuilder
- Useful for animations (see animations section)
Caution with StreamBuilder
- Never run a ListView.builder against a StreamBuilder
- Use either Keys or AnimatedList / AnimatedSliverListDelegate
Animations and backdrops
- Avoid Video and GIF/APNG (yes, finally supported !)
- Shader performance awful on web
- Use package:animations
- Keys again
Web only : initial load
- Deferred libraries (e.g. minimal splash screen)
- Use browser-optimized CanvasKit
- Defer in small pieces
- Outsource to web workers
Performant animations
Implicit Animation
- Value
- Duration
- Curve
Used for simple animations, low code effort
Curves

      Center(
        child: AnimatedContainer(
          width: selected ? 200.0 : 100.0,
          height: selected ? 100.0 : 200.0,
          color: selected ? Colors.red : Colors.blue,
          alignment:
              selected ? Alignment.center : AlignmentDirectional.topCenter,
          duration: Duration(seconds: 2),
          curve: Curves.fastOutSlowIn,
          child: FlutterLogo(size: 75),
        ),
      ),        Center(
          child: TweenAnimationBuilder<double>(
            tween: Tween<double>(begin: 0, end: 2 * math.pi),
            duration: Duration(seconds: 2),
            builder: (BuildContext context, double angle, Widget child) {
              return Transform.rotate(
                angle: angle,
                child: Image.asset('assets/Earth.png'),
              );
            },
          ),
        ),Explicit Animation
- Ticker
- Build for every single frame
Used for complex animations, high performance impact
Recepie
- SingleTickerProviderStateMixin
- AnimationController
- addListener
- setSate
class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  
  AnimationController? _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
    _controller?.addListener(_update);
  }
  
  void _update() {
    setState(() {
      // TODO
    });
  }
  
  @override
  Widget build(BuildContext context) {
  	print('I\'m re-built very often.');
    return Container();
  }
}class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  
  AnimationController? _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this, 
      duration: const Duration(seconds: 1),
    );
    _controller?.addListener(_update);
    _controller?.forward();
  }
  
  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }
  
  void _update() {
    setState(() {
      // TODO
    });
  }
  
  @override
  Widget build(BuildContext context) {
    prin('Still getting re-built...')
    return Container();
  }
}AnimatedBuilder
- AnimationController
- Builder
Used for custom animations, easy to implement
    _controller = AnimationController(
      duration: const Duration(seconds: 10),
      vsync: this,
    )..repeat();
    
    // [...]
	return AnimatedBuilder(
      animation: _controller,
      child: Container(
        width: 200.0,
        height: 200.0,
        color: Colors.green,
        child: const Center(
          child: Text('Whee!'),
        ),
      ),
      builder: (BuildContext context, Widget child) {
        return Transform.rotate(
          angle: _controller.value * 2.0 * math.pi,
          child: child,
        );
      },
    );Hero animations
- Hero class
- mostly known from FAB
Used for simple animations, easy to implement

Hero transitions
Container(
	child: Hero(
    	tag: "SomeObject",
    	child: whatever    
    )
)
// [...]
//
// On any other page
//
Card(
	child: Hero(
    	tag: "SomeObject",
    	child: whatever    
    )
)`animations` package
- Prebuild animations
- Common cases
Used for UI transitions, easy to implement

Container transform
    return OpenContainer<bool>(
      transitionType: transitionType,
      openBuilder: (BuildContext context, VoidCallback _) {
        return const _DetailsPage();
      },
      onClosed: onClosed,
      tappable: false,
      closedBuilder: closedBuilder,
    );
Axis transitions
Types
- SharedAxisPageTransitionsBuilder
- SharedAxisTransition
Flutter - the bad, the ugly
- Engine build - not possible without BLOBs
- Your free Google Fonts connection by CanvasKit
Introduction into Dart JS interop
Cursed part, powered by an alpaca horse
A journey into deep insights of Dart you actually don't want to know about.
- Interops any Dart into JS
- null safety LOL
- syntax lol
History of Dart
- Replace JS with a type safe language -> failed
- Dart JS interop still part of Dart core eight years later
- UX nightmare
- semi-implemented API
- everything type dynamic -> we got JS with Dart syntax uwu
Use cases for Dart JS interop
- Expose APIs to embedding website
- Accessing native JS APIs in Flutter / Dart apps
- Writing pure Dart web apps
- Flutter's web engine
JS & util JS
- exposing links between Dart and JavaScript runtime
- Examples include:
	- jsify
- allowInterop
- constructor calls
- promiseToFuture
- access type unsafe JS objects in an array API
 
final completer = Completer<MyType>(); final promiseProto = getProperty(window, 'Promise'); final promise = callConstructor( promiseProto, jsify([ allowInterop((resolve, reject) { completer.future.then((value) => resolve(value)); }) ]), ); completer.complete(MyFuture()); return promise;
Flutter testing
Kinds of tests
- Widget tests
- Golden tests
- Unit tests
- Integration tests
- Plugin tests
- Code coverage tests
Testing in common
- Dart has a pretty advanced built-in test framework
- Clean environment
	- Initial state mockable
- Always reproducible device state
 
- Tests run in dedicated isolate from application
- Tests run in Dart VM / browser
	- Flutter plugins require native tests
- No canonical way for Flutter native device tests
- See Test Drivers later
 
- Mock implementations for APIs
Unit, widget and integration tests in common
- "Pumping" widget or app
- Mocking required APIs
	- Alternatively, connect to test services for sure
 
- simple find-expect syntax
Golden tests
- Ensures UI components' appearance
- Matches against abstracted bitmap
- Uses abstracted platform specific UI
- Easy to use interface
- Can be generated using a single command
Testing the web
- Browsers have no canonical debug protocol
- Web drivers it is
- Flutter takes test driver running web driver as argument
Useful links
- https://pub.dev/packages/mockito
- https://github.com/flutter/flutter/wiki/Running-Flutter-Driver-tests-with-Web#examples-from-flutter-project
	- https://github.com/flutter/web_installers/tree/master/packages/web_drivers/lib
 
- https://pub.dev/packages/coverage
- https://github.com/flutter/flutter/wiki/Plugin-Tests
Questions left?
Flutter
By The one with the braid
Flutter
- 936
 
   
   
  