구글 지도 api 를 사용해보려고 한다. 기본 패키지 설정은 지난번에 진행했었다.
오늘할 것은 구글 지도를 화면에 띄우고 화면을 터치하면 이에 대한 마커를 생성하는 것이다.
지난 시간에 Provider는 상태 관리를 위한 패키지라고 했다. 상태 변화를 감지하여 ui 에 반영하기 위한 클래스인데, 우리는 구글맵에 터치를 하면 해당 위치에 마커를 추가하는 간단한 기능을 넣을 것이기 때문에 상태 관리를 위한 provider 가 필요하다.
오늘은 Provider 중 ChangeNotifierProvider 을 사용할 건데 이는 ChangeNotifier 와 같이 쓰여서 ChangeNotifier 로 상태의 변화를 감지하고 해당 상태를 앱의 다른 파트와 공유할 수 있도록 설계된 클래스이다.
우선 main.dart 에는 다음처럼 작성하였다.
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'Utils/googlemap_marker.dart';
import 'package:sizer/sizer.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return Sizer(
builder: (context, orientation, deviceType) {
return ChangeNotifierProvider(
create: (context) => GoogleMapsMarkerNotifier(),
child: const MaterialApp(
title: 'Google Maps Markers App',
home: GoogleMapsMarkerExample(),
),
);
},
);
}
}
우선 main의
우선 Sizer 객체를 리턴하게 되는데 이 안에는 builder 함수가 정의되어 있다.
builder 함수는 context, orientation, deviceType 세 가지 인자를 받는다. context는 현재 위젯의 위치를 나타내고, orientation과 deviceType은 Sizer 위젯이 제공하는 추가적인 정보로, 화면의 방향(가로/세로)과 디바이스 타입(휴대폰, 태블릿 등)을 나타내게 된다.
Sizer 위젯을 다음처럼 사용하면 반응형 UI 를 만들 수 있다.
builder 함수는 ChangeNotifierProver 객체를 리턴하게 되는데 이 ChangeNotifierProvider 에서는 create 속성을 통해 상태 객체(GoogleMapsMarkerNotifier 객체)를 생성하고, 자식 위젯인 MaterialApp 은 context를 통해 상태 객체에 접근하여 변화를 감지하고 이를 ui 에 반영하게 된다.
컨텍스트의 흐름을 정리하면,
MyApp의 build 메서드는 앱의 루트 컨텍스트를 받아, Sizer 위젯을 반환 -> builder 함수는 새로운 컨텍스트와 함께 화면의 방향(orientation)과 디바이스 유형(deviceType)을 매개변수로 받음 -> Sizer 위젯에 의해 컨텍스트를 통해, GoogleMapsMarkerNotifier는 앱의 상태를 관리하며, 상태 변경 시 앱의 다른 부분에 알림 -> ChangeNotifierProvider의 child로 MaterialApp 위젯이 위치
컨텍스트의 전달: 컨텍스트는 위젯 트리를 통해 위에서 아래로 전달된다. 각 위젯은 자신의 build 메서드에서 새로운 컨텍스트를 생성하며, 이 컨텍스트는 하위 위젯에 전달되어 앱의 다른 부분에서 사용될 수 있습니다. 이러한 방식으로, 위젯들은 앱의 전체 구조 내에서 자신의 위치와 관계를 알 수 있으며, 필요한 데이터와 함수에 접근할 수 있습니다.
다음은 googlemap_marker.dart 파일을 보면, GoogleMapsMarkerExample 클래스는 ui 를 짜기 위한 클래스이고, GoogleMapsMarkerNotifier 은 만약 사용자가 터치한다면 marker 를 추가하기 위한 클래스이다.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:provider/provider.dart';
import 'package:sizer/sizer.dart';
import '../Style/Color/app_colors.dart';
import '../Style/Text/app_text.dart';
import '../Style/customrounded_btn.dart';
import '../Style/vsized_box.dart';
class GoogleMapsMarkerNotifier extends ChangeNotifier {
final List<Marker> _markers = [];
bool isSoloMarker = false;
addNewMarker({required LatLng latLng}) {
if (isSoloMarker) {
_markers.clear();
Marker marker = Marker(
position: LatLng(latLng.latitude, latLng.longitude),
markerId: MarkerId(latLng.toString()));
_markers.add(marker);
notifyListeners();
} else {
Marker marker = Marker(
position: LatLng(latLng.latitude, latLng.longitude),
markerId: MarkerId(latLng.toString()));
_markers.add(marker);
notifyListeners();
}
}
setSoloMarker() {
isSoloMarker = !isSoloMarker;
notifyListeners();
}
clearMarkers() {
_markers.clear();
notifyListeners();
}
}
class GoogleMapsMarkerExample extends StatelessWidget {
const GoogleMapsMarkerExample({Key? key}) : super(key: key);
Widget build(BuildContext context) {
Completer<GoogleMapController> _controller = Completer();
GoogleMapsMarkerNotifier googleMapsMarkerNotifier(
{required bool renderUI}) =>
Provider.of<GoogleMapsMarkerNotifier>(context, listen: renderUI);
return Sizer(builder: ((context, orientation, deviceType) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked,
floatingActionButton: SizedBox(
child: Column(mainAxisAlignment: MainAxisAlignment.end, children: [
CustomRoundedButton(
onTap: () {
googleMapsMarkerNotifier(renderUI: false).setSoloMarker();
},
backgroundColor:
googleMapsMarkerNotifier(renderUI: true).isSoloMarker
? Colors.deepOrangeAccent
: KConstantColors.blueColor,
label: googleMapsMarkerNotifier(renderUI: true).isSoloMarker
? "Single mode"
: "Multiple mode",
height: 5,
width: 40),
vSizedBox1,
if (googleMapsMarkerNotifier(renderUI: true)._markers.isNotEmpty)
CustomRoundedButton(
onTap: () {
googleMapsMarkerNotifier(renderUI: false).clearMarkers();
},
backgroundColor: KConstantColors.bgColorFaint,
label: "Clear markers",
height: 5,
width: 40),
]),
),
appBar: AppBar(
backgroundColor: KConstantColors.bgColorFaint,
title: Text("Google maps markers",
style: KCustomTextStyle.kBold(context, 14))),
backgroundColor: KConstantColors.bgColor,
body: GoogleMap(
mapType: MapType.hybrid,
onTap: (latln) {
googleMapsMarkerNotifier(renderUI: false)
.addNewMarker(latLng: latln);
},
markers: Set<Marker>.from(
googleMapsMarkerNotifier(renderUI: true)._markers.toList()),
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
initialCameraPosition: const CameraPosition(
zoom: 16, target: LatLng(36.3504, 127.3845))),
),
);
}));
}
}
초기 카메라 위치는 대전광역시의 위도와 경도인 LatLng(36.3504, 127.3845)로 설정하였다.
앱 실행 시 초기 위치 대전광역시로 설정된 것 확인 가능
아무 위치 클릭했을 떄 마커 생성 확인