zlyt
zlyt

Reputation: 269

How to request Place Details from Google Places API using place_id in Swift?

Im creating an app that needs to be able to determine what locations are in a given area and then retrieve details about those locations, which then will be displayed. To do this I've been using Google Places API, but I am having trouble retrieving place details for locations using their place_id.

At the moment I am able to successfully perform a Google Places 'Nearby Search' request which returns many locations and some basic data about them (including a place_id). What I'm having trouble with is using the place-id's I receive from the first request to make a 'Place Details' request to get all the information Google has about the respective location.

vv My code below details my current attempt vv

function where I call the 2 requests:

    func requestAndCombineGData(location: CLLocation, radius: Int) {

        self.mapView.clear()

        // Calls 'Nearby Search' request
        googleClient.getGooglePlacesData(location: location, withinMeters: radius) { (response) in
            print("Made Nearby Search request. Passed response here:", response)

            // loops through each result from the 'Nearby Request' to get the 'place_id' and make 'Place Details'
            for location in response.results {
                // Calls 'Place Details' request
                self.googleClient.getGooglePlacesDetailsData(place_id: location.place_id) { (detailsResponse) in
                    print("Made Place Details request. Passed response here:", detailsResponse)

//                    function to drop markers using data received above here
//                    self.putPlaces(places: response.results)
                }
            }

        }

    }

GoogleClient.swift file that contains the code for handling the requests above:

import SwiftUI
import Foundation
import CoreLocation


//Protocol
protocol GoogleClientRequest {
    var googlePlacesKey : String { get set }
    func getGooglePlacesData(location: CLLocation, withinMeters radius: Int, using completionHandler: @escaping (GooglePlacesResponse) -> ())
    func getGooglePlacesDetailsData(place_id: String, using completionHandler: @escaping (GooglePlacesDetailsResponse) -> ())
}



// GoogleClient class that conforms to the ZGoogleClient Request protocol
class GoogleClient: GoogleClientRequest {

    let session = URLSession(configuration: .default)
    var googlePlacesKey: String = "MY_KEY_GOES_HERE"
    let categoriesArray = [
        "park",
        "restaurant",
        "zoo"
    ]


    func getGooglePlacesData(location: CLLocation, withinMeters radius: Int, using completionHandler: @escaping (GooglePlacesResponse) -> ())  {

        for category in categoriesArray {

            let url = googlePlacesNearbyDataURL(forKey: googlePlacesKey, location: location, radius: radius, type: category)

            let task = session.dataTask(with: url) { (responseData, _, error) in
                if let error = error {
                    print(error.localizedDescription)
                    return
                }

                guard let data = responseData, let response = try? JSONDecoder().decode(GooglePlacesResponse.self, from: data) else {
                    print("Could not decode JSON response")
                    completionHandler(GooglePlacesResponse(results:[]))
                    return
                }


                if response.results.isEmpty {
                    print("GC - response returned empty", response)
                } else {
                    print("GC - response contained content", response)
                    completionHandler(response)
                }

            }
            task.resume()
        }

    }


    func getGooglePlacesDetailsData(place_id: String, using completionHandler: @escaping (GooglePlacesDetailsResponse) -> ())  {

        let url = googlePlacesDetailsURL(forKey: googlePlacesKey, place_ID: place_id)

        let task = session.dataTask(with: url) { (responseData, _, error) in
            if let error = error {
                print(error.localizedDescription)
                return
            }

            guard let data = responseData, let detailsResponse = try? JSONDecoder().decode(GooglePlacesDetailsResponse.self, from: data) else {
                print("Could not decode JSON response. responseData was: ", responseData)
                completionHandler(GooglePlacesDetailsResponse(results:[]))
                return
            }

//                print("response result: ", detailsResponse.results)

            if detailsResponse.results.isEmpty {
                print("getGPDetails - response returned empty", detailsResponse)
            } else {
                print("getGPDetails - response contained content", detailsResponse)
                completionHandler(detailsResponse)
            }

        }
        task.resume()

    }


    func googlePlacesNearbyDataURL(forKey apiKey: String, location: CLLocation, radius: Int, type: String) -> URL {
        print("passed  location  before url creation ", location)
        print("passed  radius  before url creation ", radius)

        let baseURL = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
        let locationString = "location=" + String(location.coordinate.latitude) + "," + String(location.coordinate.longitude)
        let radiusString = "radius=" + String(radius)
        let typeString = "type=" + String(type)
//        let rankby = "rankby=distance"
//        let keywrd = "keyword=" + keyword
        let key = "key=" + apiKey

        print("Request URL:", URL(string: baseURL + locationString + "&" + radiusString + "&" + type + "&" + key)!)
        return URL(string: baseURL + locationString + "&" + radiusString + "&" + typeString + "&" + key)!
    }


