Blog

Google address Finder API

Innovalleyworks - Google address Finder API

This blog provides an end-to-end solution to pre-populate address on Salesforce standard layout, custom lwc component for internal org and communities using google API.

  1. Standard out of the box feature with Map and Location Settings.
  2. LWC component with show-address-lookup , out of the box feature.
  3. Complete customization solution with Google API integration in LWC component.
  4. (Since the above two options are not supported in the communities).

Salesforce Standard out of box Address Lookup feature.

To enable the Address Lookup in Salesforce, search for Maps in Setup and enable the following setting:

Google address Finder API

Address Lookup in Standard Layouts:

On enabling the above setting, the Address Lookup will be available in the Standard layouts for the Address fields.

Address search field:

Google address Finder API

Address data fed into the respective fields:

Google address Finder API

2. Address Lookup in Custom Lightning Web Components:

The below solution works only if the Map setting is enabled in your Salesforce org.

Google address Finder API

The code for the above LWC Component is:

<lightning-input-address
address-label=”Address”
street-label=”Street”
city-label=”City”
country-label=”Country”
province-label=”Province”
postal-code-label=”PostalCode”
street=””
city=””
country=””
province=””
postal-code=””
required
field-level-help=”Help Text for inputAddress field”
show-address-lookup=true
onchange={handleAddressChange} />
</lightning-input-address/>

The attribute ‘show-address-lookup’, when set to true will get the lookup on the component.

Google address Finder API

The code for the above LWC Component is:

                                handleAddressChange(event) {
                                   this.account.mailingStreet = event.detail.street;
                                   this.account.mailingCity = event.detail.city;
                                   this.account.mailingCountry = event.detail.country;
                                   this.account.mailingPostalCode = event.detail.postcode;
                               }

The ‘show-address-lookup’ attribute does not work in the Salesforce Communities.

References:

https://developer.salesforce.com/docs/component-library/bundle/lightning:inputAddress/documentation

3. Address Lookup in Salesforce Communities:

To implement the Address Lookup in Salesforce Communities, we need to integrate the Google Places API.

Please follow the below link to get a Google Developer Account:

https://console.cloud.google.com/freetrial/signup/tos?redirectPath=%2Fgoogle%2Fmaps-apis%2Fwelcome%3Fpli%3D1%26step%3Dproduct_selection

The Billing details are available in the below link:

https://developers.google.com/maps/documentation/places/web-service/usage-and-billing

The rest of this blog details the implementation of LWC component to search and autocomplete addresses, integrating with Google Places API.

Address Search:

This is the first API call, to get the possible suggestions from Google. The address search in Google Places API uses the Place Autocomplete, which gets the possible places, that match with the input text.

https://developers.google.com/maps/documentation/places/web-service/autocomplete

Address Detail:

This is the API call to get the details of a place chosen by the user, using the place ID.

Reference:

https://developers.google.com/maps/documentation/places/web-service/details

LWC Component Integrated with Google Places API:

Custom Setting:

The below snapshot indicates the Custom Setting configuration, used to store the API Key details of the Google Places API:

Google address Finder API

