1

I'm encountering a "Duplicate GlobalKey detected in the widget tree" error when using a PageView.builder in Flutter along with the Screenshot widget from the screenshot package. The error occurs when I scroll to the next page in the PageView. I'm not sure how to properly manage the GlobalKey instances to prevent this error. Here's my code setup:

I have a CurrentAffairsScreen widget containing a PageView.builder that renders a list of CurrentAffairsStory widgets, and I want to capture screenshots of the entire screen using the Screenshot widget. The BottomNavBar widget is responsible for capturing and sharing screenshots.

code for CurrentAffairsScreen()

import 'package:flutter/material.dart';

import 'package:provider/provider.dart';
import 'package:screenshot/screenshot.dart';

import '../../home/classes/date.list.class.dart';
import '../../internet.check/wraper/internet.check.wraper.dart';

import '../classes/current.affairs.class.dart';
import '../widgets/bottom.nav.bar.dart';
import '../widgets/current.affairs.story.dart';

class CurrentAffairsScreen extends StatefulWidget {
  const CurrentAffairsScreen({super.key});
  static const routeName = '/current-affairs-screen';

  @override
  State<CurrentAffairsScreen> createState() => _CurrentAffairsScreenState();
}

class _CurrentAffairsScreenState extends State<CurrentAffairsScreen> {
  PageController controller = PageController();
  ScreenshotController screenshotController = ScreenshotController();
  

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final currentAffairsList =
        ModalRoute.of(context)!.settings.arguments as DateList;
   
    final List<CurrentAffairs> currentAffairs =
        currentAffairsList.currentAffairsList;

    return InternetCheckWraper(
      widget: Scaffold(
        appBar: AppBar(
          title: Text(currentAffairsList.date),
        ),
        body: PageView.builder(
                padEnds: true,
                controller: controller,
                physics: const BouncingScrollPhysics(),
                scrollDirection: Axis.vertical,
                itemCount: currentAffairs.length,
                itemBuilder: (_, i) => CurrentAffairsStory(
                  imgUrl: currentAffairs[i].imgUrl,
                  header: currentAffairs[i].header,
                  body: currentAffairs[i].body,
                  source: currentAffairs[i].source,
                  screenshotController: screenshotController,
                ),
              ),
        bottomNavigationBar: BottomNavBar(
              screenshotController: screenshotController,
            ),),
    );
  }
}

While running the above code there is no error in the the first page of pageview.builder() but, when I scroll to next page, I have got the following error in framework.dart

Exception has occurred.

