TIL

(22.02.13) Flutter 프로젝트에 Google Map API 연동 (사진 짱 많음)

도깨비젤리 2022. 2. 13. 14:40
작은 지식이라도, 하루에 하나씩.


 

한 줄 요약


🦄 잘 보고 따라 하면 됩니다. 🦄
🦄 플러그인 공식 문서 를 같이 띄워놓고 보시면 더욱 편합니다.🦄

 

 

 

들어가는 말


회사에서 지금 제공 중인 서비스를 모바일 앱으로도 제공하자는 말이 나와, 나에게 플러터를 공부하도록 지시하였다.

 

왜 플러터가 채택되었는지를 설명하자면 너무 길고, 게시글 본연의 목적과 거리가 멀어지므로 생략하겠습니다.여하튼 나는 업무 시간에 플러터를 공부할 수 있는 시간을 부여받았다. 당연히 무작정 시간을 부여받은 건 아니고, 간단한 토이 프로젝트를 완성해 제출하기로 했다.

 

곰곰이 생각해보던 나는 예전에 친구가 심심풀이로 제작한 회사 근처의 식당을 추천해주는 웹 앱을 참조하여 같은 기능의 모바일 앱을 만들기로 하였다.

 

원래는 이 친구와 동일하게 카카오 맵이나 네이버 맵 SDK를 끌어와서 쓰려고 했는데.... 쉽지가 않았다. 모바일 개발에 대한 지식이 전무하다 보니 ( 코틀린 , 스위프트 조차 모른다 ) 플랫폼 별 세팅 없이 쉽게 사용할 수 있는 플러그인을 pub-dev에서 사용하려고 했는데, 카카오 맵 플러그인은 버전이 너무 낮고, 네이버 맵 플러그인은 원작자 부재로 관리가 안되고 있는 상황이라 선뜻 사용하기 망설여졌다. ( 네이버 맵 플러그인은 사용해봤는데 자꾸 arm64 프로세서와 관련된 에러가 뜨는데, 이게 내가 뭘 잘못한 건지, 아니면 플러그인이 문제가 있는 건지 확인할 수도 없는 지식의 부재도 한 몫했다.)

 

결국 구글에서 플러터를 위해 공식적으로 제공하는 google_maps_flutter 플러그인을 사용하기로 했다.

다행스럽게도, 해당 플러그인은 계속해서 업데이트를 하고 있었고, 플러터를 사용한다면 간단하게 이용할 수 있도록 튜토리얼까지 제공하고 있었다.

 

 

...그럼에도 이를 적용하기는 쉽지 않았다. 

 

그래서 직접 머리 박아가며 고군분투했던 여정을 비루한 솜씨나마 남겨보니, 훗날 같은 문제를 겪고 있는 사람들에게 도움이 되었으면 하는 바람입니다.

 

블로그 주인 개발환경

IDE : Intellij IDEA Ultimate
장비 : MacBook Pro (2021) M1 Pro
간식 : 크라운 제과 초코하임 4EA

 

설치하기


$ flutter pub add google_maps_flutter

 

먼저 플러그인을 다운로드 한 다음, pubspec.yaml에 의존성 추가를 해준다.

 

완료되었으면 구글 클라우드 플랫폼 콘솔에 접속하여 API 키를 발급받자.

 

 

⚠️ 미리 알아두기 : 안드로이드 용으로 사용할 키와 아이폰 용으로 사용할 키를 각각 하나씩 받아야 한다. 하나의 키로 안드로이드, 아이폰 모두를 땜빵할 수 없다.

 

 

 

 

콘솔에 접속하면 이런 화면이 보일 텐데, api key를 사용할 프로젝트를 선택한다 ( 빨간 동그라미 부분)

 

나는 이전에 유튜브 api로 장난칠 때 생성한 프로젝트가 있어, 여기서 그대로 사용하기로 했다.

 

프로젝트 구분이 필요한 사람이라면 아예 새로운 프로젝트를 개설하면 되겠다.

 

 

 

이후 drawer에서 API 및 서비스 -> 라이브러리 클릭

 

 

 

그러면 API를 검색할 수 있는 페이지로 넘어간다.

 

여기서 Maps SDK for Android, Maps SDK for iOS를 검색한다

 

 

클릭하여 사용하기 (enable) 상태로 만든 후, 활성화된 관리 버튼을 클릭하여 관리 탭으로 이동한다.

 

** Maps SDK for iOS도 사용하기 상태로 만들어준다.

 

 

 

 

그러면 이런 화면으로 넘어올 텐데, 좌측의 사용자 인증정보를 클릭한다.

 

 

나는 이미 설정을 완료해서 Maps API Key라는 이름을 붙여놓았는데, 처음 이 페이지에 들어오기 되면 아마도 노란색 경고 배지가 붙은  "N개의 api 키 묶음"이라는 이상한 이름의 레코드가 보일 것이다. 뭐 잘못된 게 아니니까 걱정 말기 바란다.

 

