⭐️ 개발/플러터

[프로젝트] - 출근 기록

짱구러버 2022. 12. 16. 19:47
728x90

들어가기 앞서...

구글 맵과 geolocator 플러그인을 통해서 회사의 위치와 나의 위치를 알고 어떤 조건에 맞으면 출근이 가능하게끔 하는 프로젝트이다.


본문으로...

1.  기본 세팅!

 

geolocator | Flutter Package

Geolocation plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API for generic location (GPS etc.) functions.

pub.dev

 

 

google_maps_flutter | Flutter Package

A Flutter plugin for integrating Google Maps in iOS and Android applications.

pub.dev

1) 구글 맵 api readme 따라 해주자!

홈페이지 따라가서 계정 설정 다한 후에 사용 설정된 api 에 저 2개 sdk 가 잘 설정 되어있는지 확인!

그리고 api key 복사 하기

안드로이드 세팅

1) android > app > src > build.gradle

android {
    defaultConfig {
        minSdkVersion 20
    }
}

 

2) androif > app > src> main > AndroidManifest.xml

<manifest ...
  <application ...
    <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR KEY HERE"/>

3)  ios/Runner/AppDelegate.swift

import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GMSServices.provideAPIKey("YOUR KEY HERE")
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

depenencies 추가

google_maps_flutter: ^2.2.2
geolocator: ^9.0.2

geolocator 세팅

android 설정!

androidx 는 최근 플러터에서는 자동으로 설정되어있으므로 넘어간다.

현재 지금 필요한 permission 은 ACCESS_FINE_LOCATION 만 복사해서 적용해주자! (AndroidManifext.xml 파일에 적용!)

 

// 현재 정보를 접근이 가능하게 하는 permission (굉장히 디테일한 위치 접근가능)
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
// 조금 더 덜 정확한 위치 
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
// 예시) 자전거 얼마나 탔는지 기록해주는 앱 에서 사용한다. 
// 앱이 백그라운드에 있을때(현재 앱이 실행중이 아니여도) 현재의 위치를 가져올 수 있는 기능 
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

ios 세팅!

안드로이드 유저는 생소하지만, 애플 유저들은 어느 앱을 실행할때마다 권한을 요청하는 부분이 있다.

  1. 앱의 위치 권한을 항상 허용할것인가?
  2. 앱을 사용하는 동안 권한을 허용할것인가? 
  3. 권한을 허용하지 않는다.

라는 3가지 선택사항을 항상 봐왔을 것이다.

// 앱을 사용하는 동안 허용!
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to location when open.</string>
// 항상 허용! 
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to location when in the background.</string>

 

// 만약 앱 실행시 에러가 난다면, cleans 하고 앱을 다시 실행해주면 된다.
flutter clean

 

 

2. 구글 지도를 띄워보자!

1) 구글 샘플을 한번 따라해보자 

샘플을 그대로 복사해서 실행을 해보면, 위도와 경

도에 맞게 지도가 나오고 To the lake! 버튼이 작동되는 것을 볼 수 있다. 