AddressLookupDetails MockResponse:

                                {
    "html_attributions": [],
    "result": {
        "address_components": [
            {
                "long_name": "TF3 4JH",
                "short_name": "TF3 4JH",
                "types": [
                    "postal_code"
                ]
            },
            {
                "long_name": "Telford",
                "short_name": "Telford",
                "types": [
                    "postal_town"
                ]
            },
            {
                "long_name": "Shropshire",
                "short_name": "Shropshire",
                "types": [
                    "administrative_area_level_2",
                    "political"
                ]
            },
            {
                "long_name": "England",
                "short_name": "England",
                "types": [
                    "administrative_area_level_1",
                    "political"
                ]
            },
            {
                "long_name": "United Kingdom",
                "short_name": "GB",
                "types": [
                    "country",
                    "political"
                ]
            }
        ],
        "adr_address": "Telford TF3 4JH, UK",
        "formatted_address": "Telford TF3 4JH, UK",
        "geometry": {
            "location": {
                "lat": 52.6724488,
                "lng": -2.4425239
            },
            "viewport": {
                "northeast": {
                    "lat": 52.67413585574869,
                    "lng": -2.441274692484295
                },
                "southwest": {
                    "lat": 52.6714378951657,
                    "lng": -2.444574120171743
                }
            }
        },
        "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/geocode-71.png",
        "icon_background_color": "#7B9EB0",
        "icon_mask_base_uri": "https://maps.gstatic.com/mapfiles/place_api/icons/v2/generic_pinlet",
        "name": "TF3 4JH",
        "place_id": "ChIJk7hOBoZ_cEgRUDnpw3KBzUk",
        "reference": "ChIJk7hOBoZ_cEgRUDnpw3KBzUk",
        "types": [
            "postal_code"
        ],
        "url": "https://maps.google.com/?q=TF3+4JH&ftid=0x48707f86064eb893:0x49cd8172c3e93950",
        "utc_offset": 60
    },
    "status": "OK"
}

AddressLookupDetails MockResponse:

                                {
    "predictions": [
        {
            "description": "Telford TF3 3AT, UK",
            "matched_substrings": [
                {
                    "length": 3,
                    "offset": 8
                }
            ],
            "place_id": "ChIJKRvpnvh_cEgRppCTUhWrmI8",
            "reference": "ChIJKRvpnvh_cEgRppCTUhWrmI8",
            "structured_formatting": {
                "main_text": "TF3 3AT",
                "main_text_matched_substrings": [
                    {
                        "length": 3,
                        "offset": 0
                    }
                ],
                "secondary_text": "Telford, UK"
            },
            "terms": [
                {
                    "offset": 0,
                    "value": "Telford"
                },
                {
                    "offset": 8,
                    "value": "TF3 3AT"
                },
                {
                    "offset": 17,
                    "value": "UK"
                }
            ],
            "types": [
                "postal_code",
                "geocode"
            ]
        },
        {
            "description": "Telford TF3, UK",
            "matched_substrings": [
                {
                    "length": 3,
                    "offset": 8
                }
            ],
            "place_id": "ChIJCWNyiQl_cEgRTIROLK4L03o",
            "reference": "ChIJCWNyiQl_cEgRTIROLK4L03o",
            "structured_formatting": {
                "main_text": "TF3",
                "main_text_matched_substrings": [
                    {
                        "length": 3,
                        "offset": 0
                    }
                ],
                "secondary_text": "Telford, UK"
            },
            "terms": [
                {
                    "offset": 0,
                    "value": "Telford"
                },
                {
                    "offset": 8,
                    "value": "TF3"
                },
                {
                    "offset": 13,
                    "value": "UK"
                }
            ],
            "types": [
                "postal_code_prefix",
                "postal_code",
                "geocode"
            ]
        },
        {
            "description": "Telford TF3 4JH, UK",
            "matched_substrings": [
                {
                    "length": 3,
                    "offset": 8
                }
            ],
            "place_id": "ChIJk7hOBoZ_cEgRUDnpw3KBzUk",
            "reference": "ChIJk7hOBoZ_cEgRUDnpw3KBzUk",
            "structured_formatting": {
                "main_text": "TF3 4JH",
                "main_text_matched_substrings": [
                    {
                        "length": 3,
                        "offset": 0
                    }
                ],
                "secondary_text": "Telford, UK"
            },
            "terms": [
                {
                    "offset": 0,
                    "value": "Telford"
                },
                {
                    "offset": 8,
                    "value": "TF3 4JH"
                },
                {
                    "offset": 17,
                    "value": "UK"
                }
            ],
            "types": [
                "postal_code",
                "geocode"
            ]
        },
        {
            "description": "Telford TF3 3BA, UK",
            "matched_substrings": [
                {
                    "length": 3,
                    "offset": 8
                }
            ],
            "place_id": "ChIJ-TQZ_PZ_cEgRO6EYkC-5XXY",
            "reference": "ChIJ-TQZ_PZ_cEgRO6EYkC-5XXY",
            "structured_formatting": {
                "main_text": "TF3 3BA",
                "main_text_matched_substrings": [
                    {
                        "length": 3,
                        "offset": 0
                    }
                ],
                "secondary_text": "Telford, UK"
            },
            "terms": [
                {
                    "offset": 0,
                    "value": "Telford"
                },
                {
                    "offset": 8,
                    "value": "TF3 3BA"
                },
                {
                    "offset": 17,
                    "value": "UK"
                }
            ],
            "types": [
                "postal_code",
                "geocode"
            ]
        },
        {
            "description": "Telford TF3 3BD, UK",
            "matched_substrings": [
                {
                    "length": 3,
                    "offset": 8
                }
            ],
            "place_id": "ChIJ98h38yCAekgRCmROOqkNxpI",
            "reference": "ChIJ98h38yCAekgRCmROOqkNxpI",
            "structured_formatting": {
                "main_text": "TF3 3BD",
                "main_text_matched_substrings": [
                    {
                        "length": 3,
                        "offset": 0
                    }
                ],
                "secondary_text": "Telford, UK"
            },
            "terms": [
                {
                    "offset": 0,
                    "value": "Telford"
                },
                {
                    "offset": 8,
                    "value": "TF3 3BD"
                },
                {
                    "offset": 17,
                    "value": "UK"
                }
            ],
            "types": [
                "postal_code",
                "geocode"
            ]
        }
    ],
    "status": "OK"
}

