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

Eigen Engine — whitelabel Flutter client for turn-based multiplayer games on the Eigen server.

EigenInteractive

Eigen Flutter #

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

eigen_flutter supplies the complete app shell—authentication, lobbies, reconnection, timing, ratings, history, replay, social features, notifications, deep links, and update UX. 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.1.0
  firebase_core: ^4.9.0
  firebase_messaging: ^16.2.0

Import the framework through its public barrel:

import 'package:eigen_flutter/eigen_flutter.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, flutter_local_notifications 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. Eigen's 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() => runEngineApp(
  module: const MyGameModule(),
  config: AppConfig(
    branding: const Branding(appName: 'My Game', seedColor: Colors.indigo),
    engine: EngineConfig(
      apiBaseUrl: apiBaseUrl,
      googleWebClientId: googleWebClientId,
      firebaseVapidKey: firebaseVapidKey,
      appHost: appHost.isEmpty ? null : appHost,
    ),
  ),
  firebaseOptions: DefaultFirebaseOptions.currentPlatform,
  onBackgroundMessage: onBackgroundMessage,
);

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 Firebase or any engine service starts. Keep actual secrets on the Worker.

Connect a game app to Firebase with the package executable:

dart run eigen_flutter: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; this package's executable turns that artifact into immutable Dart payloads, a typed rules base, and fixture copies:

dart run eigen_flutter: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.