본문 바로가기

Flutter 프로그래밍/플러그인 사용

Flutter 권한 관리 - permission handler

안드로이드 / iOS 앱을 만들다보면 여러가지 권한을 사용해야 할 때가 있다.

카메라, 저장장치, 위치정보, 메세지, 연락처 등등 여러 정보를 기기에서 받아서 활용하는 경우가 많다.

 

만약 적절한 권한이 없는 경우 디버그 모드에서는 동작하는데 컴파일 후 장치에서 실행하면 오류와 함께 종료되는 경우가 많다.

 

다행히도 flutter 에 적절한 패키지가 있다. 패키지가 있을 경우에는 정말 사용하기 편하다.

 

Permission Handler 5.0.1+1

현시점에서 버전은 5.0.1+1 이다. 

pub.dev/packages/permission_handler

 

permission_handler | Flutter Package

Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.

pub.dev

 

사용전 설정

안드로이드 

  • pre 1.12 android project 로 업그레이드 후 사용
  • gladdle.properties 파일에 아래와 같이 추가
android.useAndroidX=true
android.enableJetifier=true
  • 'android/app/build.gradle' 파일에서 android sdk 버전 29로 설정
android {
  compileSdkVersion 28
  ...
}

 

iOS

Info.plist 에 권한 추가

아래 링크에서 모든 종류의 권한 예시 확인 가능

github.com/Baseflow/flutter-permission-handler/blob/develop/permission_handler/example/ios/Runner/Info.plist

 

 

사용법

pubspec.yaml 파일 depency 에 추가해준다.

  permission_handler: ^5.0.1+1

사용하려는 dart 파일에 import 추가 후 사용

import 'package:permission_handler/permission_handler.dart';

 

권한 상태 확인(및 종류)

권한 상태는 undetermined, granted, denied, restricted 가 있으며 안드로이드는 추가로 permanentlyDenied 가 있다.

예를 들어 카메라 권한 상태가 궁금하면 Permission.camera.status 로 확인하면 된다.

var status = await Permission.camera.status;
if (status.isUndetermined) {
  // We didn't ask for permission yet.
}

// You can can also directly ask the permission about its status.
if (await Permission.location.isRestricted) {
  // The OS restricts access, for example because of parental controls.
}

 

권한 요청

아래와 같이 Permission.contacts.request() 로 요청하면 된다. 

return 값은 undetermined, granted, denied, restricted 와 같은 값이다.

if (await Permission.contacts.request().isGranted) {
  // Either the permission was already granted before or the user just granted it.
}

// You can request multiple permissions at once.
Map<Permission, PermissionStatus> statuses = await [
  Permission.location,
  Permission.storage,
].request();
print(statuses[Permission.location]);

 

 

사용예시

실제로 사용할 때는 await 가 있기 때문에 async 로 감싸진 클래스(또는 함수) 에서 다뤄야 한다.

위젯 생명주기중 하나인 initState 에서는 Future 클래스가 호출이 안된다. 