Static Resource:

The above MockResponses are to be added in the Static Resource, for the purpose of testing the API Callouts:

Google address Finder API

Apex Wrapper Classes:

The below two classes are the API Responses, converted into Apex Classes:

AddressListWrapper:

public class AddressListWrapper {
    
    @AuraEnabled
	public List predictions;
	public String status;
    
	public class cls_predictions {
        @AuraEnabled
		public String description;
        @AuraEnabled
		public String place_id;
        @AuraEnabled
		public String reference;
	}
	
	public static AddressListWrapper parse(String json){
		return (AddressListWrapper) System.JSON.deserialize(json, AddressListWrapper.class);
	}

}

                            

AddressDetailWrapper:

public class AddressDetailWrapper {

    @AuraEnabled
    public Result result;
    public String status;
    
    public class Result {
        @AuraEnabled
        public List address_components;
        public String adr_address;
        public String business_status;
        public String formatted_address;
        public String place_id;
    }
    
    public class AddressComponent {
        @AuraEnabled
        public String long_name;
        @AuraEnabled
        public String short_name;
        @AuraEnabled
        public List types {get;set;} 
    }

    public static AddressDetailWrapper parse(String json){
        return (AddressDetailWrapper) System.JSON.deserialize(json, AddressDetailWrapper.class);
    }

}
                            

Apex Class for API Callout:

public class AddressSearchController {
	

    private static final String suggestionURL = 'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=';
    private static final String placesURL = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
    
    @AuraEnabled
    public static List getPlaces(String input) {

 
        
        String url = suggestionURL + EncodingUtil.urlEncode(input, 'UTF-8') + getAPIKey();   
        String response = getResponse(url); 
        AddressListWrapper addressList = AddressListWrapper.parse(response);
        
        
        return addressList.predictions;
    }

    @AuraEnabled
    public static AddressData getPlaceDetails(String placeId) {
        
        
        AddressData addressData = new AddressData();
        String url = placesURL + EncodingUtil.urlEncode(placeId, 'UTF-8') + getAPIKey();   
        String response = getResponse(url);
        
        AddressDetailWrapper addressDetail = AddressDetailWrapper.parse(response);
        
        addressData = constructAddressData(addressDetail);

        return addressData;
    }


