---
title: "Flutter SDK – Relyo"
description: "Add Relyo to a Flutter app: dependency, Relyo.init, RelyoScope, support entry, masking, router and limitations."
lang: en
canonical: https://relyo.de/en/docs/flutter
---

# Flutter apps

This is how you add co-browsing for support to a Flutter app. The Flutter SDK `relyo_flutter` runs on iOS (15 and later), Android (8 and later) and Flutter Web with the same code. It transmits the Flutter UI as a sequence of images: only changed regions are sent, up to 10 images per second. No operating system permission is needed.

## Adding it with a coding agent

Copy this prompt into your coding agent (Claude Code, Cursor, Copilot …) and add your project key if you have one:

```text
Add Relyo co-browsing support to this Flutter app (iOS and Android).
First read the guide https://relyo.de/.well-known/agent-skills/relyo-integrate/SKILL.md
and the Flutter docs https://relyo.de/en/docs/flutter.md, then:

1. Add relyo_flutter as a git dependency in pubspec.yaml (section "Dependency"), pinned to a commit.
2. Set the iOS deployment target to at least 15.0 (Podfile and Xcode project); on Android check the INTERNET permission.
3. Call Relyo.init in main() before runApp: relayUrl 'wss://relyo.de', connect: RelyoConnect.lazy,
   appVersion from the pubspec, projectKey: 'PROJECT_KEY' (leave it out if there is none).
4. Wrap MaterialApp or CupertinoApp in RelyoScope, exactly once.
5. Add a "Support" entry at the end of the settings that calls Relyo.instance.showCode(context).
6. Mask sensitive views (account, health, personal data) with RelyoRedact; password fields are masked automatically.
7. With go_router or another router, connect navigation as described in the section "Navigation".
8. Run flutter analyze and the tests and summarise what changed.
Never start a support session or enter support codes yourself.
```

Then tap "Support" in the app and enter the code on [relyo.de](/en/).

## Dependency

The package is not on pub.dev but in the Relyo repository:

```yaml
# pubspec.yaml
dependencies:
  relyo_flutter:
    git:
      url: https://github.com/justcode049/Relyo.git
      path: packages/flutter-sdk
      ref: main # better: pin a specific commit
```



> The repository is not public yet. Until then, request access by email to [mail@jochen-maurer.com](mailto:mail@jochen-maurer.com?subject=Relyo%20Flutter%20SDK).


Requires Flutter 3.38 or later.

- **iOS:** deployment target 15.0 (`platform :ios, '15.0'` in the `Podfile`).
- **Android:** `android.permission.INTERNET` in the manifest; for a local server via `ws://` during development also `android:usesCleartextTraffic="true"`.

## Setup

```dart
import 'package:relyo_flutter/relyo_flutter.dart';

void main() {
  Relyo.init(
    relayUrl: 'wss://relyo.de',
    projectKey: 'pk_yourapp', // not needed with the open service
    connect: RelyoConnect.lazy,
    appVersion: '1.4.0',
  );
  runApp(RelyoScope(child: const MyApp()));
}
```

`RelyoScope` goes **around** `MaterialApp` or `CupertinoApp` and may only appear once. The SDK dialogs and the status bar live in their own overlay outside the transmitted area and therefore never appear in the supporter's view.

## Support entry

Ideally in the settings, unobtrusively at the end of the list:

```dart
TextButton(
  onPressed: () => Relyo.instance.showCode(context),
  child: const Text('Support'),
)
```

`showCode` shows the code dialog; after that the SDK guides through consent, session and end. During the session it enlarges the app's top inset by the height of the status bar (36 pixels) so the bar does not cover any controls.

## Options

| Parameter     | Default             | Meaning                                                                                                                         |
| ------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `relayUrl`    | –                   | Required. Server address, for example `wss://relyo.de`.                                                                         |
| `projectKey`  | –                   | The app's project key. Without a key, a server with the open service assigns the app to its app ID (bundle ID or package name). |
| `user`        | –                   | Signed-in user, `RelyoUser(id: …, name: …)`. Display only, not verified.                                                        |
| `connect`     | `RelyoConnect.auto` | `auto`: with `user` connect at once, otherwise only on `showCode`; `eager`: always at once; `lazy`: only on `showCode`.         |
| `appVersion`  | `''`                | App version; shown in the console.                                                                                              |
| `fps`         | `5`                 | Target frame rate, 2 to 10. With limited bandwidth the SDK lowers it by itself.                                                 |
| `consentText` | –                   | Your own explanation in the consent dialog; `{agent}` is replaced by the supporter's name.                                      |

Further calls:

```dart
Relyo.instance.identify(const RelyoUser(id: 'u_123', name: 'Max M.')); // after login
Relyo.instance.identify(null); // on logout
Relyo.instance.end(); // end the session, otherwise “Beenden” in the status bar
```

Before the first connection the SDK stores nothing on the device, not even an identifier.

## Masking

Masking happens on the device before any image leaves it:

- **Always:** every field with `obscureText: true`.
- **`RelyoRedact`:** everything inside this widget.

```dart
RelyoRedact(
  child: TextField(decoration: InputDecoration(labelText: 'IBAN')),
)
```

Masked areas are painted black before the image is encoded. If the SDK cannot fully check the screen once, it blacks out the whole image. The SDK rejects remote input into masked fields.

## Navigation

So that the supporter sees where the user is and drawings disappear on page changes:

```dart
MaterialApp(
  navigatorObservers: [RelyoNavigatorObserver()],
  // …
)
```

With **GoRouter**, additionally for changes that do not trigger a navigator push (`go()`, `ShellRoute`):

```dart
final router = GoRouter(observers: [RelyoNavigatorObserver()], routes: [/* … */]);

router.routerDelegate.addListener(() {
  Relyo.instance.reportRoute(router.routerDelegate.currentConfiguration.uri.path);
});
```

## Control

After the second consent, the supporter can tap, swipe and scroll, type text into the focused field and send Enter and Backspace. Any touch of the user's own or a hardware key ends control at once.

## Limitations

- Platform views (WebView, maps, camera) appear as a grey area labelled “Eingebettete Ansicht”.
- System dialogs (permissions, sharing) and the on-screen keyboard are not in the image.
- Remote text only in Flutter text fields (`TextField`, `CupertinoTextField` and anything based on `EditableText`).
- When the app goes to the background, transmission pauses and resumes when the user returns.
- The dialogs are in German only so far.

## Testing

For your own widget tests without a server, `package:relyo_flutter/testing.dart` provides an in-memory test server (`RelyoTesting.install`, `FakeRelyoServer`).
