planX

[Flutter] google map api 사용 예제 본문

Flutter

[Flutter] google map api 사용 예제

oxx_o 2024. 9. 24. 16:28

오늘은 google map api를 사용한 앱을 만들어 보겠습니다.

일단, api가 사용 가능하다는 가정 하에 포스팅을 진행하겠습니다.

 

만약 내가 google map api가 없는데 하시면 아래 포스팅 보시고

api를 받으신 다음 이 글을 참고해주시면 되겠습니다.

 

[API] Google map api key 받기

📍Google Map API Key 받기준비물 : 구글 계정 1. 구글 클라우드 접속 https://cloud.google.com/ 클라우드 컴퓨팅 서비스 | Google Cloud데이터 관리, 하이브리드 및 멀티 클라우드, AI와 머신러닝 등 Google의 클

oxxo.tistory.com

 

 

1. Flutter 프로젝트 만들기 

📍 터미널에 프로젝트 만들 위치로 이동하고 명령어 입력

flutter create google_map_api

 

 

 

2. VScode로 Flutter 프로젝트 열기 

📍 VScode 열기 -> open -> google_map_api 폴더 열기

 

 

 

3. 패키지 추가 (geolocator, google_maps_flutter) 

📍 Terminal -> New Terminal

📍 아래 명령어 각각 terminal 에 복붙

https://pub.dev/packages/geolocator/install

 

geolocator install | Flutter package

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

pub.dev

flutter pub add geolocator

 

 

https://pub.dev/packages/google_maps_flutter/install

 

google_maps_flutter install | Flutter package

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

pub.dev

flutter pub add google_maps_flutter

 

설치가 제대로 됐다면, pubspec.yaml에서 설치된 것을 확인할 수 있다.

geolocator: ^13.0.1

google_maps_flutter: ^2.9.0

 

 

 

4. 안드로이드 네이티브 설정 

📍 android/app/src/main/AndroidManifest.xml - 구글 api 키 등록

<uses-permission
        android:name="android.permission.ACCESS_FINE_LOCATION"/>
  • Android 앱에서 정확한 위치 정보를 얻기 위해 필요한 권한을 요청하는 선언
  • 이 권한은 GPS와 같은 고정밀 센서를 사용해 사용자의 정확한 위치(수 미터 이내)를 가져올 수 있다.
  • 이 권한이 있어야 앱이 Google Maps 등에서 사용자의 정확한 현재 위치를 표시할 수 있다.
<meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="자신의_API_KEY"/>

 

 

 

 

5. iOS 네이티브 설정 

📍 ios/Runner/AppDelegate.swift - 아래 사진을 참고하여 코드를 수정해주세요.

import GoogleMaps
GMSServices.provideAPIKey("자신의_API_KEY")

 

📍 ios/Runner/Info.plist - 아래 사진을 참고하여 코드를 수정해주세요.

<key>NSLocationWhenInUseUsageDescription</key>
<string>앱 사용 중에 위치 정보를 이용하여 서비스를 제공합니다.</string>
<key>NSLocationWhenAlwaysUsageDescription</key>
<string>앱이 백그라운드에서도 위치 정보를 이용하여 더 나은 서비스를 제공합니다.</string>

 

 

 

7. 추가 설정 

위 설정대로 테스트를 해봤는데 오류가 있네요.

 

 

📍 Xcode - Open Existing Project - 자신의 프로젝트/ios/Runner.xcworkspace를 open해주세요.

 

 

📍 Runner - Runner - General - Minimum Deployments : 14.0 으로 변경

 

 

📍 ios/Podfile 에서 platform의 주석을 해제하고, '14.0'으로 수정

 

 

📍 terminal에 명령어 한 줄씩 입력

flutter clean
flutter pub get
cd ios
pod install

 

테스트 완

 

 

 

8. 예제 내용 설명 

간단한 API 사용 예제이기 때문에 디자인은 최소화하여

📍 AppBar() + 지도 + 현재 위치 버튼

📍 권한을 요청하고 허가 받는 코드

 

 

 

9. 초기 코드 

📍 저도 시작하지 얼마 안 된 쌩초보여서 ! 참고만 해주세요 ! 

📍 코드는 일단, main.dart와 home_screen.dart로 만들어줬어요.

