[앱개발]/Flutter, Dart, Figma

[플러터] 위치 기반 앱 만들기

기록자_Recordian 2025. 5. 26. 13:15
728x90
반응형
이전 내용
 

[플러터] 비디오 플레이어 앱 만들기

이전 내용 [플러터] 주사위 앱 만들기이전 내용 [플러터] D-Day 앱 만들기 (feat. 폰트 반영하기, Cupertino 위젯)이전 내용 [플러터] 이미지 롤링 기능 구현하기이전 내용 [플러터] 웹 앱 만들어보기에

puppy-foot-it.tistory.com


위치 기반 앱
1. 사전 작업: 구글 지도 API 키 발급 받기

 

구글 지도를 사용해서 위치 기반 앱을 구현해 본다.

먼저, 구글 지도를 사용하기 위해서는 구글 클라우드 플랫폼에서 구글 지도 API를 발급받아야 한다.

https://cloud.google.com/

 

https://cloud.google.com/

 

cloud.google.com

구글에 로그인 후 콘솔 클릭

프로젝트를 누르고, 새 프로젝트 클릭

 

프로젝트 이름을 입력하고 만들기 클릭

검색 창에 'Maps SDK for' 를 검색하고, 결과에 나온 for Android와 for iOS를 각각 클릭하여

 

[사용] 버튼 클릭 (iOS 도 같은 방법으로)

 

그리고나서, '키 및 사용자 인증 정보' 에서 Maps SDK for Android 를 누른 후 [+ 사용자 인증 정보 만들기] - [API 키] 를 누르면 API가 생성된다.

 

 iOS 도 같은 방식으로 진행하며, API 키는 잘 보관해 둔다.

 


안드로이드 스튜디오 설정

 

앞서 발급받은 API 를 사용하기 위해 pubspec.yaml 에 플러그인을 추가해줘야 한다. dependencies 에 추가해 주면 된다.

dependencies:
  flutter:
    sdk: flutter
  
  cupertino_icons: ^1.0.8
  google_maps_flutter: 2.9.0
  geolocator: 13.0.1

※ geolocator 플러그인: 지리와 관련된 기능을 쉽게 사용할 수 있는 플러그인

  • 위치 서비스 사용 권한 확인 후 권한 요청
  • 현재 GPS 위치가 변경될 때마다 현재 위치의 값을 받을 수 있는 기능 사용
  • 현재 위치와 목적지 간의 거리 계산

https://pub.dev/packages/geolocator

 

geolocator | Flutter package

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

pub.dev

 

그리고 안드로이드 설정을 위해 AndroidManifest.xml 에 권한을 등록하고 발급받은 구글 지도 API 키를 등록해 준다.

- 상세 위치 권한 등록

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

 

- API 키 등록 (Maps SDK for Android)

<meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="YOUR_API_KEY" />

 


앱 구현하기

 

레이아웃은 아래와 같이 3등분으로 분할시킬 예정이다.

  • AppBar: 타이틀 표시
  • Body: 구글 지도 표시
  • Footer: 길 찾기 표시 기능 구현

폴더 구조 및 코드

 

이번 앱은 [screen] 폴더 내에 있는 'home_screen.dart' 파일 내에서 대부분의 코드가 작성되었다.

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

class HomeScreen extends StatelessWidget {
  static final LatLng goalLatLng = LatLng( // 지도 초기화 위도, 경도
    37.5233273,
    126.921252,
  );

  // 목적지 위치 마커 선언
  static final Marker marker = Marker(
    markerId: MarkerId('goal'),
    position: goalLatLng,
  );
  
