Skip to content

Commit 7a14338

Browse files
committed
add multi-view troubleshooting guide for sentry dart
1 parent 9619b2e commit 7a14338

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed

docs/platforms/flutter/troubleshooting.mdx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,25 @@ There is an [issue with the Sentry Flutter SDK](https://github.com/getsentry/sen
6060
This is an [issue](https://github.com/flutter/flutter/issues/135245) with Flutter itself and should be fixed in [Flutter 3.15](https://github.com/flutter/flutter/labels/found%20in%20release%3A%203.15).
6161

6262
For prior versions, you can work around this by configuring the SDK only to take screenshots when the app is in the `resumed` state. To do this, set `SentryFlutterOptions.attachScreenshotOnlyWhenResumed` to `true`.
63+
64+
## Using Flutter multi-view for Web
65+
66+
Multi-view embedding was introduced in Flutter 3.24. A short description of this feature can be found [here on Medium](https://medium.com/flutter/whats-new-in-flutter-3-24-6c040f87d1e4).
67+
A more detailed guide about the usage is available in the [Flutter docs](https://docs.flutter.dev/platform-integration/web/embedding-flutter-web).
68+
69+
Using Sentry in a multi-view application is possible, but there are some limitations. To make the [sentry flutter example app](https://github.com/getsentry/sentry-dart/tree/main/flutter/example) `multi-view` ready, you will need to modify some parts.
70+
71+
The `SentryWidget` with the `SentryScreenshotWidget` and the `SentryUserInteractionWidget` do not currently support multiple instances, and cannot be used in a `multi-view` application.
72+
73+
Also note the `NavigatorKey` in the `MaterialApp` widget. This key is a single `GlobalKey` in the `example app` and therefore doesn't support multiple instances. If you rely on this `NavigatorKey` you will need to handle multiple `Keys` and pass them to the appropriate view/instance. For simplicity, we will not use the `NavigatorKey` in the following example.
74+
75+
### Example app as multi-view
76+
77+
Modify your `main.dart` and create the `multi_view_app.dart` file next to your `main.dart` and insert the content from the [docs](https://docs.flutter.dev/platform-integration/web/embedding-flutter-web#handling-view-changes-from-dart).
78+
Modify the `index.html`in the `web` directory and add the `flutter_bootstrap.js`.
79+
80+
The last step is to add `--web-renderer auto` as additional run args.
81+
82+
Now you should be able to see 2 instances of the same application side by side, with different ViewIds in the `AppBar`.
83+
84+
<Include name="troubleshooting/flutter/enable_multi_view.mdx" />
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
```dart {filename:multi_view_app.dart}{tabTitle: multi_view_app.dart}
2+
// multi_view_app.dart
3+
4+
// Copyright 2014 The Flutter Authors. All rights reserved.
5+
// Use of this source code is governed by a BSD-style license that can be
6+
// found in the LICENSE file.
7+
8+
import 'dart:ui' show FlutterView;
9+
import 'package:flutter/widgets.dart';
10+
11+
/// Calls [viewBuilder] for every view added to the app to obtain the widget to
12+
/// render into that view. The current view can be looked up with [View.of].
13+
class MultiViewApp extends StatefulWidget {
14+
const MultiViewApp({super.key, required this.viewBuilder});
15+
16+
final WidgetBuilder viewBuilder;
17+
18+
@override
19+
State<MultiViewApp> createState() => _MultiViewAppState();
20+
}
21+
22+
class _MultiViewAppState extends State<MultiViewApp>
23+
with WidgetsBindingObserver {
24+
@override
25+
void initState() {
26+
super.initState();
27+
WidgetsBinding.instance.addObserver(this);
28+
_updateViews();
29+
}
30+
31+
@override
32+
void didUpdateWidget(MultiViewApp oldWidget) {
33+
super.didUpdateWidget(oldWidget);
34+
// Need to re-evaluate the viewBuilder callback for all views.
35+
_views.clear();
36+
_updateViews();
37+
}
38+
39+
@override
40+
void didChangeMetrics() {
41+
_updateViews();
42+
}
43+
44+
Map<Object, Widget> _views = <Object, Widget>{};
45+
46+
void _updateViews() {
47+
final Map<Object, Widget> newViews = <Object, Widget>{};
48+
for (final FlutterView view
49+
in WidgetsBinding.instance.platformDispatcher.views) {
50+
final Widget viewWidget = _views[view.viewId] ?? _createViewWidget(view);
51+
newViews[view.viewId] = viewWidget;
52+
}
53+
setState(() {
54+
_views = newViews;
55+
});
56+
}
57+
58+
Widget _createViewWidget(FlutterView view) {
59+
return View(
60+
view: view,
61+
child: Builder(
62+
builder: widget.viewBuilder,
63+
),
64+
);
65+
}
66+
67+
@override
68+
void dispose() {
69+
WidgetsBinding.instance.removeObserver(this);
70+
super.dispose();
71+
}
72+
73+
@override
74+
Widget build(BuildContext context) {
75+
return ViewCollection(views: _views.values.toList(growable: false));
76+
}
77+
}
78+
```
79+
80+
```dart {filename:main.dart}{tabTitle: main.dart}
81+
import 'multi_view_app.dart';
82+
import 'package:sentry_flutter/src/integrations/widgets_binding_integration.dart';
83+
84+
...
85+
86+
Future<void> main() async {
87+
await setupSentry(
88+
() => runWidget(
89+
MultiViewApp(
90+
viewBuilder: (BuildContext context) => DefaultAssetBundle(
91+
bundle: SentryAssetBundle(),
92+
child: const MyApp(),
93+
),
94+
),
95+
),
96+
exampleDsn,
97+
);
98+
}
99+
100+
Future<void> setupSentry(
101+
...
102+
}) async {
103+
await SentryFlutter.init(
104+
(options) {
105+
...
106+
final integration = options.integrations
107+
.firstWhere((element) => element is WidgetsBindingIntegration);
108+
options.removeIntegration(integration);
109+
},
110+
// Init your App.
111+
appRunner: appRunner,
112+
);
113+
}
114+
115+
class MyApp extends StatefulWidget {
116+
const MyApp({super.key});
117+
118+
@override
119+
_MyAppState createState() => _MyAppState();
120+
}
121+
122+
class _MyAppState extends State<MyApp> {
123+
@override
124+
Widget build(BuildContext context) {
125+
return feedback.BetterFeedback(
126+
child: ChangeNotifierProvider<ThemeProvider>(
127+
create: (_) => ThemeProvider(),
128+
child: Builder(
129+
builder: (context) => MaterialApp(
130+
navigatorObservers: [
131+
SentryNavigatorObserver(),
132+
],
133+
theme: Provider.of<ThemeProvider>(context).theme,
134+
home: const MainScaffold(),
135+
),
136+
),
137+
),
138+
);
139+
}
140+
}
141+
142+
...
143+
144+
145+
class MainScaffold extends StatelessWidget {
146+
...
147+
148+
@override
149+
Widget build(BuildContext context) {
150+
...
151+
return Scaffold(
152+
appBar: AppBar(
153+
title:
154+
Text('Sentry Flutter Example (ViewId:${View.of(context).viewId})'),
155+
actions: [
156+
...
157+
],
158+
),
159+
body: SingleChildScrollView(
160+
...
161+
),
162+
);
163+
}
164+
165+
...
166+
}
167+
168+
169+
```
170+
171+
```js {filename:flutter_bootstrap.js}{tabTitle: flutter_bootstrap.js}
172+
// flutter_bootstrap.js
173+
{{ flutter_js; }}
174+
{{ flutter_build_config; }}
175+
176+
_flutter.loader.load({
177+
onEntrypointLoaded: async function onEntrypointLoaded(engineInitializer) {
178+
let engine = await engineInitializer.initializeEngine({
179+
multiViewEnabled: true, // Enables embedded mode.
180+
});
181+
let app = await engine.runApp();
182+
// Make this `app` object available to your JS app.
183+
app.addView({ hostElement: document.querySelector("#left") });
184+
app.addView({ hostElement: document.querySelector("#right") });
185+
},
186+
});
187+
```
188+
189+
```html {filename:index.html}{tabTitle: index.html}
190+
<!doctype html>
191+
<html>
192+
...
193+
<body>
194+
<div style="width: 100%; height: 100%; position: absolute;">
195+
<div id="left" style="width: 50%; height: 100%; float: left;"></div>
196+
<div id="right" style="width: 50%; height: 100%; float: left;"></div>
197+
</div>
198+
<!-- This script installs service_worker.js to provide PWA functionality to
199+
application. For more information, see:
200+
https://developers.google.com/web/fundamentals/primers/service-workers -->
201+
<script>
202+
if ("serviceWorker" in navigator) {
203+
window.addEventListener("load", function () {
204+
navigator.serviceWorker.register("flutter_service_worker.js");
205+
});
206+
}
207+
</script>
208+
<script src="main.dart.js" type="application/javascript"></script>
209+
<script
210+
src="flutter_bootstrap.js"
211+
type="application/javascript"
212+
async
213+
></script>
214+
</body>
215+
</html>
216+
```

0 commit comments

Comments
 (0)