(현재 코드 블럭에 dart언어가 없네요;; ㅋㅋㅋㅋㅋ 이건 후에 제가 다시 설정해볼게요)

 

📍 main.dart

import 'package:flutter/material.dart';
import 'package:google_map_api/home_screen.dart';

void main() {
  runApp(const MaterialApp(
    home: HomeScreen(),
  ));
}

 

 

📍 home_screen.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Google Maps'),
      ),
      body: const Text('hello world'),
    );
  }
}

 

 

📍 iOS simulator

 

 


📍 지도를 추가하는 방법을 몰라서 일단 구글링해서 찾아봤어요.

https://codelabs.developers.google.com/codelabs/google-maps-in-flutter?hl=ko#3

 

Flutter 앱에 Google 지도 추가  |  Google Codelabs

이 Codelab에서는 iOS 및 Android에서 고품질 네이티브 환경을 제작하기 위해 Flutter 모바일 앱 SDK를 사용하여 Google 지도 환경을 빌드합니다.

codelabs.developers.google.com

위에 링크에 제시된 코드에는 late GoogleMapController mapController; 라는게 있는데 이게 뭔지는 잘 모르겠어요. 

꼭 찾아보고 기록해볼게요 :)

 

 

 

위도 경도를 아무거나 해서 설정해봤는데 지도가 뜨긴하네요.

GoogleMap(
        initialCameraPosition: CameraPosition(
          target: _center,
          zoom: 11.0,
        ),

 

 

📍 저는 Column 위젯을 사용해서 아래에 원래 자신의 위치로 돌아가는 버튼을 만드려는데 비슷한 기능을 하는 구글 지도 버튼이 있어요. 그걸 없애줄게요.

myLocationButtonEnabled: false,

 

 

📍 TextButton 만들기

body: Column(
        children: [
          Expanded(
            child: GoogleMap(
              initialCameraPosition: CameraPosition(
                target: _center,
                zoom: 11.0,
              ),
              myLocationButtonEnabled: false,
            ),
          ),
          Expanded(
            child: TextButton(
              onPressed: () {},
              child: const Text('현재위치'),
            ),
          )
        ],
      ),

 

 

📍 지도와 버튼 부분 비율 변경 (지도 flex : 4, 버튼 flex : 1)

body: Column(
        children: [
          Expanded(
            flex: 4,
            child: GoogleMap(
              initialCameraPosition: CameraPosition(
                target: _center,
                zoom: 11.0,
              ),
              myLocationButtonEnabled: false,
            ),
          ),
          Expanded(
            flex: 1,
            child: TextButton(
              onPressed: () {},
              child: const Text('현재위치'),
            ),
          )
        ],
      ),

 

 

📍 버튼을 눌렀을 때 현재 위치로 돌아가는 기능 구현

- 기기의 GPS기능이 켜져있는지 확인

- 앱내 위치 권한 허가확인, 권한이 없다면 요청

- 버튼을 클릭했을 때, 현재 위치가져오기

- 현재 위치를 지도로 보여주기

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geolocator/geolocator.dart'; // 현재 위치 가져오기

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

  @override
  State<HomeScreen> createState() {
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {
  late GoogleMapController _mapController;
  final LatLng _center = const LatLng(34.11, 111.4);

  @override
  void initState() {
    super.initState();
    _checkLocationService(); // 앱 시작 시 GPS 확인
    _checkLocationPermission(); // 앱 시작 시 권한 확인
  }

  void _onMapCreated(GoogleMapController controller) {
    _mapController = controller;
  }

  Future<void> _checkLocationService() async {
    // 위치 서비스가 활성화 되어있는지 확인
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      print('GPS가 꺼져 있습니다. 켜주세요.');
      return;
    }
  }

  Future<void> _checkLocationPermission() async {
    // 위치 권한 확인
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied ||
        permission == LocationPermission.deniedForever) {
      permission = await Geolocator.requestPermission();
    }
    if (permission == LocationPermission.whileInUse ||
        permission == LocationPermission.always) {
      print("위치 권한이 부여되었습니다.");
    } else {
      print("위치 권한이 없습니다.");
    }
  }

  Future<void> _goToCurrentLocation() async {
    Position currentPosition = await Geolocator.getCurrentPosition(
      locationSettings: const LocationSettings(
        accuracy: LocationAccuracy.high,
      ),
    );
    _mapController.animateCamera(
      CameraUpdate.newLatLng(
        LatLng(currentPosition.latitude, currentPosition.longitude),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Google Maps'),
      ),
      body: Column(
        children: [
          Expanded(
            flex: 4,
            child: GoogleMap(
              onMapCreated: _onMapCreated,
              initialCameraPosition: CameraPosition(
                target: _center,
                zoom: 11.0,
              ),
              myLocationButtonEnabled: false,
            ),
          ),
          Expanded(
            flex: 1,
            child: TextButton(
              onPressed: () async {
                await _goToCurrentLocation();
              },
              child: const Text('현재위치'),
            ),
          )
        ],
      ),
    );
  }
}

 

 

