Search code examples
androidflutterdartflutter-layoutscreenshot

Flutter `Screenshot` package: Why isn't the `captureAndSave()` method saving an image to my (Android) device?


Preface:

I'm using the Screenshot package. In this package, there is a method captureAndSave() which saves a widget as an image to a specific location, however, when I call this function, my image is not being saved. Why?

Complete Code Example:

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:screenshot/screenshot.dart';

class QrCodeScreen extends StatefulWidget {
  const QrCodeScreen({Key? key}) : super(key: key);

  @override
  State<QrCodeScreen> createState() => _QrCodeScreenState();
}

class _QrCodeScreenState extends State<QrCodeScreen> {
  final _screenshotController = ScreenshotController();
  Image? image;
  var doesTheImageExist = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          if (image == null)
            TextButton(
              child: Text("Save QR Code"),
              onPressed: () async {
                await _captureAndSaveQRCode();
                image = await _loadImage();
                setState(() {});
              },
            )
          else
            image!,
          Text("Is the QR Code saved to your device? ${doesTheImageExist}"),
          if (image == null)
            Screenshot(
                controller: _screenshotController,
                child: _buildQRImage('_authProvider.user!.uid')),
        ],
      ),
    );
  }

  Widget _buildQRImage(String data) {
    return QrImage(
      data: data,
      size: 250.0,
      gapless: false,
      foregroundColor: Colors.black,
      backgroundColor: Colors.white,
    );
  }

  Future<String> get imagePath async {
    final directory = (await getApplicationDocumentsDirectory()).path;
    return '$directory/qr.png';
  }

  Future<Image> _loadImage() async {
    return imagePath.then((imagePath) => Image.asset(imagePath));
  }

  Future<void> _captureAndSaveQRCode() async {
    final path = await imagePath;
    await _screenshotController.captureAndSave(path);
    // It always returns false, although I'm saving the file using `captureAndSave` .
    doesTheImageExist = File(path).existsSync();
  }
}

The Question:

In the code above, when I click on the TextButton() that says "Save QR Code" it then calls _captureAndSaveQRCode() and _loadImage(). Hence my image should successfully be saved to my (Android) phone. However, I get an error:

Unable to load asset: /data/user/0/com.example.qr/app_flutter/qr.png

enter image description here

Full Traceback:


======== Exception caught by image resource service ================================================
The following assertion was thrown resolving an image codec:
Unable to load asset: /data/user/0/com.example.qr/app_flutter/qr.png

When the exception was thrown, this was the stack: 
#0      PlatformAssetBundle.load (package:flutter/src/services/asset_bundle.dart:237:7)
<asynchronous suspension>
#1      AssetBundleImageProvider._loadAsync (package:flutter/src/painting/image_provider.dart:675:14)
<asynchronous suspension>
Image provider: AssetImage(bundle: null, name: "/data/user/0/com.example.qr/app_flutter/qr.png")
Image key: AssetBundleImageKey(bundle: PlatformAssetBundle#5986d(), name: "/data/user/0/com.example.qr/app_flutter/qr.png", scale: 1.0)
====================================================================================================
  • Why isn't my image being saved to the device when calling _captureAndSaveQRCode()?

Side note:

I recently posted an answer (currently in Bounty) with (almost) the same code as in this question which does work correctly, so, what's the difference?


Solution

  • The problem was that I had an empty setState:

    onPressed: () async {
                    await _captureAndSaveQRCode();
                    image = await _loadImage();
                    setState(() {});
                  },
                )
    

    So to solve the problem, I removed the setState and also got rid of the _loadImage() function. And then updated the image variable within the TextButton():

      TextButton(
                  child: Text("Save QR Code"),
                  onPressed: () async {
                    await _captureAndSaveQRCode();
    
                    setState(() {
                      doesTheImageExist = true;
                      image = image;
                    });
                  },
                )
    

    Complete working example:

    import 'dart:io';
    
    import 'package:flutter/material.dart';
    import 'package:path_provider/path_provider.dart';
    import 'package:qr_flutter/qr_flutter.dart';
    import 'package:screenshot/screenshot.dart';
    
    class QrCodeScreen extends StatefulWidget {
      const QrCodeScreen({Key? key}) : super(key: key);
    
      @override
      State<QrCodeScreen> createState() => _QrCodeScreenState();
    }
    
    class _QrCodeScreenState extends State<QrCodeScreen> {
      final _screenshotController = ScreenshotController();
      Image? image;
      var doesTheImageExist = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              if (image == null)
                TextButton(
                  child: Text("Save QR Code"),
                  onPressed: () async {
                    await _captureAndSaveQRCode();
    
                    setState(() {
                      doesTheImageExist = true;
                      image = image;
                    });
                  },
                )
              else
                image!,
              Row(children: [
                Text('hi'),
                Text("Is the QR Code saved to your device? ${doesTheImageExist}")
              ]),
              if (image == null)
                Screenshot(
                    controller: _screenshotController,
                    child: _buildQRImage('_authProvider.user!.uid')),
            ],
          ),
        );
      }
    
      Widget _buildQRImage(String data) {
        return QrImage(
          data: data,
          size: 250.0,
          gapless: false,
          foregroundColor: Colors.black,
          backgroundColor: Colors.white,
        );
      }
    
      Future<String> get imagePath async {
        final directory = (await getApplicationDocumentsDirectory()).path;
        return '$directory/qr.png';
      }
    
      // Future<Image> _loadImage() async {
      //   return imagePath.then((imagePath) => Image.asset(imagePath));
      // }
    
      Future<void> _captureAndSaveQRCode() async {
        final path = await imagePath;
        await _screenshotController.captureAndSave(path);
        // It always returns false, although I'm saving the file using `captureAndSave` .
        doesTheImageExist = File(path).existsSync();
      }
    }