cratestack_cbor 0.12.0 copy "cratestack_cbor: ^0.12.0" to clipboard
cratestack_cbor: ^0.12.0 copied to clipboard

Native CBOR codec for CrateStack Dart/Flutter: flutter_rust_bridge (native, vendored) and wasm-bindgen (web, vendored), mirroring @cratestack/cbor conditional exports for JS.

example/lib/main.dart

// Minimal example app exercising `package:cratestack_cbor` end to end
// (cratestack#563's "Flutter app integration" slice). This is deliberately
// the whole point of this directory: prove the package works inside a REAL
// `flutter build linux` / `flutter build web` — not `dart test` — by
// actually calling `createCborCodec()` and round-tripping a value through
// it at app startup.
//
// The result is both shown on screen AND printed to stdout with a unique,
// greppable marker. The stdout print is load-bearing, not decorative: a
// built desktop binary or a served release web bundle has no interactive
// console to casually read, so headless verification (`just
// cbor-example-verify-linux`, and the equivalent headless-Chrome check for
// web) greps process/console output for this marker rather than trying to
// screenshot a GUI. See this package's README for the verification
// transcripts, including the "corrupt the vendored artifact -> the app
// fails, loudly" proof.
import 'dart:async';

import 'package:cratestack_cbor/cratestack_cbor.dart';
import 'package:flutter/material.dart';

/// Prefix every marker line starts with, so verification can `grep` for a
/// single literal string regardless of OK/FAILED outcome.
const resultMarkerPrefix = 'CRATESTACK_CBOR_EXAMPLE_RESULT:';

// THE ROUND TRIP STARTS HERE, NOT IN A WIDGET.
//
// It used to hang off `late final _future = runRoundTrip()` on the page's
// State, whose only read was inside `build()`. `late final` initializes
// lazily, so the marker `print` that every headless verification greps for
// was a side effect of building a widget — the app had to construct its
// tree successfully before the assertion everything downstream depends on
// could even be attempted.
//
// HOW MUCH LATER, precisely, because an earlier version of this comment got
// it wrong and claimed the round trip waited for a rendered frame. It did
// not. `runApp` calls `scheduleAttachRootWidget`, which is a bare
// `Timer.run(() => attachRootWidget(...))`, and `attachToBuildOwner`
// inflates the element tree synchronously inside `buildScope` — so `build()`
// ran one event-loop turn after `runApp`, with no frame and no platform
// scene required. See `packages/flutter/lib/src/widgets/binding.dart`.
//
// So this change buys one thing, not two: the marker no longer depends on
// the widget tree building at all. It does NOT rescue a platform that never
// starts the Dart isolate — on iOS the engine is launched from
// `FlutterViewController.viewDidLoad`, upstream of any Dart code, in both
// shapes. cratestack#704 remains open and unexplained; do not read this
// comment as its fix.
void main() {
  // The round trip runs before `runApp`, so the binding is not yet
  // initialized as a side effect of it. Neither backend needs a platform
  // channel today (native resolves a path via `dart:io`/`dart:isolate`,
  // web injects a script tag), but doing pre-`runApp` async work without
  // this is the documented way to get bitten the moment one of them does.
  WidgetsFlutterBinding.ensureInitialized();
  final roundTrip = runRoundTrip();
  // Attach a listener NOW. `runRoundTrip` rethrows after printing the
  // FAILED marker, and between here and the first `build()` the
  // `FutureBuilder` below is not listening yet — an error landing in that
  // window would be reported as an unhandled async error on top of the
  // marker we actually want read. The FutureBuilder still surfaces it.
  unawaited(
      roundTrip.then<void>((_) {}, onError: (Object _, StackTrace __) {}));
  runApp(CratestackCborExampleApp(roundTrip: roundTrip));
}

class CratestackCborExampleApp extends StatelessWidget {
  const CratestackCborExampleApp({super.key, required this.roundTrip});

  /// The round trip started by [main], already in flight. Passed in rather
  /// than started on demand — see [main]'s comment for why that matters.
  final Future<RoundTripResult> roundTrip;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'cratestack_cbor example',
      theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple)),
      home: RoundTripPage(roundTrip: roundTrip),
    );
  }
}

class RoundTripPage extends StatelessWidget {
  const RoundTripPage({super.key, required this.roundTrip});

  /// See [CratestackCborExampleApp.roundTrip].
  final Future<RoundTripResult> roundTrip;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('cratestack_cbor round-trip')),
      body: Center(
        child: FutureBuilder<RoundTripResult>(
          future: roundTrip,
          builder: (context, snapshot) {
            if (snapshot.connectionState != ConnectionState.done) {
              return const CircularProgressIndicator();
            }
            if (snapshot.hasError) {
              return Padding(
                padding: const EdgeInsets.all(24),
                child: Text(
                  key: const Key('cratestack_cbor_result'),
                  'ROUND-TRIP FAILED\n${snapshot.error}',
                  textAlign: TextAlign.center,
                  style: const TextStyle(color: Colors.red),
                ),
              );
            }
            final result = snapshot.data!;
            return Padding(
              padding: const EdgeInsets.all(24),
              child: Text(
                key: const Key('cratestack_cbor_result'),
                'ROUND-TRIP OK\n'
                'backend content-type: ${result.contentType}\n'
                'input:    ${result.input}\n'
                'cbor hex: ${result.hex}\n'
                'output:   ${result.output}',
                textAlign: TextAlign.center,
              ),
            );
          },
        ),
      ),
    );
  }
}

class RoundTripResult {
  const RoundTripResult({
    required this.contentType,
    required this.input,
    required this.hex,
    required this.output,
  });

  final String contentType;
  final String input;
  final String hex;
  final String output;
}

/// Calls the real, public `createCborCodec()`/`CratestackCborCodec` API
/// (not a `src/`-internal shortcut) and round-trips a fixed JSON value
/// through it, matching one of the package's own shared cross-binding
/// fixtures (`../test/shared_fixtures.dart`) so the printed hex can be
/// checked against a known-good value, not just "didn't throw".
Future<RoundTripResult> runRoundTrip() async {
  const input = '{"cratestack":["cool","stack"],"n":42,"ok":true}';
  try {
    final codec = await createCborCodec();
    final bytes = codec.encodeJson(input);
    final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
    final output = codec.decodeJson(bytes);
    final result = RoundTripResult(
      contentType: codec.contentType,
      input: input,
      hex: hex,
      output: output,
    );
    // ignore: avoid_print
    print('$resultMarkerPrefix OK $hex');
    return result;
  } catch (error, stackTrace) {
    // ignore: avoid_print
    print('$resultMarkerPrefix FAILED $error');
    Error.throwWithStackTrace(error, stackTrace);
  }
}
0
likes
150
points
2.22k
downloads

Documentation

API reference

Publisher

verified publishercratestack.dev

Weekly Downloads

Native CBOR codec for CrateStack Dart/Flutter: flutter_rust_bridge (native, vendored) and wasm-bindgen (web, vendored), mirroring @cratestack/cbor conditional exports for JS.

Topics

#cbor #codec #rpc

License

MIT (license)

Dependencies

flutter_rust_bridge, web

More

Packages that depend on cratestack_cbor

Packages that implement cratestack_cbor