각각 새로이 인증 정보를 세팅해줘 사용하자.

 

 

 

 

안드로이드 셋팅


 

 

먼저 안드로이드 키 사용자 인증정보부터 세팅해주자.

 

1. 좌측의 애플리케이션 제한사항에 Android 앱 체크.

2. Android 앱의 사용량 제한에서 항목 추가 버튼 클릭

3. 패키지 이름과 SHA-1 인증서 디지털 지문 등록 후 저장

 

 

 

여기가 정말 중요한 부분이다.

 

패키지 이름은 대충 막 넣는 게 아니다!!!!!

 

 

본인이 프로젝트를 생성할 때 사용한 패키지 이름을 사용해야 한다. 만약 아무 이름이나 넣게 되면 잘 연결해놓고서도 터미널에서 키 인증 내역을 확인할 수 없다며 구글 지도 대신 흰 화면만 볼 것이다.

 

 

 

 

인텔리제이 기준으로, flutter package의 이름은 Organization + '.' + project name이다.

 

위 예시에서 package Name은 com.example.untitled이다.

 

나는 처음 프로젝트 생성할 때 대충 만들어서 패키지 이름이 엄청 이상한데, 이걸 코드 내에서 바꿔보려고 하니까 이상한 에러들이 튀어나오더라고요...

 

교훈 : 처음부터 프로젝트 이름을 잘 짓자.

 

 

 

 

그럼 이제 SHA-1 인증서 디지털 지문을 얻어보자. 각자 운영체제에 맞는 터미널을 실행시킨 후 위 명령어를 실행시킨다.

 

그러면 디지털 지문이 발급되는데, 여기서 SHA-1 : ~~~~ 이라고 적힌 부분을 폼에 복사 붙여 넣기 하면 된다.

 

이후 저장하기를 누르면 안드로이드 key는 설정이 끝난다.

 

 

P.S) API 제한 사항은 필요하다면 넣을 것. 이 키가 불필요하게 다른 키 호출을 하지 않도록 설정해주는 부분인데, 나는 딱히 제약할 필요가 없는 규모의 앱이라 생략했다.

 

 

 

 

 

iOS 세팅


 

 

 

iOS는 번들 ID라는 것 하나만 지정해주면 된다.

 

지금 이 글 쓰다가 본 건데, 이 항목 수정할 때 애초에 “다음 번들 식별자 중 하나가 포함된 iOS 앱의 요청 수락”이라는 말이 나온다.

 

말인즉, 여기서 지정한 번들 ID와 프로젝트의 번들 ID가 다르다면 요청을 거부하겠다는 뜻이다.

 

아무튼, 안드로이드와 마찬가지로 동일한 패키지 네임을 넣어주고 저장한다.

 

이러면 iOS 키 설정도 끝!

 

 

 

 

빌드 설정 수정


 

 

https://pub.dev/packages/google_maps_flutter

 

 

google_maps_flutter | Flutter Package

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

pub.dev

 

다 끝난 지금은 여기서 시키는 대로만 하면 된다...라는 생각이 드는데, 처음 이 튜토리얼을 보고 따라 할 때는 모든 것이 다 낯설고 새로워 전전긍긍했던 과거가 생각났다.

 

화장실 들어갈 때와 나올 때의 스텐스를 동등하게 유지하기 위해서 차근차근 설명해보겠다.

 

 

 

안드로이드


 

 

 

먼저 android/app/build.gradle에서 minSdkVersion을 20 이상을 사용하도록 지정해준다.

 

원래는 16인가로 되어있을 텐데, google map api를 사용하려면 20 버전 이상이 요구된다.

 

공식 문서에는 minSdkVersion만 세팅하면 된다고 하지만, google map platform doc을 보면 추가적으로 다른 값도 설정해주는 것을 확인할 수 있었다. 크게 다른 부분은 없는데, 혹시나 하여 해당 코드를 첨부한다.

 

 

android {
	compileSdkVersion flutter.compileSdkVersion
    
    defaultConfig {
    	minSdkVersion 20
        targetSdkVersion 30
        versionCode flutterVersionCode.toInterger()
        versionName flutterVersionName
   }
 }

 

위와 같이 수정해주자.

 

주석으로 처리된 부분은 무시해라. 보면 localProperties.getProperty라고 적혀있는 게 신기해서 참고용으로 주석 처리한 것이다,

 

값을 사용하기 위해서는 android/local.properties flutter.XXXXX = XX라고 미리 먹여놔야하는데, 봐도 env 파일 쓰는 것과 비슷하다는 느낌이 온다.

 

 

⚠️ 주의점 2가지

  1. Build.gradle 파일이 여러 개다!! android/app/build.gradle 파일을 수정했는지 확인해라
  2. android bracket을 잘 보면 맨 밑 부분에 defaultConfig가 다시 선언되어있는 부분이 보이는데, 만약 이걸 그대로 두면 내가 선언한 부분이 cascade 된다. 삭제해라. 중복되면 에러 뱉을 줄 알았는데, 그게 아니어서 시간 소모가 많이 되었다.

 

 

