Skip to content

[firebase_data_connect]: TypeError: Instance of 'LegacyJavaScriptObject' is not a subtype of 'MyClass' #18365

@wer-mathurin

Description

@wer-mathurin

Is there an existing issue for this?

  • I have searched the existing issues.

Which plugins are affected?

Other

Which platforms are affected?

Web

Description

In Flutter Web applications, when utilizing JS-Interop SDKs (such as Firebase Data Connect, WebSockets, or third-party JS listeners) that maintain active event subscriptions in the browser, triggering a Hot Restart causes type check failures.

Specifically, the browser's JavaScript environment persists across Hot Restarts while the Dart VM restarts. Lingering JS callbacks continue firing and passing events back into the newly loaded Dart context. When the new Dart runtime performs a type-check on elements returned from these callbacks, it fails with:

TypeError: Instance of 'LegacyJavaScriptObject': type 'LegacyJavaScriptObject' is not a subtype of type 'MyClass'

This is caused by the type representation changing between VM reloads, causing Dart to treat the old execution's objects as generic JS objects (LegacyJavaScriptObject).

Reproducing the issue

  • Configure Firebase Data Connect in a Flutter Web project.
  • Define a GraphQL query (e.g., ListMessages) and generate the SDK.
  • Call subscribe().listen((result) { ... }) on the generated query reference.
  • Perform a Hot Restart (r in the terminal or click hot restart in your IDE).
  • As soon as the page loads and registers the query subscription, it immediately receives the initial/cached query data.
  • The app crashes immediately with the following type-cast error during the mapping phase

Firebase Core version

4.9.0

Flutter Version

3.41.8

Relevant Log Output

Flutter dependencies

Expand Flutter dependencies snippet
Replace this line with the contents of your `flutter pub deps -- --style=compact`.

Additional context and comments

Workaround

To bypass this during development, detect if the application is starting up from a hot-restart state on the web. If a global flag is already present in window, programmatically force a browser refresh (window.location.reload()) to cleanly flush the JS environment:

import 'dart:js_interop';
import 'package:flutter/foundation.dart';

@JS('window')
external JSObject get _window;

@JS('window.location.reload')
external void _reloadPage();

@JS('Reflect.has')
external bool _reflectHas(JSObject target, JSString propertyKey);

@JS('Reflect.set')
external bool _reflectSet(JSObject target, JSString propertyKey, JSAny value);

void checkHotRestart() {
  if (kIsWeb && kDebugMode) {
    try {
      final windowObj = _window;
      final hasLoaded = _reflectHas(windowObj, '__hasLoaded'.toJS);
      if (hasLoaded) {
        _reloadPage();
        return;
      }
      _reflectSet(windowObj, '__hasLoaded'.toJS, true.toJS);
    } catch (_) {}
  }
}

Workaround Caveats

Web Debugger Connection Warning: Forcing a browser reload outside of the Dart debugger's flow interrupts the active Debugger Connection (DWDS), printing these harmless warnings to the terminal:

AppInspector: Error calling Runtime.evaluate with params {expression: ...} Error: WipError -32000 Cannot find context with specified id
RemoteDebuggerExecutionContext: Timed out finding an execution context after 100 ms.

The debugger automatically re-attaches once the browser finishes reloading.

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions