I am trying to make an error reporting system where when a user presses a button it takes a snapshot of the screen and then allows them to send it. I am using the Screenshot package and managed to get it working by creating a Scaffold Wrapper and wrapping the main Scaffold with the screenshot.
The problem I am running into is that it works but it come up with he error of Duplicate Global Keys. The problem to solving this is to make the Screenshot controller local to the Widget/page as found here...
(Flutter error "Multiple widgets used the same GlobalKey" - using ScreenShot with SocialShare plugins)
...however when dealing with multiple pages I do not want to have to create a controller for each page.
I have tried placing the controller within a Provider which works but still has the some problem of duplicate Global Keys.
The next thing I have been looking into trying is making it so that the Screenshot is local to one widget but the Provider can send a notification to that widget and then the widget takes the image and sends it back to the provider. However after many attempts I have been unable to find a way to do this. I am pretty new to flutter around 2-3 months so forgive me if I'm using the wrong terminology. Let me know of any more information you may need.
Below is an example of the code
Here is the Scaffold Wrapper
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:screenshot/screenshot.dart';
import '../repository/constants.dart';
class ScaffoldWrapper extends StatefulWidget {
final Widget scaffold;
Function()? testFunction1;
ScaffoldWrapper({super.key, required this.scaffold, this.testFunction1});
static const redColor = Color.fromARGB(255, 213, 115, 57);
static const textColor = Color(0xff483538);
static const buttonColor = Color(0xff43060f);
static const bgColor = Color(0xff0A0B10);
static const bgGradientColor = Color.fromARGB(255, 213, 115, 57);
@override
State<ScaffoldWrapper> createState() => _ScaffoldWrapperState();
}
class _ScaffoldWrapperState extends State<ScaffoldWrapper> {
@override
@override
initState() {
super.initState();
}
bool devMode = true;
@override
Widget build(BuildContext context) {
return Screenshot(
controller: screenshotController,
child: Scaffold(
backgroundColor: ScaffoldWrapper.bgColor,
appBar: devMode
? AppBar(actions: [
ElevatedButton(
onPressed: () => widget.testFunction1!(),
child: const Text("Test Button 1")),
ElevatedButton(
onPressed: () {
Hive.box("USER_PREFERENCES").clear();
Hive.box("USER_DATA").clear();
},
child: const Text("Clear Data")),
ElevatedButton(
onPressed: () {Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ShowScreenshot()));},
child: const Text("Trigger Screenshot"))
])
: null,
extendBody: true,
body: Stack(
children: [
dradient(-1.0, -1.0),
dradient(1, 0),
Positioned(
child: widget.scaffold,
)
],
),
),
);
}
Container dradient(double x, double y) => Container(
decoration: BoxDecoration(
gradient: RadialGradient(
colors: [
ScaffoldWrapper.bgGradientColor.withOpacity(0.2),
ScaffoldWrapper.bgGradientColor.withOpacity(0)
],
radius: (0.8),
stops: const <double>[0, 1],
center: Alignment(x, y)),
),
);
}
Constant File Contains the Controller
ScreenshotController screenshotController = ScreenshotController();
Example of Widget using the Scaffold Wrapper
import 'package:flutter/material.dart';
import 'package:teacher_app_design/repository/constants.dart';
import 'package:teacher_app_design/widgets/custom_scaffold_wrapper.dart';
class ShowScreenshot extends StatefulWidget {
const ShowScreenshot({super.key});
@override
State<ShowScreenshot> createState() => _ShowScreenshotState();
}
class _ShowScreenshotState extends State<ShowScreenshot> {
@override
var screenshot;
@override
Widget build(BuildContext context) {
return ScaffoldWrapper(
scaffold: Scaffold(
body: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
const Center(
child: Text("Take Picture of this Page"),
),
ElevatedButton(
onPressed: () async {
await screenshotController.capture().then((value) {
screenshot = value;
});
showGeneralDialog(
context: context,
pageBuilder: (context, a1, a2) => Scaffold(
body: Center(
child: screenshot != null
? InteractiveViewer(
child: Image.memory(screenshot))
: Container()),
));
},
child: const Text('Take Screenshot'))
]),
),
);
}
}
Any solutions to this problem would be appreciated.
Managed to find a solution later after going for a break.
I just used the provider method and set in the ScaffoldWrapper to add a Listener. I then made a bool variable in the Provider and made a toggle function.
When the value is toggled it notifies listeners and in the ScaffoldWrapper the listener is set to take a capture of with the local screenShot Controller and send the value to the Provider.screenshot.
I can then access this Provider.screenshot wherever I need it.
No global Key problems!
Changes are in between the ---- lines.
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:provider/provider.dart';
import 'package:screenshot/screenshot.dart';
import 'package:teacher_app_design/providers/screenshot_provider.dart';
class ScaffoldWrapper extends StatefulWidget {
final Widget scaffold;
Function()? testFunction1;
ScaffoldWrapper({super.key, required this.scaffold, this.testFunction1});
static const redColor = Color.fromARGB(255, 213, 115, 57);
static const textColor = Color(0xff483538);
static const buttonColor = Color(0xff43060f);
static const bgColor = Color(0xff0A0B10);
static const bgGradientColor = Color.fromARGB(255, 213, 115, 57);
@override
State<ScaffoldWrapper> createState() => _ScaffoldWrapperState();
}
class _ScaffoldWrapperState extends State<ScaffoldWrapper> {
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
@override
initState() {
super.initState();
}
bool devMode = true;
ScreenshotController screenshotController = ScreenshotController();
----------------------------------------------------
@override
Widget build(BuildContext context) {
Provider.of<ScreenshotProvider>(context).addListener(
() => screenshotController.capture().then((value) {
print(value);
Provider.of<ScreenshotProvider>(context, listen: false).screenshot =
value;
}),
);
-------------------------------------------------------
return Screenshot(
controller: screenshotController,
child: Scaffold(
backgroundColor: ScaffoldWrapper.bgColor,
appBar: devMode
? AppBar(actions: [
ElevatedButton(
onPressed: () => widget.testFunction1!(),
child: const Text("Test Button 1")),
ElevatedButton(
onPressed: () {
Hive.box("USER_PREFERENCES").clear();
Hive.box("USER_DATA").clear();
},
child: const Text("Clear Data")),
ElevatedButton(
onPressed: () async {
screenshotController.capture().then((value) {
print(value);
});
print(Provider.of<ScreenshotProvider>(context,
listen: false)
.screenshot);
},
child: const Text("Trigger Screenshot"))
])
: null,
extendBody: true,
body: Stack(
children: [
dradient(-1.0, -1.0),
dradient(1, 0),
Positioned(
child: widget.scaffold,
)
],
),
),
);
}
Container dradient(double x, double y) => Container(
decoration: BoxDecoration(
gradient: RadialGradient(
colors: [
ScaffoldWrapper.bgGradientColor.withOpacity(0.2),
ScaffoldWrapper.bgGradientColor.withOpacity(0)
],
radius: (0.8),
stops: const <double>[0, 1],
center: Alignment(x, y)),
),
);
}
and the Provider.
import 'package:flutter/material.dart';
class ScreenshotProvider extends ChangeNotifier {
var screenshot;
bool screenshotNotifier = false;
toggleScreenshotNotifier() {
screenshotNotifier = !screenshotNotifier;
notifyListeners();
}
}
I know I don't need to toggle a value in the provider as the notifyListener will alert the listener in the ScaffoldWrapper but it will help me to understand what it is doing when I go back to it in the future.