upscopeio_flutter_sdk 2026.7.0
upscopeio_flutter_sdk: ^2026.7.0 copied to clipboard
Flutter SDK for Upscope cobrowsing — screen sharing, annotations, and remote control
Upscope Flutter SDK #
Flutter plugin for Upscope cobrowsing — lets support agents view and interact with your users' screens in real-time, with no extra permissions required.
Requirements #
- Flutter 3.19+
- Dart SDK 3.3+
- iOS 14.0+
- Android API 26+ (Android 8.0)
Installation #
flutter pub add upscopeio_flutter_sdk
Or add it manually to your pubspec.yaml:
dependencies:
upscopeio_flutter_sdk: ^2026.7.0
Then run:
flutter pub get
Quick Start #
1. Initialize #
Call initialize once, early in your app lifecycle (e.g. in main or your root widget's initState):
import 'package:upscopeio_flutter_sdk/upscopeio_flutter_sdk.dart';
await Upscope.instance.initialize(
UpscopeConfiguration(apiKey: 'your_api_key'),
);
By default autoConnect: true, so the SDK connects immediately after initialization.
2. Identify the visitor #
await Upscope.instance.updateConnection(
uniqueId: 'user-123',
callName: 'Jane Smith',
tags: ['premium', 'mobile'],
);
Call this any time visitor identity changes (e.g. after sign-in).
3. Show a lookup code to the user #
The lookup code lets a support agent start a session by entering a short code in the Upscope dashboard:
Upscope.instance.lookupCode.listen((code) {
if (code != null) {
print('Share this code with support: $code');
}
});
That's all. The agent can now initiate a cobrowsing session from the dashboard.
Core Concepts #
Connection vs. Session #
- Connection — persistent WebSocket link to Upscope servers. Established via
connect(), torn down viadisconnect(). Required before any session can start. - Session — an active cobrowsing window with an agent. Begins when an agent joins (or when
requestAgent()is called), ends when either party stops it.
Reactive streams #
All state is exposed as Streams so you can drive your UI reactively without polling.
// Show connection status in a StreamBuilder
StreamBuilder<ConnectionState>(
stream: Upscope.instance.connectionState,
builder: (context, snapshot) {
return Text(switch (snapshot.data) {
ConnectionState.connected => 'Ready',
ConnectionState.connecting => 'Connecting…',
ConnectionState.reconnecting => 'Reconnecting…',
ConnectionState.error => 'Error',
_ => 'Offline',
});
},
);
Masking sensitive widgets #
Wrap any widget in UpscopeMasked to hide it from the agent's view. The region is replaced with a black rectangle in the screen capture; your users see the real content as normal.
UpscopeMasked(
child: CreditCardField(),
)
UpscopeMasked tracks position automatically — it handles layout changes, device rotation, and scroll without any extra wiring.
Common Tasks #
Listen for session lifecycle events #
Upscope.instance.onSessionStarted.listen((_) {
showBanner('Screen sharing started');
});
Upscope.instance.onSessionEnded.listen((reason) {
showBanner('Session ended: $reason');
});
React to viewer changes #
Upscope.instance.onViewerJoined.listen((viewer) {
print('Agent joined: ${viewer.name}');
});
Upscope.instance.onViewerCountChanged.listen((count) {
print('Viewers watching: $count');
});
Remote control and full-device sharing #
Observe and revoke agent device control, and respond to full-device sharing requests:
Upscope.instance.remoteControlState.listen((state) {
print('Remote control: ${state.name}'); // inactive | pendingRequest | active
});
Upscope.instance.fullDeviceSharingState.listen((state) {
print('Full-device sharing: ${state.name}'); // inactive | pendingRequest | active
});
// Stop an in-progress agent interaction (session stays active)
await Upscope.instance.stopRemoteControl();
// Stop full-device screen sharing
await Upscope.instance.stopFullDeviceSharing();
// Approve or decline a full-device sharing request
Upscope.instance.onFullDeviceRequest.listen((request) async {
final allow = await askUser(request.agentName);
await Upscope.instance
.respondToFullDeviceRequest(request.requestId, accept: allow);
});
// Approve or decline a remote-control request
Upscope.instance.onControlRequest.listen((request) async {
final allow = await askUser(request.agentName);
await Upscope.instance
.respondToControlRequest(request.requestId, accept: allow);
});
The pendingRequest state lets you build a fully custom accept/decline UI
driven purely off the state stream: render your prompt while the state is
pendingRequest, then respond via respondToControlRequest /
respondToFullDeviceRequest. The onControlRequest / onFullDeviceRequest
events each carry the requesting agentName (nullable) alongside the
requestId.
Full-device sharing on iOS requires a Broadcast Upload Extension target — see Full device sharing (iOS).
Request an agent #
Proactively ask for a support agent without waiting for them to initiate:
await Upscope.instance.requestAgent();
// Cancel if the user changes their mind
await Upscope.instance.cancelAgentRequest();
Stop a session #
await Upscope.instance.stopSession();
Send a custom message to the agent #
await Upscope.instance.sendCustomMessage('{"event": "checkout_started"}');
Receive messages from the agent:
Upscope.instance.onCustomMessageReceived.listen((msg) {
print('From ${msg.viewerId}: ${msg.message}');
});
Reset the connection #
Clears stored identity and reconnects (useful on sign-out):
// Reset and reconnect
await Upscope.instance.reset();
// Reset without reconnecting
await Upscope.instance.reset(reconnect: false);
Handle errors #
Upscope.instance.onError.listen((error) {
print('Upscope error [${error.code}]: ${error.message}');
});
Configuration Reference #
All options are set once in UpscopeConfiguration passed to initialize:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
String |
required | Your Upscope API key |
autoConnect |
bool |
true |
Connect automatically on initialize |
requireAuthorizationForSession |
bool |
true |
Prompt user before allowing agent to view screen |
authorizationPromptTitle |
String |
'Screen Sharing Request' |
Title of the consent dialog |
authorizationPromptMessage |
String |
'An agent wants to view your screen. Do you accept?' |
Body of the consent dialog |
showTerminateButton |
bool |
true |
Show a stop-sharing button in the session banner |
stopSessionText |
String |
'Stop Sharing' |
Label on the stop-sharing button |
endOfSessionMessage |
String? |
null |
Message shown to the user when the session ends |
allowRemoteClick |
bool? |
null |
Override server-side remote click permission |
allowRemoteScroll |
bool? |
null |
Override server-side remote scroll permission |
requireControlRequest |
bool? |
null |
Require explicit user approval before agent can interact |
region |
String? |
null |
Force a specific Upscope server region |
broadcastAppGroupId |
String? |
null |
iOS only — App Group shared with the broadcast extension |
broadcastExtensionBundleId |
String? |
null |
iOS only — Bundle ID of the broadcast upload extension |
Full device sharing (iOS) #
Full device sharing lets the agent see the entire device screen (other apps, home screen) via ReplayKit. It requires a Broadcast Upload Extension target in your app — Flutter plugins can't add extension targets, so this is a one-time manual setup (~10 minutes). Without it, the agent's "Full app sharing" button is hidden.
Android needs no setup: it uses MediaProjection, and the required foreground-service permissions are merged automatically from the SDK's manifest.
1. Add the extension target #
In Xcode: File → New → Target → Broadcast Upload Extension. Name it (e.g.
YourAppBroadcast), uncheck "Include UI Extension".
2. Replace the generated SampleHandler #
Open YourAppBroadcast/SampleHandler.swift (created by Xcode) and replace its
contents:
import UpscopeSDK
class SampleHandler: UpscopeSampleHandler {}
3. Add an App Group to BOTH targets #
In Signing & Capabilities for your app target and the extension target,
add the App Groups capability with the same group, e.g.
group.com.yourcompany.yourapp.
4. Declare the group in the extension's Info.plist #
<key>UpscopeAppGroupId</key>
<string>group.com.yourcompany.yourapp</string>
Also make sure NSExtension > RPBroadcastProcessMode is 2 (sample-buffer
mode); some Xcode template versions omit it. See the example app's
Info.plist.
5. Add the extension pod #
# ios/Podfile (top level, alongside the Runner target)
target 'YourAppBroadcast' do
pod 'UpscopeSDK/BroadcastExtension'
end
Run pod install.
6. Pass the keys at initialization #
await Upscope.instance.initialize(
UpscopeConfiguration(
apiKey: 'your_api_key',
broadcastAppGroupId: 'group.com.yourcompany.yourapp',
broadcastExtensionBundleId: 'com.yourcompany.yourapp.YourAppBroadcast',
),
);
broadcastAppGroupId enables the feature (the agent's button appears);
broadcastExtensionBundleId preselects your extension in the iOS picker.
Notes #
- The App Group ID must match in three places: both targets' entitlements,
the extension's
Info.plist(UpscopeAppGroupId), andbroadcastAppGroupId. - Test on a physical device — ReplayKit broadcasts are unreliable on the simulator.
- Full-device capture cannot mask views. Set
disableFullScreenWhenMasked: trueto automatically end full-device sharing whenever masked content is on screen. - Add-to-app setups: the extension target must be added to the host iOS project, not the Flutter module.
- Without the extension, screen-only and visitor-app sharing still work; only full-device sharing is unavailable.
- A complete working setup is in
example/ios(RunnerBroadcasttarget).
API Reference #
Upscope.instance — methods #
| Method | Returns | Description |
|---|---|---|
initialize(UpscopeConfiguration) |
Future<void> |
Initialize and optionally auto-connect |
connect() |
Future<void> |
Connect to Upscope servers |
disconnect() |
Future<void> |
Disconnect from Upscope servers |
reset({bool reconnect}) |
Future<void> |
Clear identity and optionally reconnect |
updateConnection({uniqueId, callName, tags, identities, metadata}) |
Future<void> |
Update visitor identity |
stopSession() |
Future<void> |
End the current cobrowsing session |
requestAgent() |
Future<void> |
Request a support agent |
cancelAgentRequest() |
Future<void> |
Cancel a pending agent request |
getLookupCode() |
Future<void> |
Trigger a lookup code refresh |
getShortId() |
Future<String?> |
Get the persistent visitor short ID |
getWatchLink() |
Future<String?> |
Get a direct watch link for the current session |
sendCustomMessage(String) |
Future<void> |
Send a custom string to all connected agents |
stopRemoteControl() |
Future<void> |
Revoke agent device control (session continues) |
stopFullDeviceSharing() |
Future<void> |
Stop full-device screen sharing |
respondToFullDeviceRequest(String, {required bool accept}) |
Future<void> |
Approve/decline a full-device sharing request |
respondToControlRequest(String, {required bool accept}) |
Future<void> |
Approve/decline a remote-control request |
Upscope.instance — streams #
| Stream | Type | Emits |
|---|---|---|
connectionState |
Stream<ConnectionState> |
Connection state changes |
sessionState |
Stream<SessionState> |
Session state changes |
lookupCode |
Stream<String?> |
Current lookup code (refreshes periodically) |
shortId |
Stream<String?> |
Persistent visitor short ID |
onSessionStarted |
Stream<String?> |
Session ID when a session begins |
onSessionEnded |
Stream<SessionEndReason> |
Reason the session ended |
onViewerJoined |
Stream<Viewer> |
Agent joined the session |
onViewerLeft |
Stream<String> |
Agent ID when an agent leaves |
onViewerCountChanged |
Stream<int> |
Current number of watching agents |
onCustomMessageReceived |
Stream<CustomMessage> |
Custom message from an agent |
remoteControlState |
Stream<RemoteControlState> |
Agent device-control state changes |
fullDeviceSharingState |
Stream<FullDeviceSharingState> |
Full-device sharing state changes |
onFullDeviceRequest |
Stream<FullDeviceRequest> |
Request ID + agent name when an agent asks for full device |
onControlRequest |
Stream<ControlRequest> |
Request ID + agent name when an agent asks for control |
onError |
Stream<UpscopeError> |
SDK-level errors |
Enums #
ConnectionState — inactive, connecting, connected, reconnecting, error
SessionState — inactive, pendingRequest, active, paused, ended
SessionEndReason — userStopped, agentStopped, timeout, error
RemoteControlState — inactive, pendingRequest, active
FullDeviceSharingState — inactive, pendingRequest, active
Widgets #
UpscopeMasked({required Widget child}) — Wraps a child and masks its screen region during cobrowsing. Handles position tracking, rotation, and scroll automatically. No configuration needed.
Troubleshooting #
Sessions never start / connection stays inactive
- Confirm
autoConnect: true(default) or that you calledconnect()explicitly. - Verify the API key is correct for your Upscope account.
- Check that the device has internet access.
Streams receive no events
- Subscribe to streams before calling
initialize, or use aStreamControllerwithsync: falseto avoid missing the first emission.
Masked widget position is wrong after navigation
UpscopeMaskedtracks metrics changes and scroll, but not route transitions. If you push/pop routes while a masked widget is off-screen, the rect will be corrected on the next frame when the widget is visible again. This is typically transparent to the agent.
iOS build fails with missing plugin
- Ensure you have run
pod install(orflutter pub getwhich runs it for you). - The plugin class is
UpscopePluginregistered under theio.upscope.flutternamespace.
Android build fails
- Minimum supported API is 26. Check
minSdkin your app-levelbuild.gradle.
Support #
- Documentation: Flutter Docs
- Email: team@upscope.io
License #
Proprietary. See your agreement with Upscope for details.