Documentation

Full Device Screen Sharing

By default, the UserView Flutter SDK shares only your app's screen. With full device screen sharing, agents can see the entire device screen, including other apps, the home screen, and system UI.

On iOS this uses Apple's Broadcast Upload Extension (ReplayKit); on Android it uses the MediaProjection API. Both need a little native setup, described below.

Physical devices only
Full device screen sharing only works on physical devices. It is not supported on the iOS Simulator or Android emulators.

Full device screen sharing must also be enabled in the UserView dashboard (admin interface) in addition to the steps below. If it is disabled there, agents will not see the full device option during sessions.

iOS setup

Full device capture on iOS runs in a Broadcast Upload Extension — a separate native target in your app's Xcode project (under ios/). It sends frames to your app through an App Group, and the SDK relays them to the agent.

1. Create the Broadcast Upload Extension target

Create the extension target first — the Podfile step below references it by name, so it must already exist in the Xcode project before you run pod install.

  1. Open ios/Runner.xcworkspace in Xcode
  2. Go to File > New > Target
  3. Select Broadcast Upload Extension
  4. Uncheck "Include UI Extension" — a UI extension adds a setup screen whose default handler never completes, so the broadcast would never start
  5. Name it (e.g., YourAppBroadcast) — you'll reference this exact name in the Podfile next
  6. Note the extension's bundle identifier (Xcode prefixes it with your app's, e.g. com.yourcompany.yourapp.YourAppBroadcast) — you'll need it in step 6

2. Add the extension dependency

In your app's ios/Podfile, add the broadcast subspec to the extension target. Declare it as a top-level target (a sibling of Runner, not nested inside it). use_frameworks! is required so the extension's linkage matches the Runner target, which Flutter links as frameworks. Do not pin a version — it follows the UpscopeSDK version that upscopeio_flutter_sdk already requires:

target 'YourAppBroadcast' do
  use_frameworks!
  pod 'UpscopeSDK/BroadcastExtension'
end

Then run pod install from the ios/ directory.

3. Configure App Groups

Both your main app (the Runner target) and the extension need to share data through an App Group:

  1. Select the Runner target > Signing & Capabilities > + Capability > App Groups
  2. Add a group identifier (e.g., group.com.yourcompany.yourapp)
  3. Select your extension target > Signing & Capabilities > + Capability > App Groups
  4. Add the same group identifier

4. Configure the extension's Info.plist

Add the App Group identifier to the extension's Info.plist:

<key>UpscopeAppGroupId</key>
<string>group.com.yourcompany.yourapp</string>

5. Implement the extension

Replace the contents of the extension's SampleHandler.swift with:

import UpscopeBroadcastExtension

class SampleHandler: UpscopeSampleHandler {}

All frame capture and forwarding is handled by UpscopeSampleHandler.

6. Configure the SDK

Pass the App Group and the extension's bundle identifier when initializing the SDK. broadcastExtensionBundleId must exactly match the extension target's bundle identifier from step 1 — it is not the App Group id:

import 'package:upscopeio_flutter_sdk/upscopeio_flutter_sdk.dart';

await Upscope.instance.initialize(
  UpscopeConfiguration(
    apiKey: 'YOUR_API_KEY',
    broadcastAppGroupId: 'group.com.yourcompany.yourapp',
    broadcastExtensionBundleId: 'com.yourcompany.yourapp.YourAppBroadcast',
  ),
);

If you do not set broadcastAppGroupId, the SDK will not advertise full device support on iOS, and agent requests for it are declined automatically.

By default, an agent's full device request goes straight to the system permission prompt. To show your own confirmation UI first, set customFullDeviceRequestUI: true in the configuration and handle incoming requests — see Responding to full device requests below. With the flag set, requests stall silently unless a listener calls respondToFullDeviceRequest.

Android setup

Add the screen-capture permissions to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />

The SDK already declares the capture service and activity. It detects these permissions at runtime — if they are missing, full device screen sharing is disabled regardless of the dashboard setting. No further configuration is required; Android shows the system screen-capture permission dialog when sharing starts.

Responding to full device requests

By default the SDK proceeds directly to the system permission prompt when an agent requests full device sharing. To gate the request behind your own UI, set customFullDeviceRequestUI: true in the configuration — the SDK then routes requests to your Dart code instead. You must listen on onFullDeviceRequest and respond, or requests will never proceed:

final requestSub = Upscope.instance.onFullDeviceRequest.listen((request) {
  // request.agentName is the requesting agent's name, if available.
  // Optionally show your own confirmation UI here, then respond:
  Upscope.instance
      .respondToFullDeviceRequest(request.requestId, accept: true); // accept: false to decline
});

final stateSub = Upscope.instance.fullDeviceSharingState.listen((state) {
  // FullDeviceSharingState.pendingRequest / .active / .inactive
  debugPrint('Full device sharing: $state');
});

// Cancel both subscriptions in dispose().

After you accept, iOS shows the system broadcast picker (the user taps Start Broadcast) and Android shows the screen-capture dialog (the user taps Start now). End sharing programmatically at any time:

Upscope.instance.stopFullDeviceSharing();

The user can also stop sharing from iOS Control Center or the Android capture notification; the SDK reports this via fullDeviceSharingState.

Responding to control requests

Remote control requests follow the same pattern, but are opt-in: set customControlRequestUI: true in the configuration to replace the native control request prompt. The SDK then routes requests to your Dart code — listen on onControlRequest and respond with respondToControlRequest:

final controlRequestSub = Upscope.instance.onControlRequest.listen((request) {
  // request.agentName is the requesting agent's name, if available.
  // Optionally show your own confirmation UI here, then respond:
  Upscope.instance
      .respondToControlRequest(request.requestId, accept: true); // accept: false to decline
});

final controlStateSub = Upscope.instance.remoteControlState.listen((state) {
  // RemoteControlState.pendingRequest / .active / .inactive
  debugPrint('Remote control: $state');
});

// Cancel both subscriptions in dispose().

Revoke control at any time with Upscope.instance.stopRemoteControl().

Limitations

  • Works on physical devices only (not the iOS Simulator or Android emulators)
  • Element masking is not available in full device mode (the SDK cannot inspect views outside your app)
  • Remote control only works within your app, not on the home screen or other apps
  • Drawing annotations are not displayed in full device mode
  • The user must explicitly start the broadcast/capture via the system prompt — it cannot be started programmatically