    private static String getResponse(String url) {

        
        String responseBody = '';
        Http h = new Http();

        HttpRequest req = new HttpRequest();
        HttpResponse res = new HttpResponse();
        req.setMethod('GET');
        req.setEndpoint(url);
        req.setTimeout(120000);
        res = h.send(req); 
        
        responseBody = res.getBody(); 

        return responseBody; 
    }



    @TestVisible
    private static String getAPIKey() {
        
        
        
        String apiKey = '';

        for(APIKeysForAuthorization__c apiDetails : APIKeysForAuthorization__c.getall().values()) {
            if(apiDetails.Name == 'GooglePlacesAPI') {
                apiKey = apiDetails.API_Key__c;
            }
        }
        apiKey = '&key=' + apiKey;


        return apiKey;
    }
    

    private static AddressData constructAddressData(AddressDetailWrapper addressDetail) {
        
        AddressData addressData = new AddressData();
        
        if(addressDetail != null) {
            
            for(AddressDetailWrapper.AddressComponent addressComponent: addressDetail.result.address_components) {
                if(addressComponent.types.contains('postal_code')) {
                    addressData.postcode = addressComponent.long_name;
                }
                if(addressComponent.types.contains('postal_town')) {
                    addressData.city = addressComponent.long_name;
                }
                if(addressComponent.types.contains('country')) {
                    addressData.country = addressComponent.long_name;
                }
                if(addressComponent.types.contains('route')) {
                    addressData.street = addressComponent.long_name;
                }
            }
        }
        

        return addressData;
    }

    // Class to hold the details of the selected address
    public class AddressData {
        @AuraEnabled
        public String street {get; set;}
        @AuraEnabled
        public String city {get; set;}
        @AuraEnabled
        public String country {get; set;}
        @AuraEnabled
        public String postcode {get; set;}
    }
}

                            

Testing the Address Lookup component Apex:

                                @isTest
public class AddressSearchControllerTest {

    @isTest
    static void testGetSuggestions() {
        
        Test.setMock(HttpCalloutMock.class, new AddressLookupMock('Suggestions'));
        Test.startTest();
        AddressSearchController.getSuggestions('TF3');
        //AddressSearchController.getPlaceDetails('TF3');
        Test.stopTest();
    }
    
        @isTest
    static void testPlaceDetails() {
        
        Test.setMock(HttpCalloutMock.class, new AddressLookupMock('Place'));
        Test.startTest();
        //AddressSearchController.getSuggestions('TF3');
        AddressSearchController.getPlaceDetails('TF3');
        Test.stopTest();
    }
    
    @isTest
    static void testGetAPIKey() {
        
       List settingsList = new                List();
        APIKeysForAuthorization__c  config = new APIKeysForAuthorization__c();
        config.Name = 'GooglePlacesAPI';
        config.API_Key__c  = 'ABCD12345';
        settingsList.add(config);
        insert settingsList;
        
        Test.startTest();
        String apiKey = AddressSearchController.getAPIKey();
        System.assertEquals(true, apiKey.equals('&key=ABCD12345'));
        Test.stopTest();
    }
}

                            

Testing the Address Lookup component Apex:

                                @isTest
public class AddressSearchControllerTest {

    @isTest
    static void testGetSuggestions() {
        
        Test.setMock(HttpCalloutMock.class, new AddressLookupMock('Suggestions'));
        Test.startTest();
        AddressSearchController.getSuggestions('TF3');
        //AddressSearchController.getPlaceDetails('TF3');
        Test.stopTest();
    }
    
        @isTest
    static void testPlaceDetails() {
        
        Test.setMock(HttpCalloutMock.class, new AddressLookupMock('Place'));
        Test.startTest();
        //AddressSearchController.getSuggestions('TF3');
        AddressSearchController.getPlaceDetails('TF3');
        Test.stopTest();
    }
    