// 컨트롤러 에서 업데이트하면서 반환된 값을 저장할 변수 선언
final Completer<GoogleMapController> _controller =
      Completer<GoogleMapController>();
  // 앱이 처음 실행될때 보여지는 위치를 담는 변수
  static const CameraPosition _kGooglePlex = CameraPosition(
  // 경도와 위도
    target: LatLng(37.42796133580664, -122.085749655962),
  // 줌 레벨 (확대를 어느정도로 할 것인지,숫자가 높을 수록 확대) 
    zoom: 14.4746,
  );
  // 강의 위치를 담는 변수
  static const CameraPosition _kLake = CameraPosition(
      // bearing :카메라의 방위를 뜻한다. 기본적으로 0.0(북쪽)을 가르킨다.90 은 동쪽, 192는 남쪽인데, 살짝 서쪽이다.
      bearing: 192.8334901395799,
      target: LatLng(37.43296265331129, -122.08832357078792),
      tilt: 59.440717697143555,
      zoom: 19.151926040649414);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GoogleMap(
        // MapType 은 enum 열거형으로 작성되어있다. 지도가 보여지는 타입이다. (none, noramal, satellite,terrain, hybrid 가 존재)
        mapType: MapType.hybrid,
        // initialCameraPosition 초기 카메라 위치
        initialCameraPosition: _kGooglePlex,
        // onMapCreated : 구글맵을 위한 구글맵 컨트롤러를 받는데 사용한다.
        onMapCreated: (GoogleMapController controller) {
          _controller.complete(controller);
        },
      ),
      // 오른쪽 하단 모서리에 body 위에 표시되는 버튼이다.
      floatingActionButton: FloatingActionButton.extended(
        // 클릭하면 _goToTheLake 함수 실행
        onPressed: _goToTheLake,
        // 플러팅 버튼에 써져있는 글씨
        label: const Text('To the lake!'),
        // 플러팅 버튼의 아이콘
        icon: const Icon(Icons.directions_boat),
      ),
    );
  }
  // 비동기 함수
  Future<void> _goToTheLake() async {
    // 구글 맵의 컨트롤러 생성
    final GoogleMapController controller = await _controller.future;
    // animateCamera() : 지도 카메라의 위치의 애니메이션 변경을 시작한다.
    // 플랫폼 쪽에 변경이 시작된후 반환된 [Future]가 완료된다.
    // CameraUpdate : 카메라이동을 정의한다. 절대적인 이동과 상대적인 이동을 지원한다.
    // newCameraPosition : 카메라를 지정된 위치로 이동하는 카메라 업데이틑 리턴한다.
    controller.animateCamera(CameraUpdate.newCameraPosition(_kLake));
  }

2) 샘플 코드를 토대로 코딩!

// state 클래스 
// latitude - 위도, longitude - 경도
  // 현재 위치를 저장하는 방법
  // 구글에서 제공하는 클래스 LatLng
  static final LatLng companyLatLng = LatLng(37.518820573402, 126.89986969097);

  // 줌 레벨
  static final CameraPosition initialPosition = CameraPosition(
    target: companyLatLng,
    zoom: 15,
  );



  body: GoogleMap(
    mapType: MapType.normal,
    initialCameraPosition: initialPosition,
  ),

 

 

3. 구글 맵 꾸미기!

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
        title: Text(
          '오늘도 출근',
          style: TextStyle(
            color: Colors.blue,
            fontWeight: FontWeight.w700
          ),
        ),
      ),
      body: Column(
        children: [
          Expanded(
            flex: 2,
            child: GoogleMap(
              // 처음 실행시켰을때 기본 위치
              initialCameraPosition: initialPosition,
              mapType: MapType.normal,
            ),
          ),
          Expanded(child: Text('출근'))
        ],
      ),
    );
  }

3. 코드 정리!

1)  AppBar 분리 

 AppBar renderAppBar(){
    return AppBar(
      backgroundColor: Colors.white,
      title: Text(
        '오늘도 출근',
        style: TextStyle(
            color: Colors.blue,
            fontWeight: FontWeight.w700
        ),
      ),
    );
  }

2) 두개의 위젯을 새로 분리

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: renderAppBar(),
      body: Column(
        children: [
          _CustomGoogleMap(initialPosition: initialPosition,),
          _AttendanceCheckButton(),
        ],
      ),
    );
  }


class _CustomGoogleMap extends StatelessWidget {
  final CameraPosition initialPosition;

  const _CustomGoogleMap({Key? key, required this.initialPosition}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return  Expanded(
      flex: 2,
      child: GoogleMap(
        // 처음 실행시켰을때 기본 위치
        initialCameraPosition: initialPosition,
        mapType: MapType.normal,
      ),
    );
  }
}

class _AttendanceCheckButton extends StatelessWidget {
  const _AttendanceCheckButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return  Expanded(child: Text('출근'));
  }
}

