특정위치의 위도, 경도부터 100미터 이내에서 출근 체크 기능을 제공하는 앱으로 구글 지도 API를 이용한다.
지리와 관련된 기능을 쉽게 사용할 수 있는 플러그인으로 위치 서비스 사용 권한을 확인하고 요청, GPS 위치가 바뀔 때마다 현재 위칫값을 받을 수 있는 기능, 회사 건물 간의 거리를 계산하는 기능을 가진다.
LocationPermission | 설명 |
---|---|
denied | 거절상태, requestPermission() 함수를 이용해서 권한 요청가능 |
deniedForever | 완전히 거절된 상태, 다시 권한 요청이 불가능 |
whileInUse | 앱 사용중 허가상태 |
always | 허가 상태 |
unableToDetermine | 알 수 없음 |
getPositionStream() 함수를 이용하면 현재 위치가 변경될 때마다 주기적으로 반환받을 수 있다.
Geolocator.getPositionStream().listen((Position position) {
print(position);
});
position 속성
속성 | 설명 |
---|---|
longitude | 경도 |
latitude | 위도 |
timestamp | 위치가 확인된 날짜 및 시간 |
accuracy | 위치 정확도, 특정기기에서는 확인이 불가능 |
speed | 이동 속도, 특정기기에서는 확인 불가능 |
speedAccuracy | 이동 속도 정확도, 특정기기에서 확인 불가능 |
final distance = Geolocator.distanceBetween(
sLat, // 시작점 위도
sLng, // 시작점 경도
eLat, // 끝지점 위도
eLng, // 끝지점 경도
);
pubsepc.yaml에 google_maps_flutter을 입력해 설치해준다.
dependencies:
cupertino_icons: ^1.0.6
flutter:
sdk: flutter
google_maps_flutter: ^2.5.0
구글 지도를 사용하려면 안드로이드와 IOS 모두 네이티브 설정이 필요하다.
android/app/build.gradle파일에서 compileSdk 33, minSdkVersion 20으로 설정해준다.
android {
namespace "com.example.chool_check"
compileSdk 33
ndkVersion flutter.ndkVersion
defaultConfig {
applicationId "com.example.chool_check"
minSdkVersion 20
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
}
android/app/src/main/AndroidManifest.xml 파일에서 상세 위치 권한과 구글지도 API 키를 등록한다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application
android:label="chool_check"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
>
<meta-data android:name="com.google.android.geo.API_KEY" android:value="발급된 API 키 입력" />
</manifest>
ios/Runner/AppDelegate.seift파일에 아래코드를 덮어쓴다.
import UIKit
import Flutter
import GoogleMaps
class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("발근된 API 키 입력")
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
ios/Runner/Info.plist파일을 열고 아래 내용을 추가해준다.
<dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string>위치 정보가 필요합니다.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>위치 정보가 필요합니다.</string>
</dict>
// lib/screen/home_screen.dart
import "package:flutter/material.dart";
import "package:geolocator/geolocator.dart";
import "package:google_maps_flutter/google_maps_flutter.dart";
class HomeScreen extends StatelessWidget {
// 지도 초기화 위치
static final LatLng companyLatLen = LatLng(37.2933, 127.0705);
static final Marker marker =
Marker(markerId: MarkerId("company"), position: companyLatLen);
static final Circle circle = Circle(
circleId: CircleId("choolCheckCircle"),
center: companyLatLen,
fillColor: Colors.blue.withOpacity(0.5),
radius: 100,
strokeColor: Colors.blue,
strokeWidth: 1,
);
const HomeScreen({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: renderAppBar(),
body: FutureBuilder<String>(
future: checkPermission(),
builder: (context, snapshot) {
if (!snapshot.hasData &&
snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.data == "위치 권한이 허가 되었습니다.") {
return (Column(children: [
Expanded(
flex: 2,
child: GoogleMap(
initialCameraPosition:
CameraPosition(target: companyLatLen, zoom: 16),
// 내 위치 지도에 보여주기
myLocationEnabled: true,
// 마커표시
markers: Set.from([marker]),
circles: Set.from([circle]),
)),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const 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,
companyLatLen.latitude,
companyLatLen.longitude);
bool canCheck = distance < 100;
showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: Text("출근하기"),
content: Text(canCheck
? "출근을 하시겠습니까?"
: "출근할 수 없는 위치입니다."),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text("취소")),
if (canCheck)
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text("출근하기"))
],
);
});
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0))),
child: const Text(
"출근하기!",
style: TextStyle(color: Colors.white),
))
],
))
]));
}
return Center(
child: Text(snapshot.data.toString()),
);
},
),
);
}
AppBar renderAppBar() {
return AppBar(
centerTitle: true,
title: Text("오늘도 출첵",
style: TextStyle(color: Colors.blue, fontWeight: FontWeight.w700)),
backgroundColor: Colors.white,
);
}
Future<String> checkPermission() async {
// 위치 서비스 활성화 여부 확인
final isLocationEnalbed = await Geolocator.isLocationServiceEnabled();
if (!isLocationEnalbed) {
return "위치 서비스를 활성화해주세요.";
}
// 위치권한 확인
LocationPermission checkPermission = await Geolocator.checkPermission();
if (checkPermission == LocationPermission.denied) {
checkPermission = await Geolocator.requestPermission();
if (checkPermission == LocationPermission.denied) {
return "위치 권한을 허가해주세요.";
}
}
if (checkPermission == LocationPermission.deniedForever) {
return "앱의 위치 권한을 설정에서 허가해주세요.";
}
return "위치 권한이 허가 되었습니다.";
}
}