📍 수정(1) 

- GPS가 꺼져있다면 권한을 받아올 필요가 없음 -> GPS와 권한 한번에 확인

- Google map zoom 크기 변경 11.0 -> 15.0 

- 사용자에게 메시지 전달을 위해 _showSnackBar 함수 만듦

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

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

  @override
  State<HomeScreen> createState() {
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {
  late GoogleMapController _mapController;
  final LatLng _center = const LatLng(34.11, 111.4);

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

  void _onMapCreated(GoogleMapController controller) {
    _mapController = controller;
  }

  Future<void> _checkLocationServiceAndPermission() async {
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      // 위치 서비스가 비활성화된 경우 사용자에게 안내
      _showSnackBar("GPS가 꺼져 있습니다. 켜주세요.");
      return; // GPS가 꺼져있으면 권한 확인할 필요 없음
    }
    
    // 위치 권한 확인 및 요청
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
      permission = await Geolocator.requestPermission();
    }

    if (permission == LocationPermission.whileInUse || permission == LocationPermission.always) {
      print('위치 권한이 부여되었습니다.');
    } else {
      _showSnackBar('위치 권한이 없습니다.');
    }
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
      ),
    );
  }


  Future<void> _goToCurrentLocation() async {
    Position currentPosition = await Geolocator.getCurrentPosition(
      locationSettings: const LocationSettings(
        accuracy: LocationAccuracy.high,
      ),
    );
    _mapController.animateCamera(
      CameraUpdate.newLatLng(
        LatLng(currentPosition.latitude, currentPosition.longitude),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Google Maps'),
      ),
      body: Column(
        children: [
          Expanded(
            flex: 4,
            child: GoogleMap(
              onMapCreated: _onMapCreated,
              initialCameraPosition: CameraPosition(
                target: _center,
                zoom: 15.0,
              ),
              myLocationButtonEnabled: false,
            ),
          ),
          Expanded(
            flex: 1,
            child: TextButton(
              onPressed: () async {
                await _goToCurrentLocation();
              },
              child: const Text('현재위치'),
            ),
          )
        ],
      ),
    );
  }
}

 

 

📍 수정(2)

- 위치 권한을 거부 했을 때 설정으로 이동

- 위치를 가져오는 과정에서 오류 예외 처리 (try-catch)

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

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

  @override
  State<HomeScreen> createState() {
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {
  late GoogleMapController _mapController;
  final LatLng _center = const LatLng(34.11, 111.4);

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

  void _onMapCreated(GoogleMapController controller) {
    _mapController = controller;
  }

  Future<void> _checkLocationServiceAndPermission() async {
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      // 위치 서비스가 비활성화된 경우 사용자에게 안내
      _showSnackBar("GPS가 꺼져 있습니다. 켜주세요.");
      return; // GPS가 꺼져있으면 권한 확인할 필요 없음
    }

    // 위치 권한 확인 및 요청
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied ||
        permission == LocationPermission.deniedForever) {
      permission = await Geolocator.requestPermission();

      if (permission == LocationPermission.deniedForever) {
        _showSnackBar("위치 권한이 영구적으로 거부되었습니다. 설정에서 권한을 활성화하세요.");
        return;
      }
    }

    if (permission == LocationPermission.whileInUse ||
        permission == LocationPermission.always) {
      print('위치 권한이 부여되었습니다.');
    } else {
      _showSnackBar('위치 권한이 없습니다.');
    }
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        action: SnackBarAction(
            label: '설정으로 가기',
            onPressed: () {
              Geolocator.openAppSettings(); // 앱 설정으로 가기
            }),
      ),
    );
  }


  Future<void> _goToCurrentLocation() async {
    try {
      Position currentPosition = await Geolocator.getCurrentPosition(
        locationSettings: const LocationSettings(
          accuracy: LocationAccuracy.high,
        ),
      );
      _mapController.animateCamera(
        CameraUpdate.newLatLng(
          LatLng(currentPosition.latitude, currentPosition.longitude),
        ),
      );
    } catch (e) {
      _showSnackBar('현재 위치를 가져올 수 없습니다.');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Google Maps'),
      ),
      body: Column(
        children: [
          Expanded(
            flex: 4,
            child: GoogleMap(
              onMapCreated: _onMapCreated,
              initialCameraPosition: CameraPosition(
                target: _center,
                zoom: 15.0,
              ),
              myLocationButtonEnabled: false,
            ),
          ),
          Expanded(
            flex: 1,
            child: TextButton(
              onPressed: () async {
                await _goToCurrentLocation();
              },
              child: const Text('현재위치'),
            ),
          )
        ],
      ),
    );
  }
}

 

 