  // 위치 반경 표시
  static final Circle circle = Circle(
    circleId: CircleId('locationCircle'),
    center: goalLatLng, // 원의 중심이 되는 위치 (위도, 경도 제공)
    fillColor: Colors.blue.withOpacity(0.5), // 원 색상
    radius: 100, // 원의 반지름 (m)
    strokeColor: Colors.blue, // 원 테두리 색
    strokeWidth: 1, // 원 테두리 두께
  );

  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: renderAppBar(),
    body: FutureBuilder<String>(
      future:checkPermission(),
      builder:(context,snapshot) {
        // 로딩 상태
        if (!snapshot.hasData &&
            snapshot.connectionState == ConnectionState.waiting) {
          return Center(
            child: CircularProgressIndicator(),
          );
        }

      // 권한 허가 완료 상태
      if(snapshot.data == '위치 권한이 허가되었습니다'){
        return Column(
          children: [
            Expanded(
              flex: 2,
              child: GoogleMap(
                initialCameraPosition: CameraPosition(
                  target: goalLatLng,
                  zoom: 16, // 확대 정도 (높을수록 확대)
                ),
                myLocationEnabled: true, // 내 위치 지도에 표시
                markers: Set.from([marker]), // Set 로 Marker 제공
                circles: Set.from([circle]), // Set 로 Circle 제공
              ),
            ),
            Expanded(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon( // 시계 아이콘
                    Icons.timelapse_outlined,
                    color: Colors.blue,
                    size: 50.0,
                    ),
                    const SizedBox(height: 20.0),
                    ElevatedButton(
                      onPressed: () async{
                        final curPosition = await Geolocator.getCurrentPosition(); // 현재 위치
                        final distance = Geolocator.distanceBetween(
                          curPosition.latitude, // 현재 위치 위도
                          curPosition.longitude, // 현재 위치 경도
                          goalLatLng.latitude, // 목적지 위치 위도
                          goalLatLng.longitude, // 목적지 위치 경도
                        );
                        
                        bool canCheck =
                            distance < 100; // 100 미터 이내에 있으면 가능
                        
                        showDialog(
                          context: context,
                          builder: (_) {
                            return AlertDialog(
                              title: Text('길 찾기'),
                              
                              // 가능 여부에 따라 다른 메시지 제공
                              content: Text(
                                canCheck? '목적지를 찾으시겠습니까?' : '찾을 수 없는 위치입니다.',
                              ),
                              actions: [
                                TextButton(
                                  // 취소 누르면 false 반환
                                  onPressed: () {
                                    Navigator.of(context).pop(false);
                                  },
                                  child: Text('취소'),
                                ),
                                if (canCheck) // 길 찾기 가능한 상태일 때만 [길 찾기] 버튼 제공
                                  TextButton(
                                    // 길 찾기 누르면 true 반환
                                    onPressed: () {
                                      Navigator.of(context).pop(true);
                                    },
                                    child: Text('길 찾기'),
                                  ),
                              ],
                            );
                          },
                        );
                      },
                      child: Text('길 찾기!'),
                    ),
                  ],
                ),
              ),
            ],
          );
        }
      // 권한 없는 상태
      return Center(
        child: Text(
          snapshot.data.toString(),
      ),
    );
  }
),
    );
  }

  AppBar renderAppBar() {
    // AppBar 구현 함수
    return AppBar(
      centerTitle: true,
      title: Text(
        '길 찾기 앱',
        style: TextStyle(
          color: Colors.blue,
          fontWeight: FontWeight.w700,
        ),
      ),
      backgroundColor: Colors.white,
    );
  }

  Future<String> checkPermission() async {
    final isLocationEnabled = await Geolocator.isLocationServiceEnabled();

    // 위치 서비스 활성화 여부 확인

    if (!isLocationEnabled) { // 위치 서비스 활성화 안 된 경우
      return '위치 서비스를 활성화해주세요';
    }
    // 위치 권한 확인
    LocationPermission checkedPermission = await Geolocator.checkPermission();

    if (checkedPermission == LocationPermission.denied) { // 위치 권한 거절될 경우
      // 위치 권한 요청
      checkedPermission = await Geolocator.requestPermission();

      if (checkedPermission == LocationPermission.denied) {
        return '위치 권한을 허가해주세요';
      }
    }

    // 위치 권한 거절 (앱에서 재요청 불가)
    if (checkedPermission == LocationPermission.deniedForever) {
      return '앱의 위치 권한을 설정에서 허가해주세요';
    }

    // 모든 조건 통과되면 위치 권한 허가 완료
    return '위치 권한이 허가되었습니다.';
  }
}

 

 


앱 실행 테스트

 

◆ 애뮬레이터 현재 위치 설정

애뮬레이터에는 GPS가 없어 현재 위치가 보이지 않으므로, GPS 위치를 임의로 설정해 줘야한다.

애뮬레이터의 오른쪽 점 세개 버튼을 누르면

아래와 같이 Extended Controls가 뜬다.

 

 

 


[참고]

코드팩토리의 플러터 프로그래밍


다음 내용

 

 

728x90
반응형