본문 바로가기

Flutter/04 Widgets

[Flutter] Widgets - Google map 2

이번 카테고리는 google map 을 사용하는 방법에 대해서 알아보겠습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1

소스코드 위치 - https://github.com/mike-bskim/google_map_test/releases/tag/05_search%26model

 

Release 05_search&model · mike-bskim/google_map_test

 

github.com

 

 

  • 새로운 기능 구현 후 리팩토링을 하여 파일구조가 변경되었습니다.

 

 

화면은 아래와 같습니다.

메인화면에서 "Places Nearby" 버튼을 클릭하면 아래와 같은 화면이 나옵니다.

 

 

 

이전 블로그에서는 

"어디에서 볼까?" 를 클릭하여 베이커리, 레스토랑, 카페를 선택하는 기능까지 구현하였고

 

이번 블로그에서는

오늘은 업종(베이커리, 레스토랑, 카페)을 선택한 이후 검색된 결과를 화면에 표시하는 방법에 대해서 알아보겠습니다.

 

먼저 모델을 만들고 해당 기능을 구현하겠습니다.

 

모델링을 쉽게 도와주는 사이트를 이용해서 구현해 보겠습니다. ==> https://app.quicktype.io/

 

Instantly parse JSON in any language | quicktype

 

app.quicktype.io

 

 

final String url =
    '$baseUrl?key=$API_KEY&location=$latitude,$longitude&radius=500&language=ko&keyword=$locationName';

 

baseUrl 을 이용하여 조립한 전체 주소를 크롬에 입력하면 아래처럼 json 타입의 데이터를 얻을수 있다.

 

 