이후 android/app/src/main/AndroidManifest.xml으로 이동한다.

 

 

 

 

애플리케이션 바로 밑에 meta-data 태그를 삽입해준다.

 

먹칠한 value 부분에는 당근 아까 발급받은 안드로이드 용 key를 입력해준다.

 

아무것도 손대지 않았는데도 빨간 줄이 잔뜩 뜰 텐데, 다행히도 정상입니다.

 

정확히 말하면 정상이 아니라 빨간 줄 켜져도 실행이 된다...라는 건데  짧은 가방끈으로 인해 이걸 설명할 방법이 없습니다. 너그러운 마음으로 넘어가 주시길 바랍니다.

 

여기까지 하면 안드로이드 환경에서는 공식문서에서 제공하는 샘플 코드를 쓸 수 있다.

 

 

 

//main.dart

import 'dart:async';

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Google Maps Demo',
      home: MapSample(),
    );
  }
}

class MapSample extends StatefulWidget {
  @override
  State<MapSample> createState() => MapSampleState();
}

class MapSampleState extends State<MapSample> {
  Completer<GoogleMapController> _controller = Completer();

  static final CameraPosition _kGooglePlex = CameraPosition(
    target: LatLng(37.42796133580664, -122.085749655962),
    zoom: 14.4746,
  );

  static final CameraPosition _kLake = CameraPosition(
      bearing: 192.8334901395799,
      target: LatLng(37.43296265331129, -122.08832357078792),
      tilt: 59.440717697143555,
      zoom: 19.151926040649414);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: GoogleMap(
        mapType: MapType.hybrid,
        initialCameraPosition: _kGooglePlex,
        onMapCreated: (GoogleMapController controller) {
          _controller.complete(controller);
        },
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: _goToTheLake,
        label: Text('To the lake!'),
        icon: Icon(Icons.directions_boat),
      ),
    );
  }

  Future<void> _goToTheLake() async {
    final GoogleMapController controller = await _controller.future;
    controller.animateCamera(CameraUpdate.newCameraPosition(_kLake));
  }
}

 

그리고 실행을 하면....

 

 

 

이런 에러가 날 수 있다.

 

실행 중인 애뮬레이터의 용량이 모자라서 앱을 실행하지 못했다는 흔한 에러인데, 애뮬레이터 용량을 늘리는 것으로 간단히 해결이 가능하다.

 

 

 

애뮬레이터 용량 늘리기


 

 

Tools -> android -> AVD manager 이동

 

 

 

 

등록한 애뮬레이터 우클릭 하고 edit 하여 나온 창에서 show advanced option 선택

 

 

 

 

여기서 Internal Storage를 넉넉하게 늘려주면 된다. 난 한 4096 정도로 늘려줬다,

 

근데 이래도 안된다??

 

이미 애뮬레이터에 설치된 앱이 많아서 용량을 늘려도 실행이 안 되는 것이다.

 

깔끔하게 애뮬에 남아있는 데이터들을 날리면 된다.

 

 

애뮬레이터 선택 창에서 Wipe Data를 선택하면 깔끔하게 해결된다.

 

알아보니 flutter에서 디버그 모드로 빌드된 앱은 원래 용량이 꽤 크다고 한다.

 

그래서 되다가도 안되는 일이 비일비재하다고….

 

 

 

 

그러면 안드로이드에서는 정상적으로 예제 앱이 실행되는 것을 확인할 수 있다.

 

 

 

 

 

iOS


 

iOS는 안드로이드보다 훨씬 간단하다.

 

 

/iOS/Runner/AppDelegate.swift 파일에 위 코드를 붙여 넣고, 앞서 발급받았던 iOS 키를 붙여 넣어주면 된다.

 

이렇게만 하면 끝! 인데…! 난 문제가 하나 더 생겼다!!!!

 

지금 사용하는 컴퓨터가 m1 프로세스를 사용하고 있는데,  빌드에 사용되는 cocoaPod가 M1에서는 그냥 실행이 되지 않는다는 것이다.

 

Searching for inspections failed: undefined method `map' for nil:NilClass

 

이런 에러를 뱉어내는데, 구글링 결과 명령어 몇줄로 간단히 해결되는 문제였다.

 

 

1. ffi를 설치

$sudo arch -x86_64 gem install ffi

 

2. /iOS  폴더로 이동

cd iOS 

 

3. 디펜던시 재설치

Arch -x86_64 pod install

 

 

그리고 다시 run app 하니까 깔끔하게 잘 된다!

 

눈물이 나옵니다 정말로

만약에 여기까지 잘 따라왔는데도 지도가 안 나오고 흰 화면이 뜬다면 터미널을 다시 한번 확인해봐라. 아마도 앞서 말한 bundle ID 오류로 인해 구글 map 연결하지 못한 것임이 분명할 것입니다.

 

 

 

잘 안 되는 부분은 댓글로 알려주시면, 최대한 답변드리겠습니다.