secondary_screen 2.0.0 copy "secondary_screen: ^2.0.0" to clipboard
secondary_screen: ^2.0.0 copied to clipboard

A Flutter package for managing dual/secondary screen displays with BLoC state management, supporting data transfer and route control.

secondary_screen #

pub package pub points platform license: MIT

Drive a dual / secondary screen from Flutter with BLoC state management — show named routes on the second display, push live data to it, and auto-reconnect when a display is plugged or unplugged. Ideal for Point of Sale (POS) setups where the customer sees a live order summary or promotions.

POS demo

The cashier's primary screen (left) drives the customer-facing secondary display (right) in real time.

Customer-facing secondary display on a Zonerich POS terminal

Running on a real Zonerich dual-screen POS terminal — the customer-facing display.

Platform: Android only. Built on the Android Presentation API via presentation_displays.

🚀 Features #

  • Route control — show any named route on the secondary display.
  • Live data transfer — push structured, event-based payloads (TransferDataModel) to the second screen in real time.
  • Receive widgetSecondaryDisplay delivers incoming data to your secondary UI.
  • Auto-reconnect — restores the last route when a display is plugged/unplugged.
  • Single entry point — a singleton SecondaryScreenCubit any layer can call.
  • Low-level accessDisplayManager to enumerate displays directly when you need it.

📦 Installation #

Add the package to your pubspec.yaml:

dependencies:
  secondary_screen: ^2.0.0

Then import it:

import 'package:secondary_screen/secondary_screen.dart';

⚙️ Setup (Android) #

A secondary display runs in its own Flutter entry point and engine. No extra <activity> registration is required — the default flutterEmbedding v2 meta-data Flutter generates is enough.

1. Declare the secondary entry point in your main.dart. It must be annotated with @pragma('vm:entry-point') so it survives tree-shaking:

@pragma('vm:entry-point')
void secondaryDisplayMain() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MySecondApp());
}

class MySecondApp extends StatelessWidget {
  const MySecondApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      onGenerateRoute: generateRoute, // same route table as the primary app
      initialRoute: 'presentation',
    );
  }
}

2. Share one onGenerateRoute between both entry points so route names line up:

Route<dynamic> generateRoute(RouteSettings settings) {
  switch (settings.name) {
    case 'presentation':
      return MaterialPageRoute(builder: (_) => const PromotionScreen());
    case 'order_display':
      return MaterialPageRoute(builder: (_) => const OrderDisplayScreen());
    default:
      return MaterialPageRoute(
        builder: (_) => const Scaffold(body: Center(child: Text('No route'))),
      );
  }
}

🛠️ Usage #

Initialize — wrap your app with a BlocProvider and call init (e.g. in initState):

BlocProvider(
  create: (_) => SecondaryScreenCubit(),
  child: const MaterialApp(onGenerateRoute: generateRoute, initialRoute: 'sales'),
)

context.read<SecondaryScreenCubit>().init(
  autoShow: true,
  defaultRouterName: 'presentation',
);

SecondaryScreenCubit is a singleton — reach it anywhere via SecondaryScreenCubit.instance.

Show a route & push data — build the payload with TransferDataModel; the eventName tells the receiver what to do:

final payload = TransferDataModel(
  eventName: 'update_order',
  data: {'items': [...], 'total': 42000},
);

// Navigate the second screen and send the first payload:
await SecondaryScreenCubit.instance.showOnSecondary(
  'order_display',
  json: jsonEncode(payload.toJson()),
);

// Later, refresh data without re-navigating:
await SecondaryScreenCubit.instance.updateDataOnSecondary(jsonEncode(payload.toJson()));

showOnSecondary only re-navigates when the screen isn't already showing or the route changes — otherwise it just transfers data, so it's safe to call repeatedly.

Receive data on the secondary screen — wrap the route's UI in SecondaryDisplay and handle payloads in its callback:

SecondaryDisplay(
  callback: (args) {
    if (args is! Map) return;
    final map = Map<String, dynamic>.from(args);
    if (map['event_name'] == 'update_order') {
      final data = Map<String, dynamic>.from(map['data']);
      // ...update your UI from data...
    }
  },
  child: yourCustomerFacingUi,
)

Hide & reconnect:

await SecondaryScreenCubit.instance.hideOnSecondary(clearData: true);
await SecondaryScreenCubit.instance.reConnectCurrentRoute(); // restores the last route

💡 For a full, runnable Point-of-Sale demo (sales, order display, promotion carousel, and an event-based todo screen), see the example/ directory.

📖 API reference #

SecondaryScreenCubit #

A singleton Cubit<SecondaryScreenState>.

Method Returns Description
init({autoShow = true, defaultRouterName}) Future<void> Detect displays, connect, optionally show defaultRouterName
showOnSecondary(routeName, {json}) Future<bool> Navigate to a route (deduped) and optionally send a JSON payload
updateDataOnSecondary(data) Future<bool> Push a new JSON payload without changing the route
hideOnSecondary({clearData = false}) Future<bool> Hide the secondary display; keeps the last route for reconnecting
reConnectCurrentRoute() Future Re-show the last active route after a reconnect

SecondaryScreenState #

Field Type Description
status SecondaryScreenServiceState initial, connected, disconnected
currentSecondaryDisplay Display? The active secondary display
availableDisplays List<Display>? All detected displays
currentRoute String? Currently shown route name
currentData String? Last transferred JSON payload
isShowing bool Whether a route is currently shown
isLoading bool Whether an operation is in progress
error String? Last error message, if any

Getter: defaultSecondaryDisplayIdcurrentSecondaryDisplay?.displayId.

TransferDataModel #

TransferDataModel(eventName, data) — serializes to/from JSON with snake_case keys (event_name, data) via toJson() / fromJson().

SecondaryDisplay #

SecondaryDisplay({callback, child}) — widget placed on the secondary screen; callback(dynamic args) receives each decoded payload.

DisplayManager #

Low-level platform access: getDisplays(), getNameByDisplayId(), getNameByIndex(), showSecondaryDisplay(), hideSecondaryDisplay(), transferDataToPresentation(), and the connectedDisplaysChangedStream (Stream<int?> of the connected-display count).

🤝 Contributions & Issues #

Contributions are welcome!

📄 License #

Released under the MIT License. See LICENSE for details.

2
likes
0
points
267
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for managing dual/secondary screen displays with BLoC state management, supporting data transfer and route control.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_bloc

More

Packages that depend on secondary_screen

Packages that implement secondary_screen