이럴때는 WidgetsBinding.instance.addPostFrameCallback((_) { 요기 }  에다가 넣어주면 된다.

 

아래와 같은 함수를 만들어놓고 불러오면 된다.

여기서 카메라 권한 확인용으로 만든 camPermissionIsGranted 변수를 사용했다.

처음엔 false 로 사용 불가능으로 해놓고 권한이 확인되면 true 로 변경하자.

 

bool camPermissionsGranted = false;

Future<bool> CamPermissionIsGranted() async {
  var _status = await Permission.camera.status.isGranted;
  camPermissionsGranted = _status;
  return _status;
}

 

 

권한이 있을때와 없을때 widget 을 어떻게 구현하는지가 처음에 햇갈렸다.

FutureBuilder로 구현해봤는데 뭔가 배보다 배꼽이 큰거같아서 다시 검색해보니 더 쉽고 깔끔한 방법이 있다.

 

예를들어 Scaffold의 body 에 구현한다고 하면 아래 ? : 연산자를 쓰면 쉽다.

그리고 권한 확인이 되면 camPermissionIsGranted 의 값을 바꾸고 동시에 setState로 위젯을 다시 build 하면 된다.

(조건) ? (true인 경우 실행) : (false인 경우 실행) 

return Scaffold(
  body: camPermissionsGranted 
          ? Container(child: 퍼미션 있을경우 실행)
          : Container(child: 퍼미션 없을 경우 실행)

 

아래 예제 맨 아랫줄에서 보면 setState까지 구현된 권한 요청 함수를 볼 수 있다.

import 'dart:io';

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

void main() => runApp(MyApp());

/// Example Flutter Application demonstrating the functionality of the
/// Permission Handler plugin.
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
          actions: <Widget>[
            IconButton(
              icon: const Icon(Icons.settings),
              onPressed: () async {
                var hasOpened = openAppSettings();
                debugPrint('App Settings opened: ' + hasOpened.toString());
              },
            )
          ],
        ),
        body: Center(
          child: ListView(
              children: Permission.values
                  .where((Permission permission) {
                    if (Platform.isIOS) {
                      return permission != Permission.unknown &&
                          permission != Permission.sms &&
                          //permission != Permission.storage &&
                          permission != Permission.ignoreBatteryOptimizations &&
                          permission != Permission.accessMediaLocation;
                    } else {
                      return permission != Permission.unknown &&
                          permission != Permission.mediaLibrary &&
                          permission != Permission.photos &&
                          permission != Permission.reminders;
                    }
                  })
                  .map((permission) => PermissionWidget(permission))
                  .toList()),
        ),
      ),
    );
  }
}

/// Permission widget which displays a permission and allows users to request
/// the permissions.
class PermissionWidget extends StatefulWidget {
  /// Constructs a [PermissionWidget] for the supplied [Permission].
  const PermissionWidget(this._permission);

  final Permission _permission;

  @override
  _PermissionState createState() => _PermissionState(_permission);
}

class _PermissionState extends State<PermissionWidget> {
  _PermissionState(this._permission);

  final Permission _permission;
  PermissionStatus _permissionStatus = PermissionStatus.undetermined;

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

    _listenForPermissionStatus();
  }

  void _listenForPermissionStatus() async {
    final status = await _permission.status;
    setState(() => _permissionStatus = status);
  }

  Color getPermissionColor() {
    switch (_permissionStatus) {
      case PermissionStatus.denied:
        return Colors.red;
      case PermissionStatus.granted:
        return Colors.green;
      default:
        return Colors.grey;
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(_permission.toString()),
      subtitle: Text(
        _permissionStatus.toString(),
        style: TextStyle(color: getPermissionColor()),
      ),
      trailing: IconButton(
          icon: const Icon(Icons.info),
          onPressed: () {
            checkServiceStatus(context, _permission);
          }),
      onTap: () {
        requestPermission(_permission);
      },
    );
  }

  void checkServiceStatus(BuildContext context, Permission permission) async {
    Scaffold.of(context).showSnackBar(SnackBar(
      content: Text((await permission.status).toString()),
    ));
  }

  Future<void> requestPermission(Permission permission) async {
    final status = await permission.request();

    setState(() {
      print(status);
      _permissionStatus = status;
      print(_permissionStatus);
    });
  }
}

 

 

추가로 안드로이드의 경우 권한 수락을 완전히 거부할 경우 있는데 그럴때는 앱정보에 들어가서 직접 권한을 줘야 한다.

아래 루틴을 requestPermission 에 넣어주면 좋다.

if (await Permission.speech.isPermanentlyDenied) {
  // 유저가 완전히 권한을 거부하였을 경우 앱설정으로 진입
  // 유저가 알아보기 쉽게 도움말을 띄우면 더 좋음
  openAppSettings();
}

 

이상 끝.