위의 데이터 중에서 results 만 모델로 만들어 보겠습니다. 다음 사이트(https://app.quicktype.io/)를 이용해서 모델링해보겠습니다.  results 데이터는 리스트 형식인데, 모델을 만들때는 첫번째 블럭만 사용하면 됩니다.

1. 첫번째 블록 데이터를 복사하여 사이트의 왼쪽에 붙여넣는다.

2. 모델링 클래스 이름을 입력하고, 언어 타입을 "Dart" 로 변경한다.

3. 오른쪽의 출력된 결과를 dart 파일로 저장한다.

 

 

저장한 결과 파일은 아래와 같다.

 

model_google.dart

 

// To parse this JSON data, do
//
//     final modelGoogle = modelGoogleFromJson(jsonString);

// import 'dart:convert';

// ModelGoogle modelGoogleFromJson(String str) => ModelGoogle.fromJson(json.decode(str));
// String modelGoogleToJson(ModelGoogle data) => json.encode(data.toJson());

class ModelGoogle {

  String businessStatus;
  Geometry geometry;
  String icon;
  String iconBackgroundColor;
  String iconMaskBaseUri;
  String name;
  String placeId;
  PlusCode plusCode;
  double rating;// int => double
  String reference;
  String scope;
  List<String> types;
  int userRatingsTotal;
  String vicinity;

  ModelGoogle({
    required this.businessStatus,
    required this.geometry,
    required this.icon,
    required this.iconBackgroundColor,
    required this.iconMaskBaseUri,
    required this.name,
    required this.placeId,
    required this.plusCode,
    required this.rating,
    required this.reference,
    required this.scope,
    required this.types,
    required this.userRatingsTotal,
    required this.vicinity,
  });

  factory ModelGoogle.fromJson(Map<String, dynamic> json) => ModelGoogle(
    businessStatus: json["business_status"],
    geometry: Geometry.fromJson(json["geometry"]),
    icon: json["icon"],
    iconBackgroundColor: json["icon_background_color"],
    iconMaskBaseUri: json["icon_mask_base_uri"],
    name: json["name"],
    placeId: json["place_id"],
    plusCode: PlusCode.fromJson(json["plus_code"]),
    rating: json["rating"].toDouble(),
    reference: json["reference"],
    scope: json["scope"],
    types: List<String>.from(json["types"].map((x) => x)),
    userRatingsTotal: json["user_ratings_total"],
    vicinity: json["vicinity"],
  );

  Map<String, dynamic> toJson() => {
    "business_status": businessStatus,
    "geometry": geometry.toJson(),
    "icon": icon,
    "icon_background_color": iconBackgroundColor,
    "icon_mask_base_uri": iconMaskBaseUri,
    "name": name,
    "place_id": placeId,
    "plus_code": plusCode.toJson(),
    "rating": rating,
    "reference": reference,
    "scope": scope,
    "types": List<dynamic>.from(types.map((x) => x)),
    "user_ratings_total": userRatingsTotal,
    "vicinity": vicinity,
  };
}

class Geometry {
  Geometry({
    required this.location,
    required this.viewport,
  });

  Location location;
  Viewport viewport;

  factory Geometry.fromJson(Map<String, dynamic> json) => Geometry(
    location: Location.fromJson(json["location"]),
    viewport: Viewport.fromJson(json["viewport"]),
  );

  Map<String, dynamic> toJson() => {
    "location": location.toJson(),
    "viewport": viewport.toJson(),
  };
}

class Location {
  Location({
    required this.lat,
    required this.lng,
  });

  double lat;
  double lng;

  factory Location.fromJson(Map<String, dynamic> json) => Location(
    lat: json["lat"].toDouble(),
    lng: json["lng"].toDouble(),
  );

  Map<String, dynamic> toJson() => {
    "lat": lat,
    "lng": lng,
  };
}

class Viewport {
  Viewport({
    required this.northeast,
    required this.southwest,
  });

  Location northeast;
  Location southwest;

  factory Viewport.fromJson(Map<String, dynamic> json) => Viewport(
    northeast: Location.fromJson(json["northeast"]),
    southwest: Location.fromJson(json["southwest"]),
  );

  Map<String, dynamic> toJson() => {
    "northeast": northeast.toJson(),
    "southwest": southwest.toJson(),
  };
}

class PlusCode {
  PlusCode({
    required this.compoundCode,
    required this.globalCode,
  });

  String compoundCode;
  String globalCode;

  factory PlusCode.fromJson(Map<String, dynamic> json) => PlusCode(
    compoundCode: json["compound_code"],
    globalCode: json["global_code"],
  );

  Map<String, dynamic> toJson() => {
    "compound_code": compoundCode,
    "global_code": globalCode,
  };
}

 

 

안드로이드 스튜디오의 Plugins 중에 "JsonToDart" 도 비슷한 기능을하는데, 결과물이 약간 다르다. 개인적으로는 https://app.quicktype.io/ 사이트를 이용해서 모델링하는것이 좀 더 편한것 같다.

 

 

main.dart 수정 - 리팩토링 후 places_nearby.dart 로 변경됨

 

 

void _submit() {
  if (!_fbKey.currentState!.validate()) {
    return;
  }

  _fbKey.currentState!.save();
  final inputValues = _fbKey.currentState!.value;
  final id = inputValues['placeId'];

  final foundPlace = places.firstWhere(
        (place) => place['id'] == id,
    orElse: () => {}, //'id': 'null', 'placeName': 'null'
  );

  debugPrint(foundPlace.toString());

  if (foundPlace['placeName'] == null) {
    Navigator.of(context).pop();
    return;
  } else {
// 아래 추가된 함수를 호출하는 위치 및 파라미터
    _searchPlaces(
      locationName: foundPlace['placeName']!,
      latitude: 37.53609444,
      longitude: 126.9675222,
    ); //37.498295, 127.026437);
    Navigator.of(context).pop();
  }
}

 

void _searchPlaces({
  required String locationName,
  required double latitude,
  required double longitude,
}) async {
  setState(() {
    _markers.clear();
  });

  //radius=500 500m 이내. 언어 한국어,
  final String url =
      '$baseUrl?key=$API_KEY&location=$latitude,$longitude&radius=500&language=ko&keyword=$locationName';
  debugPrint('url: [$url]');

  final response = await http.get(Uri.parse(url));

  if (response.statusCode == 200) {
    final data = json.decode(response.body);


    if (data['status'] == 'OK') {
      GoogleMapController controller = await _controller.future;
      controller.animateCamera(
        CameraUpdate.newLatLng(
          LatLng(latitude, longitude),
        ),
      );

      setState(() {
        final foundPlaces = data['results'];
        debugPrint(foundPlaces.toString());

        //AddressModel addressModel = AddressModel.fromJson(resp.data['response']);
        //   Results.fromJson(dynamic json) {

        for (int i = 0; i < foundPlaces.length; i++) {
          ModelGoogle googleResults = ModelGoogle.fromJson(foundPlaces[i]);
          // debugPrint('store name: [${googleResults.name}]');
          _markers.add(
            Marker(
              // markerId: MarkerId(foundPlaces[i]['place_id']),
              markerId: MarkerId(googleResults.placeId),
              position: LatLng(
                googleResults.geometry.location.lat,
                googleResults.geometry.location.lng,
                // foundPlaces[i]['geometry']['location']['lat'],
                // foundPlaces[i]['geometry']['location']['lng'],
              ),
              infoWindow: InfoWindow(
                // title: foundPlaces[i]['name'],
                // snippet: foundPlaces[i]['vicinity'],
                title: googleResults.name,
                snippet: googleResults.vicinity,
              ),
            ),
          );
        }
      });
    }
  } else {
    debugPrint('Fail to fetch place data');
  }
}

 

 

 

 

 

 

[참고자료] 헤비프랜

- https://www.youtube.com/watch?v=7Hlos9a70Hg&list=PLGJ958IePUyBeZRFKmL5NrZrYi0mPnNcq&index=3