- FlutterError (Duplicate GlobalKey detected in widget tree.
The following GlobalKey was specified multiple times in the widget tree. This will lead to parts of the widget tree being truncated unexpectedly, because the second time a key is seen, the previous instance is moved to the new location. The key was:
- [GlobalKey#9d1f3]
This was determined by noticing that after the widget with the above global key was moved out of its previous parent, that previous parent never updated during this frame, meaning that it either did not update at all or updated before the widget was moved, in either case implying that it still thinks that it should have a child with that global key.
The specific parent that did not update after having one or more children forcibly removed due to GlobalKey reparenting is:
- Screenshot(state: ScreenshotState#55d8f)
A GlobalKey can only be specified on one widget at a time in the widget tree.)

code for CurrentAffairsStory()

import 'package:flutter/material.dart';

import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:screenshot/screenshot.dart';

class CurrentAffairsStory extends StatelessWidget {
  final String header;
  final String body;
  final String source;
  final String imgUrl;
  final ScreenshotController screenshotController;

  const CurrentAffairsStory({
    Key? key,
    required this.header,
    required this.body,
    required this.source,
    required this.imgUrl,
    required this.screenshotController,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Screenshot(
      controller: screenshotController,
      child: Container(
        margin: const EdgeInsets.only(
          left: 10,
          right: 10,
        ),
        child: SingleChildScrollView(
          physics: const NeverScrollableScrollPhysics(),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              if (imgUrl != "noImg" && imgUrl.isNotEmpty)
                Container(
                  padding: const EdgeInsets.all(5),
                  height: MediaQuery.of(context).size.height * 0.25,
                  width: MediaQuery.of(context).size.width - 20,
                  child: Image(
                    fit: BoxFit.fill,
                    image: NetworkImage(imgUrl),
                  ),
                ),
              const SizedBox(
                height: 5,
              ),
              Container(
                padding: const EdgeInsets.all(10),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisAlignment: MainAxisAlignment.start,
                  children: [
                    Text(
                      header,
                      textScaleFactor: 1.5,
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(
                      height: 10,
                    ),
                    MarkdownBody(
                      data: body,
                    ),
                    const SizedBox(
                      height: 10,
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: [
                        Text(
                          source,
                          textScaleFactor: 0.75,
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

code for BottomNavBar()

import 'dart:io';
import 'package:flutter/material.dart';

import 'package:provider/provider.dart';
import 'package:screenshot/screenshot.dart';
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';

import '../functions/get.saved.view.mode.dart';
import '../providers/viewmode.provider.dart';

class BottomNavBar extends StatefulWidget {
  final ScreenshotController screenshotController;
  const BottomNavBar({
    super.key,
    required this.screenshotController,
  });

  @override
  State<BottomNavBar> createState() => _BottomNavBarState();
}

class _BottomNavBarState extends State<BottomNavBar> {

  @override
  Widget build(BuildContext context) {
    
    return SizedBox(
      height: 45,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          IconButton(
            onPressed: () {
              Navigator.of(context).popUntil(
                ModalRoute.withName("/"),
              );
            },
            icon: const Icon(Icons.home),
          ),
            IconButton(
              onPressed: () {
                widget.screenshotController.capture().then(
                  (image) async {
                    if (image != null) {
                      Directory directory =
                          await getApplicationDocumentsDirectory();
                      File imageFile =
                          await File('${directory.path}/.png').create();
                      await imageFile.writeAsBytes(image);
                      XFile imageXFile = XFile(imageFile.path);
                      await Share.shareXFiles(
                        [imageXFile],
                 
                      );
                    }
                  },
                );
              },
              icon: const Icon(Icons.share),
            ),
          
        ],
      ),
    );
  }
}

Additionally, I'm trying to share the captured screenshots using the Share package, but I'm unsure if my approach in the BottomNavBar widget is correct.

Please explain with some examples.
Thanks in advance.

Debabrata Samal
  • 73
  • 1
  • 13
  • a workaround is to remove `Screenshot()` from `CurrentAffairsStory()` widget then wrap body of `CurrentAffairsScreen()` with `Screenshot()` and use the `screenshotController` here. – Debabrata Samal Aug 15 '23 at 06:46

1 Answers1

0

You should give UniqueKey for every widget when you need to change widget position in the view. Flutter engine will know the position of the widget from the key you provided. Here is the example. I do not test your code. I hope it will help.

class CurrentAffairsScreen extends StatefulWidget {
  const CurrentAffairsScreen({super.key});
  static const routeName = '/current-affairs-screen';

  @override
  State<CurrentAffairsScreen> createState() => _CurrentAffairsScreenState();
}

class _CurrentAffairsScreenState extends State<CurrentAffairsScreen> {
  PageController controller = PageController();
  ScreenshotController screenshotController = ScreenshotController();


  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final currentAffairsList =
    ModalRoute.of(context)!.settings.arguments as DateList;

    final List<CurrentAffairs> currentAffairs =
        currentAffairsList.currentAffairsList;

    return InternetCheckWraper(
      widget: Scaffold(
        appBar: AppBar(
          title: Text(currentAffairsList.date),
        ),
        body: PageView.builder(
          padEnds: true,
          controller: controller,
          physics: const BouncingScrollPhysics(),
          scrollDirection: Axis.vertical,
          itemCount: currentAffairs.length,
          itemBuilder: (_, i) => CurrentAffairsStory(
            key: UniqueKey(),// <= Here is the UniqueKey
            imgUrl: currentAffairs[i].imgUrl,
            header: currentAffairs[i].header,
            body: currentAffairs[i].body,
            source: currentAffairs[i].source,
            screenshotController: screenshotController,
          ),
        ),
        bottomNavigationBar: BottomNavBar(
          screenshotController: screenshotController,
        ),),
    );
  }

Share package some issue in iOS iPad so you should give RenderBox to fix it. Also i think you should give a file name and type before you create a file. Here is the example implementation.

Builder(builder: (context) {
      return Padding(
          padding: EdgeInsets.only(right: 2),
          child: IconButton(
              onPressed: () async {
                if (image != null) {
                  final box = context.findRenderObject() as RenderBox?;
                  Directory directory = await getApplicationDocumentsDirectory();
                  File imageFile = await File('${directory.path}/yourFileName.png').create();

                  await imageFile.writeAsBytes(image);
                  XFile imageXFile = XFile(imageFile.path);
                  await Share.shareXFiles(
                    [imageXFile],
                    sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
                  );
                }
              },
              icon: Icon(Icons.share, size: 2.5)));
    })
Çağatay Kaya
  • 417
  • 1
  • 6
  • 19