본문 바로가기
코딩과 알고리즘

플러터 체험기 4. 초간단 카메라 앱!

코틀린에서 카메라 앱을 만드는 부분을 따라해 본적이 있었는데요.
앱의 기본 카메라 앱을 연결해 쓰는 부분까지는 시도해 성공해 보았지만,
앱에 카메라를 내장해서 띄우는 부분은 복잡한 설정으로 인해 아직 오리무중입니다. 🦆🦆🦆...

그런데 플러터 공식사이트의 샘플들을 이것저것 보다 보니 카메라 앱 샘플이 있지 뭡니까?
따라해보니 글쎄 한번에 성공을 해버리더라는! (*゚ロ゚)

최근 작동 확인은 2024. 2. 18일입니다.

방법도 꽤 간단해서 오늘은 관련 경험을 공유드립니다.
그럼 찬양 유튜브 영상 하나 공유드리니 심심하면 틀어 놓고 렛츠 고우~


샘플 소~우스

우선 플러터 공식 사이트의 관련 예제 URL은 아래와 같은데요.
https://docs.flutter.dev/cookbook/plugins/picture-using-camera

영어로 안내가 되어 있지만 뭐 크레이가 한글로 설명드릴거니 영어울렁증으로 겁내지는 마세요 😅

거두절미하고 소스 코드를 공개하면 아래와 같은데요.
아래 소스를 비주얼 스튜디오 코드에서 바로 실행하면 이상하게 실행이 안됩니다. ●| ̄|_
그것은 설정 2개를 바꿔주어야 하기 때문이지요.
lib\main.dart

import 'dart:async';
import 'dart:io';

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

Future<void> main() async {
  // Ensure that plugin services are initialized so that `availableCameras()`
  // can be called before `runApp()`
  WidgetsFlutterBinding.ensureInitialized();

  // Obtain a list of the available cameras on the device.
  final cameras = await availableCameras();

  // Get a specific camera from the list of available cameras.
  final firstCamera = cameras.first;

  runApp(
    MaterialApp(
      theme: ThemeData.dark(),
      home: TakePictureScreen(
        // Pass the appropriate camera to the TakePictureScreen widget.
        camera: firstCamera,
      ),
    ),
  );
}

// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({
    super.key,
    required this.camera,
  });

  final CameraDescription camera;

  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}

class TakePictureScreenState extends State<TakePictureScreen> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;

  @override
  void initState() {
    super.initState();
    // To display the current output from the Camera,
    // create a CameraController.
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      widget.camera,
      // Define the resolution to use.
      ResolutionPreset.medium,
    );

    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    // Dispose of the controller when the widget is disposed.
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Take a picture')),
      // You must wait until the controller is initialized before displaying the
      // camera preview. Use a FutureBuilder to display a loading spinner until the
      // controller has finished initializing.
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            // If the Future is complete, display the preview.
            return CameraPreview(_controller);
          } else {
            // Otherwise, display a loading indicator.
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        // Provide an onPressed callback.
        onPressed: () async {
          // Take the Picture in a try / catch block. If anything goes wrong,
          // catch the error.
          try {
            // Ensure that the camera is initialized.
            await _initializeControllerFuture;

            // Attempt to take a picture and get the file `image`
            // where it was saved.
            final image = await _controller.takePicture();

            if (!context.mounted) return;

            // If the picture was taken, display it on a new screen.
            await Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => DisplayPictureScreen(
                  // Pass the automatically generated path to
                  // the DisplayPictureScreen widget.
                  imagePath: image.path,
                ),
              ),
            );
          } catch (e) {
            // If an error occurs, log the error to the console.
            print(e);
          }
        },
        child: const Icon(Icons.camera_alt),
      ),
    );
  }
}

// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
  final String imagePath;

  const DisplayPictureScreen({super.key, required this.imagePath});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Display the Picture')),
      // The image is stored as a file on the device. Use the `Image.file`
      // constructor with the given path to display the image.
      body: Image.file(File(imagePath)),
    );
  }
}

설정 변경!

첫번째 설정으로 카메라를 사용하려면 카메라 위젯 기능을  포함해주어야 하는데요.
플러터에서는 pubspec.yaml 파일을 열어 dependencies ( 종속성 ) 에 camera 기능을 추가해 주면 됩니다.
아래와 같이 말이지요. ( 빨간색 부분 )

pubspec.yaml

    :
dependencies:
  camera: ^0.10.5+9
  flutter:
    sdk: flutter
    :

비주얼 스튜디오 코드 화면

안드로이드 스튜디오는 이 밖에서 뭔가 설정이 추가되어야 하나
비주얼 스튜디오 코드에서는 이렇게 수정 후 저장만 하면 알아서 모두 설치해 줍니다.
그래서 비주얼 스튜디오가 아주 마음에 들더라구요 ㅎㅎ ☜(˚▽˚)☞

두번째 설정으로는 안드로이드 sdk 최소버전 설정을 변경해 주어야 하는데요.
이게 중요합니다. 변경하지 않으면 작동이 안되더라구요.

android\app\build.gradle 파일을 연 다음에 minSdkVersion 을 찾아 21로 변경해 주어야 합니다.(빨간색)

    :
minSdkVersion 21
    :

추후 코틀린으로 카메라 기능이 내장된 앱을 다시 도전한다면 이 설정을 한번 확인해봐야겠습니다.
플러터가 마음에 들어 시도할 일은 없을 것 같지만요 :)


결과는?! 두구두구! 잘 된닷!

별 설정 없이 기기 연결해 실행하니, 오우~ 너무 쉽게 성공하는 것을 볼 수 있었습니다.
아래와 같이 앱이 실행되는데 앱에 내장 카메라가 실행되어,
리얼 현실이 화면 안에 막막 보이지 뭡니까? 대~박!  + O +
( 아래 영상은 얼마 전에 구매한 노트로 저작권 문제가 있을까봐 모자이크 처리했습니다 )

촬영 버튼을 터치하니 사진이 떡! 하니 박히더라니 뭡니까?
이거 잘 응용하면 재미 있는걸 만들 수 있겠더군요! +_+


그 밖에..

사실 설정이 하나 더 있는데요. 바로 iOS 설정입니다.
필자는 아이폰과 맥북이 없는 관계로 실험은 못하지만 공식사이트 내용을 해석하면 아래와 같습니다.

On iOS, lines below have to be added inside ios/Runner/Info.plist in order the access the camera and microphone.
iOS 에서는 카메라와 마이크로폰을 사용하기 위해 하단 내용을 ios/Runner/Info.plist 파일에 추가해 주어야 합니다.

<key>NSCameraUsageDescription</key>
<string>Explanation on why the camera access is needed.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Explanation on why the microphone access is needed.</string>

해당 파일에는 <dict> 라는 설정이 있는데 아래와 같이 넣으라는 내용인 것 같습니다.

iOS 에서 성공하시는 분이 계시다면 제보해주시면 감개무량하겠습니다~!!


마무~리

코틀린에서 내장 카메라 앱을 시도해 보았다가 ( 비록 많은 시간을 들이지는 못했지만.. )
쉽게 안 풀려서 어렵네.. 하고 약간 낙심(?)같은 걸 했었는데
플러터에서 이렇게 쉽게 풀리다니! 하고 아주 통쾌한 마음으로 내용 공유 드립니다.

아무쪼록 오늘도 방문해주신 모든 분들께 감사드립니다.
도움 되셨다면 좋아요와 구독해주신다면 사랑해~요! ♥.