    @isTest
    static void testGetAPIKey() {
        
       List settingsList = new                List();
        APIKeysForAuthorization__c  config = new APIKeysForAuthorization__c();
        config.Name = 'GooglePlacesAPI';
        config.API_Key__c  = 'ABCD12345';
        settingsList.add(config);
        insert settingsList;
        
        Test.startTest();
        String apiKey = AddressSearchController.getAPIKey();
        System.assertEquals(true, apiKey.equals('&key=ABCD12345'));
        Test.stopTest();
    }
}

                            

AddressLookupMock:

                                @isTest
global class AddressLookupMock implements HttpCalloutMock {

    String apiChosen = '';
    
    public AddressLookupMock(String apiName) {
        apiChosen = apiName;
    }
    
    // Implement this interface method
    global HTTPResponse respond(HTTPRequest req) {
        
        StaticResource sr;
        
        if(apiChosen == 'Suggestions')
            sr = [select id,body from StaticResource Where Name = 'AddressLookupSuggestionsMock'];
        if(apiChosen == 'Place')
            sr = [select id,body from StaticResource Where Name = 'AddressLookupDetailsMock'];

        String respStr = sr.body.toString();
        
        // Create a fake response
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');
        res.setBody(respStr);
        res.setStatusCode(200);
        return res;
    }
    
}

LWC Code for AddressLookup component:

<template>

<lightning-input label=”Address Lookup” data-name=”location” onchange={getPlaces} placeholder=”Search by Postcode/Adddress”></lightning-input>
<div style=”background: white;” id=”listbox-id-1″ class=”slds-dropdown_length-with-icon-7 slds-dropdown_fluid” role=”listbox”>
<ul class=”slds-listbox slds-listbox_vertical” role=”presentation”>
<template if:true={places} for:each={places} for:item=”place” for:index=”index”>
<li onclick={handleSelect} role=”presentation” class=”slds-listbox__item” data-record-id={place.place_id} key={place.place_id}>
<div data-id={place.place_id} class=”slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta” role=”option”>
<span class=”slds-media__body”>
<span class=”slds-listbox__option-text slds-listbox__option-text_entity”>
{place.description}
</span>
</span>
</div>
</li>
</template>
</ul>
</div>
</template>

JS Controller:

                                import { LightningElement } from 'lwc';
import getPlaces from '@salesforce/apex/AddressSearchController.getPlaces';
import getPlaceDetails from '@salesforce/apex/AddressSearchController.getPlaceDetails';
export default class AddressLookup extends LightningElement {
 
   places;
 
   getPlaces(event) {
 
       if(event.detail.value) {
 
           getPlaces({input: event.detail.value})
           .then(result => {
               this.places = JSON.parse(JSON.stringify(result));
           }).catch(error => {
               console.log('error ' + JSON.stringify(error));
           });
          
       } else {
           this.places = [];
       }
   }
 
   handleSelect(event) {
 
       getPlaceDetails({placeId: event.currentTarget.dataset.recordId})
           .then(result => {
               this.places = [];
               const selectedEvent = new CustomEvent("addressselect", {detail: JSON.parse(JSON.stringify(result))});
               this.dispatchEvent(selectedEvent);
           }).catch(error => {
               console.log('error ' + JSON.stringify(error));
           });
          
   }
}

Method to be implemented in the Caller’s JS Controller:

                                handleOnAddressSelect(event) {
       this.account.mailingStreet = event.detail.street;
       this.account.mailingCity = event.detail.city;
       this.account.mailingCountry = event.detail.country;
       this.account.mailingPostalCode = event.detail.postcode;
}

Using the AddressLookup component in another LWC Component:

<c-address-lookup onaddressselect={handleOnAddressSelect}>
</c-address-lookup>

Google address Finder API

We are Innovalleyworks , We are a passionate team of developers, best thinkers and consultants who can solve anything and everything.
With our highly engaging team, you can easily bring the vision to all your business ventures come true.
We have team, put your problem, get your solution