lynx_view 1.7.0 copy "lynx_view: ^1.7.0" to clipboard
lynx_view: ^1.7.0 copied to clipboard

Wraps LynxJS (https://lynxjs.org) native LynxView as a Flutter PlatformView, so existing Flutter apps can embed Lynx templates.

lynx_view #

Wraps LynxJS's native LynxView as a Flutter PlatformView, so an existing Flutter app can embed Lynx templates.

πŸ“– ν•œκ΅­μ–΄ λ¬Έμ„œ: docs/ko

Features #

  • Renders a remote Lynx bundle (templateUrl) inside a Flutter widget, Android and iOS.
  • Runtime reload() β€” the hook an OTA/CodePush-style client uses to swap in a new bundle without recreating the widget.
  • Built-in FlutterBridge channel β€” JS <-> Dart messaging (addJavaScriptChannel/sendEvent) with no native code required.
  • Custom fonts from the host app's own assets (fonts:) β€” registered before the first paint, with nothing fetched over the network.
  • Escape hatch for custom native modules (LynxViewPlugin.registerNativeModule) when you need something typed/native.
  • Memory-aware: the platform's memory-pressure signal is forwarded to Lynx automatically, and LynxMemory.usage() reports what each live view is actually holding.

This package does not provide OTA/CodePush server infrastructure, bundle caching, or Web/Desktop/HarmonyOS support β€” see the techspec for scope boundaries.

Getting started #

1. Add the dependency #

dependencies:
  lynx_view: ^1.3.0

2. Android setup #

No manual Gradle changes needed β€” the plugin brings its own Lynx SDK dependency. minSdkVersion is enforced at 21 by the plugin (Lynx SDK 4.0.0's own floor).

If you need a custom native module, register it once at app startup:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        LynxViewPlugin.registerNativeModule("YourCustomModule", YourCustomModule::class.java)
    }
}

3. iOS setup #

No manual Podfile changes needed. In Xcode, set User Script Sandboxing to NO (a Lynx SDK build requirement).

If you need a custom native module, register it in AppDelegate:

@main
class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    LynxViewPlugin.registerNativeModule("YourCustomModule", moduleClass: YourCustomModule.self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

CocoaPods only, no Swift Package Manager yet. lynx_view_ios depends on the Lynx/PrimJS pods, and Lynx itself isn't distributed via SPM upstream (lynx-family/lynx#162 is an open, unanswered feature request). Since a Flutter SPM plugin can't mix in a CocoaPods-only native dependency, this package can't support SPM until Lynx does.

Usage #

Render a bundle #

class _LynxScreenState extends State<LynxScreen> {
  late final LynxViewController _controller = LynxViewController(
    templateUrl: 'https://your-bucket.s3.amazonaws.com/bundles/home.lynx.bundle',
    initData: const {'userId': '123'},
    onLoadSuccess: () => debugPrint('Lynx bundle loaded'),
    onLoadError: (e) => debugPrint('Lynx load failed: ${e.code} ${e.message}'),
  );

  @override
  Widget build(BuildContext context) => LynxView(controller: _controller);

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Fonts #

Lynx does not load fonts by itself β€” a template naming a font-family nobody registered draws in the system font, silently. Hand it the files the host app already ships:

LynxViewController(
  templateUrl: '...',
  fonts: const [
    LynxFontAsset(family: 'Pretendard-Regular', assetPath: 'assets/fonts/Pretendard-Regular.otf'),
    LynxFontAsset(family: 'Pretendard-Bold',    assetPath: 'assets/fonts/Pretendard-Bold.otf'),
  ],
);
.title { font-family: Pretendard-Bold, sans-serif; }
.body  { font-family: Pretendard-Regular, sans-serif; }

assetPath is a Flutter asset key exactly as pubspec.yaml spells it. Files declared under fonts: are in the asset bundle too, so a typeface the Flutter side already draws with needs no second copy.

One family is one weight. Lynx resolves font-family by name alone β€” it cannot pick between weights registered under one name, and on Android every CSS weight from 500 up collapses into a single "bold" slot. Give each weight its own family and select with font-family, not font-weight.

Registration happens natively while the view is created, so the first paint already has the font. It is per-process and idempotent; a font that will not load is skipped with a log and the screen keeps rendering in the system font.

JS <-> Dart messaging without native code #

_controller.addJavaScriptChannel(
  'MyChannel',
  onMessageReceived: (LynxMessage message) => debugPrint('from JS: ${message.data}'),
);
_controller.sendEvent('MyChannel', {'greeting': 'hello from Flutter'});
// bundle JS
NativeModules.FlutterBridge.postMessage('MyChannel', JSON.stringify({ hello: 'from lynx' }));
lynx.getJSModule('GlobalEventEmitter').addListener('MyChannel', (data) => console.log('from Flutter:', data));

Reloading (OTA/CodePush integration point) #

This package doesn't ship OTA infrastructure β€” it just gives your own OTA/CodePush client something to call:

Future<void> onNewBundleAvailable(String newTemplateUrl) async {
  await _controller.reload(newTemplateUrl);
}

If a reload fails, the previously rendered content stays on screen and onLoadError fires β€” no crash, no automatic rollback.

Memory #

A LynxView is not a cheap widget: each one owns an engine, a JS runtime, an element tree and its own image cache, and all of it lives in your app's process β€” unlike a WKWebView, whose content sits in a separate process the OS bills separately. Two things follow from that, and the package handles the first for you.

Pressure is forwarded automatically. Flutter surfaces the platform's memory warning (applicationDidReceiveMemoryWarning on iOS, onTrimMemory on Android); this package relays it to Lynx so live views shed their caches instead of holding on until the system kills the app. Nothing to wire up β€” the relay is active only while a LynxView is mounted. Trigger it yourself if you need to:

await LynxMemory.trim(LynxMemoryPressureLevel.critical);

And you can ask where the memory went, rather than inferring it from RSS (which measures something else β€” it counts mapped shared libraries, and can run well above the footprint the OS actually holds you to):

final usage = await LynxMemory.usage();
usage.appBytes;                     // the app's physical footprint
usage.totalBytes;                   // what Lynx accounts for within it
usage.elementBytes;                 // element tree
usage.viewBytes;                    // platform views Lynx created
usage.mainThreadRuntimeBytes;       // main-thread JS (PrimJS)
usage.backgroundThreadRuntimeBytes; // background JS β€” JSC on iOS
usage.instances;                    // the same, per live view

Disposing matters more than instance count. A handful of views is a few percent of a modern phone's budget, but a view that never releases grows without bound β€” so call controller.dispose(), and prefer dropping views you can cheaply rebuild over keeping them alive off-screen.

See the example/ app for a complete, runnable demo.

Additional information #

Source, issue tracker, and design notes: https://github.com/GeekTree0101/lynx_view

0
likes
0
points
168
downloads

Publisher

unverified uploader

Weekly Downloads

Wraps LynxJS (https://lynxjs.org) native LynxView as a Flutter PlatformView, so existing Flutter apps can embed Lynx templates.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, lynx_view_android, lynx_view_ios, lynx_view_platform_interface

More

Packages that depend on lynx_view

Packages that implement lynx_view