androidflutterandroid-camera

In Flutter, how to save a camera image to a folder like DCIM?


Out of my league but I've put together the bones of a camera app (actiually MS Copilot did it).

Edit: A screenie of the app in action: enter image description here

The last issue is to have the photo saved where I want it to go, e.g. "Internal shared storage/DCIM/AppTestPhotos".

The immediately related code:

 floatingActionButton: FloatingActionButton(
    child: const Icon(Icons.camera),
    onPressed: () async {
      final XFile photo = await controller.takePicture();
      String filePath = photo.path;
      print('File path from photo.path: $filePath');

      await Gal.putImage(filePath);
      print('Image saved at: $filePath');
    },
  ),

Using the default path from the XFile, the file path is indicated to be:

/data/user/0/com.example.cam_test_2_one_screen/cache/CAP555419134659270816.jpg ...which shows up in the "Internal shared storage/Pictures" Folder on the phone. I've tried amending the path to get it to show up in "Internal shared storage/DCIM/AppTestPhotos" but with no luck.

enter image description here

enter image description here

Any thoughts about how to get this to work? So close it seems.

Since it's not too long, here's the full main.dart file:

import 'dart:math';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:permission_handler/permission_handler.dart';
import 'package:gal/gal.dart';
import 'package:path/path.dart' as p;

List<CameraDescription> cameras = [];

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  cameras = await availableCameras();
  runApp(const CameraApp());
}

class CameraApp extends StatelessWidget {
  const CameraApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: CameraScreen(),
    );
  }
}

class CameraScreen extends StatefulWidget {
  const CameraScreen({super.key});

  @override
  // ignore: library_private_types_in_public_api
  _CameraScreenState createState() => _CameraScreenState();
}

class _CameraScreenState extends State<CameraScreen> {
  late CameraController controller;
  bool showFocusCircle = false;
  double x = 0;
  double y = 0;

  @override
  void initState() {
    super.initState();
    controller = CameraController(cameras[0], ResolutionPreset.medium);
    controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return Scaffold(
      appBar: AppBar(title: const Text('Take a picture')),
      body: GestureDetector(
        onTapUp: (details) {
          _onTap(details);
        },
        child: Stack(
          children: [
            Center(child: CameraPreview(controller)),
            if (showFocusCircle)
              Positioned(
                top: y - 20,
                left: x - 20,
                child: Container(
                  height: 40,
                  width: 40,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    border: Border.all(color: Colors.white, width: 1.5),
                  ),
                ),
              ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.camera),
        onPressed: () async {
          final XFile photo = await controller.takePicture();
          String filePath = photo.path;
          print('File path from photo.path: $filePath');

          await Gal.putImage(filePath);
          print('Image saved at: $filePath');
        },
      ),
      // floatingActionButton: FloatingActionButton(
      //   child: const Icon(Icons.camera),
      //   onPressed: () async {
      //     final XFile photo = await controller.takePicture();
      //     final Directory? extDir = await getExternalStorageDirectory();
      //     if (extDir != null) {
      //       final String dirPath = '${extDir.path}/DCIM/AppTestPhotos';
      //       await Directory(dirPath).create(recursive: true);
      //       final String filePath = '$dirPath/${DateTime.now()}.jpg';
      //       await photo.saveTo(filePath);
      //       print('Picture saved at: $filePath');
      //     }
      //   },
      // ),

      // floatingActionButton: FloatingActionButton(
      //   child: const Icon(Icons.camera),
      //   onPressed: () async {
      //     final XFile photo = await controller.takePicture();
      //     final Directory? extDir = await getExternalStorageDirectory();
      //     if (extDir != null) {
      //       final String dirPath = '${extDir.path}/AppTestPhotos';
      //       await Directory(dirPath).create(recursive: true);
      //       final String filePath = '$dirPath/${DateTime.now()}.jpg';
      //       await photo.saveTo(filePath);
      //       print('Picture saved at: $filePath');
      //     }
      //   },
      // ),

      // floatingActionButton: FloatingActionButton(
      //   child: Icon(Icons.camera),
      //   onPressed: () async {
      //     final XFile photo = await controller.takePicture();
      //     print('Picture saved at: ${photo.path}');
      //   },
      // ),
    );
  }

  Future<void> _onTap(TapUpDetails details) async {
    if (controller.value.isInitialized) {
      showFocusCircle = true;
      x = details.localPosition.dx;
      y = details.localPosition.dy;
      double fullWidth = MediaQuery.of(context).size.width;
      double cameraHeight = fullWidth * controller.value.aspectRatio;
      double xp = x / fullWidth;
      double yp = y / cameraHeight;
      Offset point = Offset(xp, yp);
      await controller.setFocusPoint(point);
      setState(() {
        Future.delayed(const Duration(seconds: 2)).whenComplete(() {
          setState(() {
            showFocusCircle = false;
          });
        });
      });
    }
  }
}

Future<void> _requestStoragePermission() async {
  final status = await Permission.storage.request();
  print('Permission status: $status');
}

Solution

  • Here is what I did to save the photo to the DCIM folder on Android

    Here is the result

    enter image description here

    The code snippet

            FloatingActionButton(
              child: const Icon(Icons.camera),
              onPressed: () async {
                final XFile photo = await controller.takePicture();
                Uint8List captureImage = File(photo.path).readAsBytesSync();
    
                String _directory = '';
                if (Platform.isIOS) {
                  _directory = (await getApplicationSupportDirectory()).path;
                } else {
                  /// It might not work with latest Android version
                  /// Check Permission WRITE_EXTERNAL_STORAGE
                  _directory = '/storage/emulated/0/DCIM';
                }
                final path = '$_directory/captured${const Uuid().v1()}.png';
                final imagePath = await File(path).create();
                await imagePath.writeAsBytes(captureImage);
    
                await Gal.putImage(path);
                print('Image saved at: $path');
      
              },
            ),