eigen_flutter 0.8.0 copy "eigen_flutter: ^0.8.0" to clipboard
eigen_flutter: ^0.8.0 copied to clipboard

EigenInteractive Flutter client: the whitelabel app half for turn-based multiplayer games on the EigenInteractive server.

EigenInteractive

EigenInteractive Flutter #

The Flutter half of EigenInteractive: a server-authoritative engine for turn-based multiplayer games.

eigen_client is the pure Dart protocol, domain, clock, and live-session runtime. eigen_flutter builds provider-neutral Flutter presentation on it and, for the moment, supplies the complete app shell. Firebase is an optional adapter in the separate eigen_firebase package. A game supplies a small GameModule containing its client-side rules and presentation.

Start a game #

The recommended flow creates the Cloudflare Worker and Flutter app together:

pnpm create eigen-game my-game
# or
npm create eigen-game@latest my-game

The scaffold installs published npm/pub.dev dependencies; it does not clone the engine repositories. For an existing app or separate Worker/app repositories, follow the manual setup guide.

Add to an app #

dependencies:
  eigen_flutter: ^0.8.0
  eigen_firebase: ^0.1.0 # only when the app chooses Firebase
  firebase_core: ^4.9.0

Import the framework through its public barrel:

import 'package:eigen_flutter/eigen_flutter.dart';
import 'package:eigen_firebase/eigen_firebase.dart';

Do not depend on eigen_api directly or deep-import core/, features/, or shared/. The barrel is the supported game-facing API.

On Android, the Firebase adapter's local-notification dependency requires core-library desugaring in the application module. create-eigen-game configures it automatically. For a hand-created app, add this to android/app/build.gradle.kts:

android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
    }
}

dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}

Boot the app with your module, branding, Worker origin, and generated Firebase configuration. The standard app targets Android and web, so the public Web Push key is required deployment configuration; it belongs to the same Firebase project used for authentication:

const apiBaseUrl = String.fromEnvironment('API_BASE_URL');
const googleWebClientId = String.fromEnvironment('GOOGLE_WEB_CLIENT_ID');
const firebaseVapidKey = String.fromEnvironment('FIREBASE_VAPID_KEY');
const appHost = String.fromEnvironment('APP_HOST');

Future<void> main() => runFirebaseEngineApp(
  module: const MyGameModule(),
  config: AppConfig(
    branding: const Branding(appName: 'My Game', seedColor: Colors.indigo),
    engine: EngineConfig(
      apiBaseUrl: apiBaseUrl,
      appHost: appHost.isEmpty ? null : appHost,
    ),
  ),
  firebaseOptions: DefaultFirebaseOptions.currentPlatform,
  firebase: FirebaseAdapterConfig(
    googleWebClientId: googleWebClientId,
    vapidKey: firebaseVapidKey,
  ),
  onBackgroundMessage: onBackgroundMessage,
  telemetry: FirebaseTelemetryPolicy.releaseOnly(),
);

Without Firebase, omit eigen_firebase and call runEngineApp directly. Its auth, notification, and analytics ports default to unavailable/no-op adapters; another integration can override those ports through package:eigen_flutter/adapters.dart.

These are public build-time values. Scaffolded apps keep them in app-config.json and use the same command option for Android and web:

flutter run --dart-define-from-file=app-config.json

Missing or malformed required values are reported together before their services start. Keep actual secrets on the Worker. Telemetry collection is off unless the app passes an explicit policy such as releaseOnly().

Connect a game app to Firebase with the package executable:

dart run eigen_firebase:configure_firebase

It runs FlutterFire for Android and web, then derives web/firebase-config.js for the messaging service worker from the Web app FlutterFire selected. The public VAPID key remains in app-config.json because it is not part of Firebase's app SDK configuration.

The game boundary #

The authoritative TypeScript module declares state, observation, action, and config once. It emits game-contract.json; the development-only eigen_codegen package turns that artifact into immutable Dart payloads, a typed rules base, and fixture copies:

flutter pub add --dev eigen_codegen
dart run eigen_codegen:generate_payloads \
  --contract ../server/game-contract.json \
  --output lib/game/generated/payloads.dart \
  --fixtures-output test/fixtures

Your handwritten Dart code then:

  • extends the generated V<N>RulesBase;
  • implements client-side legality and optional optimistic preview;
  • renders the game from GameContentContext;
  • registers one rules unit per server schemaVersion;
  • declares the version-independent creation and rules UI.

The server remains authoritative. Shared fixtures run against both languages so payload and behavior drift fails in tests.

Example #

example/ is a complete Rock–Paper–Scissors client. It deliberately uses simultaneous hidden commitments to demonstrate per-seat observations and the valid “do not predict this move” path:

cd example
flutter pub get
flutter test
flutter build web --release

The package treats Android and web as supported targets. The generated scaffold includes the browser Firebase Messaging service worker, Firebase Auth's web popup flow, cross-origin Worker setup, and a release web build in CI. See Deploy the web app.

Documentation #

Working on the framework #

  • CONTRIBUTING.md: local setup, generation, validation, changelog entries, and pull requests.
  • MAINTAINERS.md: pub.dev setup, releases, version tags, and failure recovery.