📍 수정(3)

- 현재 위치 마커 추가 (진짜 중요한 건데 깜빡해서 나중에 넣었어요.)

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geolocator/geolocator.dart'; // 현재 위치 가져오기

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

  @override
  State<HomeScreen> createState() {
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {
  late GoogleMapController _mapController;
  final LatLng _center = const LatLng(34.11, 111.4);

  LatLng? _currentPosition;
  Set<Marker> _markers = {};

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

  void _onMapCreated(GoogleMapController controller) {
    _mapController = controller;
  }

  Future<void> _checkLocationServiceAndPermission() async {
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      // 위치 서비스가 비활성화된 경우 사용자에게 안내
      _showSnackBar("GPS가 꺼져 있습니다. 켜주세요.");
      return; // GPS가 꺼져있으면 권한 확인할 필요 없음
    }

    // 위치 권한 확인 및 요청
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied ||
        permission == LocationPermission.deniedForever) {
      permission = await Geolocator.requestPermission();

      if (permission == LocationPermission.deniedForever) {
        _showSnackBar("위치 권한이 영구적으로 거부되었습니다. 설정에서 권한을 활성화하세요.");
        return;
      }
    }

    if (permission == LocationPermission.whileInUse ||
        permission == LocationPermission.always) {
      print('위치 권한이 부여되었습니다.');
    } else {
      _showSnackBar('위치 권한이 없습니다.');
    }
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        action: SnackBarAction(
            label: '설정으로 가기',
            onPressed: () {
              Geolocator.openAppSettings(); // 앱 설정으로 가기
            }),
      ),
    );
  }

  Future<void> _goToCurrentLocation() async {
    try {
      Position currentPosition = await Geolocator.getCurrentPosition(
        locationSettings: const LocationSettings(
          accuracy: LocationAccuracy.high,
        ),
      );

      // 현재 위치를 변수에 저장하고 상태 업데이트
      setState(() {
        _currentPosition =
            LatLng(currentPosition.latitude, currentPosition.longitude);
        _markers.add(
          Marker(
            markerId: const MarkerId('currentPosition'),
            position: _currentPosition!,
          ),
        );
      });

      _mapController.animateCamera(
        CameraUpdate.newLatLng(
          LatLng(currentPosition.latitude, currentPosition.longitude),
        ),
      );
    } catch (e) {
      _showSnackBar('현재 위치를 가져올 수 없습니다.');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Google Maps'),
      ),
      body: Column(
        children: [
          Expanded(
            flex: 4,
            child: GoogleMap(
              onMapCreated: _onMapCreated,
              initialCameraPosition: CameraPosition(
                target: _center,
                zoom: 15.0,
              ),
              markers: _markers,
              myLocationButtonEnabled: false,
            ),
          ),
          Expanded(
            flex: 1,
            child: TextButton(
              onPressed: () async {
                await _goToCurrentLocation();
              },
              child: const Text('현재위치'),
            ),
          )
        ],
      ),
    );
  }
}

 


우열곡절 끝에 만들어봤구만유 💡

읽어주셔서 감사합니다.