4. 위치 권한 받는 기능 제작!

geolocator 플러그인 을 통해서 권한을 요청할것이다. 

 

1) 함수 생성

Future<String> checkPermission() async {
    // 로케이션 서비스(위치 여부)가 활성화 되어있는지 나타내는 함수, 비동기로 boolean 값 리턴
    final isLocationEnabled = await Geolocator.isLocationServiceEnabled();

    if (isLocationEnabled) {
      return '위치 서비스를 활성화 해주세요.';
    }

    // LocationPermission : enum(열거형)
    LocationPermission checkedPermission = await Geolocator.checkPermission();

    // 만약 권한을 리턴받아 저장한 값이 == denied 라면?
    if (checkedPermission == LocationPermission.denied) {
      // 다시 요청해서 변수에 저장을 하고,
      checkedPermission = await Geolocator.requestPermission();
      // 저장을 한 후에도 변수의 값이 denied 라면 리턴문 출력
      if (checkedPermission == LocationPermission.denied) {
        return '위치 권한을 허가해주세요.';
      }

      // 리턴 받은 값 == deniedForever 이라면? 사용자가 직접 세팅에서 설정해야한다.
      if (checkedPermission == LocationPermission.deniedForever) {
        return '앱의 위치 권한을 세팅에서 허가해주세요.';
      }
    }

    return '위치 권한이 허가되었습니다.';
  }

1-1) LocationPermission 을 자세히 보기!

// 가능한 위치 권한을 나타낸다.
enum LocationPermission {
  // defulat 상태이다.
  // 장치 위치에 액세스할 수 있는 권한이 거부되었다. 앱을 다시 시도해야 한다.
  denied,

  // 장치 위치에 액세스할 수 있는 권한이 영구적으로 거부되었다.
  // 권한 요청, 권한 대화 상자는 사용자가 앱 설정에서 권한을 업데이트할 때까지 표시가 되지 않는다.
  // 플러터에서 권한을 다시 요청할 수가 없다. 그러니 사용자가 직접 설정에서 컨트롤 해줘야한다.
  deniedForever,

  // 앱이 사용중일 떄만 장치 위치에 액세스할 수 있는 권한이 허용된다.
  whileInUse,
  
  // 앱이 백그라운드에서 실행 중일때 장치 위치에 대한 액세스 권한이 허용된다.
  always,

  // 권한 상태를 확인할 수 없다. 
  // geolocator.checkPermission 메소드가 리턴했다. 
  // 권한 API를 구현하지 않는 브라우저인 경우 홈페이지를 참조해라 (https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API))
  unableToDetermine
  // 핸드폰인 경우, 위치 서비스를 허용 안 해주는 휴대폰은 없다.
  // 컴퓨터에서 위치 서비스를 안해주는 것도 있다. 
}

2) 함수 사용

갑자기 에러 발생!

해결 방법은 이 게시글을 확인하자 https://hitang.tistory.com/161

더보기

ERROR:D8: Cannot fit requested classes in a single dex file (# methods: 70980 > 65536)

FutrueBuilder 사용해서 api 통신 전, 후 그리기!

FutrueBuilder<String>(
	future: checkPermission(),
    buildr: (BuildContext context, AsyncSnapshot snapshot) {
		// snapShot 으로 api 통신이 waiting 인지 done인지 알 수 있다.
        // 현재 api 통신이 waiting 이라면, 로딩인디케이터를 통해 로딩 화면 보여주기
        if(snapShot.connectionState == ConectinState.waiting) {
        	return Center(
            	child: CircularProgressIndicator(),
            )
        }
        // snapShot의 data 가 내가 정한 ''과 같을경우 return 문(구글 지도와 현재 나의 위치 마커 등) 출력한다. 
        if(snapShot.data == '위치 권한이 허가되었습니다.') {
        	return ;
        }
        // 만약 둘다 아니라면, snapShot.data 출력
        return Center(
        	child: Text(snapShot.data), 
        )
    }
)

 


 

728x90