플러터 공부/프로젝트기록

플러터 - 구글API 지도앱 만들기

Lee_SH 2023. 6. 13. 18:33

초반 세팅

플러그인 설치

  1. https://pub.dev/packages/google_maps_flutter
  2. https://pub.dev/packages/geolocator : 위치 관련된 작업에사용 (ex 현재 위도 경도, 거리)

- android : android/app/src/main/AndroidManifest <manifest … 에 추가 

  • <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"  -> 비교적 정확한 위치를 가져옴 (추가해주기)
  • <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> -> 비교적 덜 정확한 위치 가져옴 (추가할 필요 X)
  • <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> -> 백그라운드에서 앱 구동 가능하게 함 (현 프로젝트에서는 필요 없는 기능)
  • <uses-permission *android:name*="android.permission.INTERNET" /> 인터넷 권한도 추가 -> (개발환경에서는 안해도 되지만 출시할 경우 해줘야함)

- ios : ios/Runner/info.plist <plist><dict>안에 추가

<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>

언제든지 현재 내 위치를 가져올 경우 권한에 대한 메시지

 

AVD 만들 때 구글 플레이 스토어 지원 가능한 에뮬레이터로 만들어야함!

구글지도 생성하기

static final LatLng companyLatLng= LatLng(
  37.5233273,
  126.921252,
);

LatLng : 위도(latitude) , 경도(longitude)를 하나의 클래스로 넣어줄수 있는 타입

static final CameraPosition initialPosition= CameraPosition(
  target:companyLatLng,
  zoom: 15,
);

CameraPosition : 우주에서 지구를 내려다보는 시점

@override
Widget build(BuildContext context) {
return Scaffold(
    body: GoogleMap(
      initialCameraPosition:initialPosition, // 초기 카메라 포지션값
      mapType: MapType.normal,   // 값에 따라 보여지는 지도 타입이 달라짐
			myLocationEnabled: true, // 현재 내위치 출력
      myLocationButtonEnabled: false, // 내 위치로 가기 버튼
),
);

GoogleMap() 으로 지도 생성

권한 관리하기

FutureBuilder 위젯

플러터에서 비동기 처리를 해주기 위한 위젯.

만약 우리가 서버로부터 데이터를 받거나 사용자로부터 응답을 받아야 하는 경우 비동기 처리를 해주지 않으면 해당 데이터나 응답이 들어 올 떄 까지 가만히 기다리게되고 이는 매우 비효율적이다.

따라서 데이터를 모두 다 받기 전에 먼저 데이터가 없이 그릴 수 없는 부분을 먼저 그려주기 위해 FutureBuilder를 사용한다.

future를 넣어주기 전까지는 모두 null인상태

Future<int> getNumber() async {

	await Future.delayed(Duration(seconds: 3));
	
	final random = Random();

	return random.nextInt(100);
}

이렇게 비동기 작업을 위한 타입인 Future 타입으로 함수를 만들고

body: FutureBuilder(
  future: getNumber(),
  builder: (context, snapshot) {
			...
})

FutureBuilder의 future파라미터로 넣어주면

snapshot값이 바뀔때마다 자동적으로 builder함수가 새로 불리기 때문에 새로운 화면을 띄워주게 되고 값들이 바뀐것을 확인 할 수 있다.

 

ConnectionState.none → ConnectionState.waiting(값을 받으면)→ ConnectionState.done

 

만약 FutureBuilder 내부에서 setState를 실행해서 빌더 자체를 다시 시작하면 ConnectionState는 waiting으로 바뀌지만 data는 null이 아닌 원래 값을 그대로 가지고 있다가 ConnectionState가 done이 되서 새로운 값이 들어오면 바뀐다.

 

다시 빌드가 실행되더라도 FutreBuilder가 기존의 데이터를 기억(캐싱)하기 때문에 한번 빌드가 되면 기존 데이터가 유지되는것

사용 코드

 

FutureBuilder위젯을 통해 함수의 상태가 변경될 때 마다 future 파라미터로 넣어준 함수를 다시 실행시켜 화면을 다시 그려 줄 수 있다. 이때 함수는 Future 타입이여야 한다.

이렇게 실행된 함수의 리턴값은 builder의 파라미터로 넣어준 AsyncSnapshot으로 받을수 있다.

snapshot.connectionState와 snapshot.data를 프린트 해보면 checkPermission 함수가 빌더를 통해 재실행되고 그 리턴값인 문자열이 출력되는것과 connetionState가 waiting에서 done으로 바뀐것을 볼 수 있다.

  • ConnetionState
    • none : Future값을 넣지 않은경우
    • waiting : Future가 로딩중인 경우 (값을 못받아서 함수가 계속 실행중)
    • done : 함수가 값을 받고 종료된 경우

StreamBuilder

Stream은 데이터가 들어오고 나가는 통로이다.

데이터의 변화를 listen 하다가 변화가 생기면 그에 맞춰 처리

class _HomeScreenStateextendsState<HomeScreen> {
  @override
  Widget build(BuildContext context) {

return Scaffold(
      body: StreamBuilder(
        stream: streamNumbers(),
        builder: (context, snapshot) {

								...
}
))

FutureBuilder를 StreamBuilder로 바꾸고 future를 stream으로 바꿔서 stream타입 함수를 넣어주면 되는 FutureBuilder와 비슷한 구조

Stream<int> streamNumbers() async * {
		for(int i = 0; i < 10; i++){

		await Future.delayed(Duration(seconds:1));
		
		yield i;
  }
}

Stream타입 함수는 async * 을 사용해서 만들어야 한다.

FutureBuider와 마찮가지로 캐싱을 통해 값을 기억하기 때문에 새로 빌드를 하더라도 기존 값을 유지한다.

원래 Stream은 dispose해주어야하는데 StreamBuilder는 알아서 해주기때문에 할 필요가없다.

자주 사용하는 구조

사용 코드

if(snapshot.data == '위치 권한이 허가되었습니다.') { 
			//위치 권한이 허가된경우 지도 출력
return StreamBuilder<Position>( 
      stream: Geolocator.getPositionStream(),
		// Position이 바뀔때마다 새로운 위치가 yield된다.
		// 그러면 snapshot.data가 바뀌므로 빌더가 재실행 된다.
builder: (context, snapshot) {
						
			...
						
      });
}

AlertDialog

정형화된 Dialog를 쉽게 만들 수 있는 위젯

onChoolCheckPressed()async{
		await showDialog(
    context: context,
    builder: (BuildContext context) {

	return AlertDialog(
		//다이얼로그를 쉽게 만들수 있는 위젯
		title: Text('출근하기'),
    content: Text('출근을 하시겠습니까?'),//내용
		actions: [
					//실제로 선택할 수 있는 버튼
			TextButton(
			              onPressed: () {},
			              child: Text('취소'),
			            ),
			            TextButton(
			              onPressed: () {},
			              child: Text('출근하기'),
			            ),
			          ]);
			    },
  );
}

 

출처 - 코드팩토리 인프런 인강 

https://www.inflearn.com/course/%ED%94%8C%EB%9F%AC%ED%84%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8