Documentation

Listening for Events

The UserView Flutter SDK exposes all events as Dart Streams. Subscribe to them for reactive event handling.

Listening for Events

import 'dart:async';
import 'package:upscopeio_flutter_sdk/upscopeio_flutter_sdk.dart';

class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final List<StreamSubscription> _subscriptions = [];

  @override
  void initState() {
    super.initState();

    _subscriptions.add(
      Upscope.instance.connectionState.listen((state) {
        switch (state) {
          case ConnectionState.inactive:
            print('Inactive');
          case ConnectionState.connecting:
            print('Connecting...');
          case ConnectionState.connected:
            print('Connected');
          case ConnectionState.reconnecting:
            print('Reconnecting...');
          case ConnectionState.error:
            print('Error');
        }
      }),
    );

    _subscriptions.add(
      Upscope.instance.onSessionStarted.listen((_) {
        print('Session started');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onSessionEnded.listen((reason) {
        switch (reason) {
          case SessionEndReason.userStopped:
            print('User ended session');
          case SessionEndReason.agentStopped:
            print('Agent ended session');
          case SessionEndReason.timeout:
            print('Session timed out');
          case SessionEndReason.error:
            print('Session error');
        }
      }),
    );

    _subscriptions.add(
      Upscope.instance.onCustomMessageReceived.listen((msg) {
        print('Message from ${msg.viewerId}: ${msg.message}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onError.listen((error) {
        print('Error: ${error.code} - ${error.message}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onViewerJoined.listen((viewer) {
        print('Viewer joined: ${viewer.name ?? viewer.id}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onViewerLeft.listen((viewerId) {
        print('Viewer left: $viewerId');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onViewerCountChanged.listen((count) {
        print('Viewers: $count');
      }),
    );

    _subscriptions.add(
      Upscope.instance.remoteControlState.listen((state) {
        print('Remote control: ${state.name}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.fullDeviceSharingState.listen((state) {
        print('Full device sharing: ${state.name}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onFullDeviceRequest.listen((request) {
        print('Full device request from ${request.agentName ?? "agent"}');
        // Only fires when the configuration sets customFullDeviceRequestUI:
        // true (otherwise the SDK proceeds directly to the system prompt).
        // accept: true proceeds to the system permission prompt; false declines
        Upscope.instance
            .respondToFullDeviceRequest(request.requestId, accept: true);
      }),
    );

    _subscriptions.add(
      Upscope.instance.onControlRequest.listen((request) {
        print('Control request from ${request.agentName ?? "agent"}');
        // Only fires when the configuration sets customControlRequestUI: true
        // (replacing the native control request prompt).
        // accept: true grants remote control; false declines
        Upscope.instance
            .respondToControlRequest(request.requestId, accept: true);
      }),
    );

    _subscriptions.add(
      Upscope.instance.onSessionRequest.listen((request) {
        print('Session request from ${request.agentName ?? "agent"}');
        // Only fires when the configuration sets customSessionRequestUI: true
        // (replacing the native authorization dialog) and authorization is
        // required.
        // accept: true starts the session; false declines
        Upscope.instance
            .respondToSessionRequest(request.requestId, accept: true);
      }),
    );
  }

  @override
  void dispose() {
    for (final sub in _subscriptions) {
      sub.cancel();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return const SizedBox.shrink();
  }
}

Event Reference

StreamTypeDescription
connectionStateConnectionStateConnection state changed.
onSessionStartedString?A screen sharing session has started. Emits the agent's name, if available.
onSessionEndedSessionEndReasonA session has ended. Reason indicates why (userStopped, agentStopped, timeout, or error).
onCustomMessageReceivedCustomMessageA custom message was received from a viewer. Includes message and viewerId.
onErrorUpscopeErrorAn SDK error occurred. Includes code and message.
onViewerJoinedViewerAn agent started viewing the session. The Viewer includes id, name, and screen metrics.
onViewerLeftStringAn agent stopped viewing the session. Emits the viewer ID.
onViewerCountChangedintThe total number of active viewers changed.
remoteControlStateRemoteControlStateEmits when whether an agent has remote control of the device changes (ability to tap and scroll). Enum values: inactive, pendingRequest, active. Independent of the session being active.
fullDeviceSharingStateFullDeviceSharingStateEmits when whether full-device (entire screen) sharing is running changes, as opposed to default in-app screen sharing. Enum values: inactive, pendingRequest, active.
onFullDeviceRequestFullDeviceRequestFires when an agent requests full-device sharing, before the system permission prompt. Only fires when customFullDeviceRequestUI: true is set in the configuration — without the flag, the SDK proceeds directly to the system prompt. Emits a FullDeviceRequest with requestId and agentName. Respond with Upscope.instance.respondToFullDeviceRequest(request.requestId, accept: ...)true allows (proceeds to the system prompt), false declines (stays in in-app mode). Without a listener that responds, full-device requests stall.
onControlRequestControlRequestFires when an agent requests remote control of the device. Only fires when customControlRequestUI: true is set in the configuration and requireControlRequest is enabled (note: requireControlRequest defaults to false, so control is otherwise granted without a request); the native control request prompt is not shown. Emits a ControlRequest with requestId and agentName. Respond with Upscope.instance.respondToControlRequest(request.requestId, accept: ...)true grants control, false declines. Without a listener that responds, control requests stall.
onSessionRequestSessionRequestFires when an agent requests to start a cobrowsing session. Only fires when customSessionRequestUI: true is set in the configuration and requireAuthorizationForSession is enabled; the native authorization dialog is not shown. Emits a SessionRequest with requestId and agentName. Respond with Upscope.instance.respondToSessionRequest(request.requestId, accept: ...)true starts the session, false declines. Without a listener that responds, session requests stall and the session never starts.

Using StreamBuilder

For reactive UI updates, use StreamBuilder instead of manual subscriptions:

StreamBuilder<ConnectionState>(
  stream: Upscope.instance.connectionState,
  builder: (context, snapshot) {
    if (!snapshot.hasData) return const SizedBox.shrink();
    return Text('Status: ${snapshot.data!.name}');
  },
)