    func googlePlacesDetailsURL(forKey apiKey: String, place_ID: String) -> URL {
        print("passed  place_ID  before url creation ", place_ID)

        let baseURL = "https://maps.googleapis.com/maps/api/place/details/json?"
        let place_idString = "place_id=" + place_ID
        let fields = "fields=rating"
        let key = "key=" + apiKey

        print("Details request URL:", URL(string: baseURL + place_idString + "&" + fields + "&" + key)!)
        return URL(string: baseURL + place_idString + "&" + fields + "&" + key)!
    }



}

ResponseModels.swift file that holds the structs to handle the 2 different request responses:

import SwiftUI
import Foundation



struct GooglePlacesResponse : Codable {
    let results : [Place]
    enum CodingKeys : String, CodingKey {
        case results = "results"
    }
}



// Place struct
struct Place : Codable {

    let geometry : Location
    let name : String
    let place_id: String
    let openingHours : OpenNow?
    let photos : [PhotoInfo]?
    let types : [String]
    let address : String


    enum CodingKeys : String, CodingKey {
        case geometry = "geometry"
        case name = "name"
        case place_id = "place_id"
        case openingHours = "opening_hours"
        case photos = "photos"
        case types = "types"
        case address = "vicinity"
    }


    // Location struct
    struct Location : Codable {

        let location : LatLong

        enum CodingKeys : String, CodingKey {
            case location = "location"
        }


        // LatLong struct
        struct LatLong : Codable {

            let latitude : Double
            let longitude : Double

            enum CodingKeys : String, CodingKey {
                case latitude = "lat"
                case longitude = "lng"
            }
        }

    }


    // OpenNow struct
    struct OpenNow : Codable {

        let isOpen : Bool

        enum CodingKeys : String, CodingKey {
            case isOpen = "open_now"
        }
    }


    // PhotoInfo struct
    struct PhotoInfo : Codable {

        let height : Int
        let width : Int
        let photoReference : String

        enum CodingKeys : String, CodingKey {
            case height = "height"
            case width = "width"
            case photoReference = "photo_reference"
        }
    }



}



struct GooglePlacesDetailsResponse : Codable {
    let results : [PlaceDetails]
    enum CodingKeysDetails : String, CodingKey {
        case results = "results"
    }
}



// PlaceDetails struct
// I have fields commented out because I wanted to just get a location's rating for testing before implementing the rest
struct PlaceDetails : Codable {

//    let place_id: String
//    let geometry : Location
//    let name : String
    let rating : CGFloat?
//    let price_level : Int
//    let types : [String]
//    let openingHours : OpenNow?
//    let formatted_address : String
//    let formatted_phone_number : String
//    let website : String
//    let reviews : String
//    let photos : [PhotoInfo]?


    enum CodingKeysDetails : String, CodingKey {
//        case place_id = "place_id"
//        case geometry = "geometry"
//        case name = "name"
        case rating = "rating"
//        case price_level = "price_level"
//        case types = "types"
//        case openingHours = "opening_hours"
//        case formatted_address = "formatted_address"
//        case formatted_phone_number = "formatted_phone_number"
//        case website = "website"
//        case reviews = "reviews"
//        case photos = "photos"
    }



}


Google Places API documentation I've been referencing:

https://developers.google.com/places/web-service/search

-

Like I said above, right now my first request ('Nearby Search') is successfully returning data, but when I try to implement the second request ('Place Details') using a 'place_id'. My console keeps returning "Could not decode JSON response. Response: Optional(106 bytes)" which comes from a comment in my 'GoogleClient.swift' file.

My questions are:

What is wrong with how I'm making the 'Place Details' request that is causing it to return that the response cant be decoded?

AND

Is there a better way to make a Nearby Search request then use the 'place_id' from the returned locations to make a Place Details request in swift?

Upvotes: 0

Views: 2769

Answers (1)

ethanrj
ethanrj

Reputation: 56

It looks like the issue you're having is with your model:

struct GooglePlacesDetailsResponse : Codable {
let results : [PlaceDetails]
enum CodingKeysDetails : String, CodingKey {
    case results = "results"
}

}

According to the documentation there is no key on the place details response for "results" the correct key is "result" non-plural. Also you define the model as an array of the struct PlaceDetails, which is incorrect. When you return place details using a place id it will return only one object, which is the place details for that specific id as the place id is a unique value. I expect if you correct your response model your issues should resolve.

Upvotes: 2

Related Questions