function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Harjeet Singh 13Harjeet Singh 13 

How to bulkify triggers

Dear All,

I am immense need of help from all of you. After giving a much thought and so much brainstorming happened finally I turned up here for advise.

Requirement:
I need to fetch coordinates value of account based on Map address provided

Solution approach
I create few fields on accounts like MapAddressCity,MapAddressState,MapAddressStreet,MapAddressPostalCode and MapAddresscountry on account. When an user enters values in Map fields and clicks on save button I am calling Google API. Google API will returns one coordinate value based on address filled in Map fields. I have created one field called "Location"(Data Type-Geolocation) which will stores coordinates and also I created 2 formula fields which stores the longitude and latitude values of coordinates in "LonField" and "LatField" respectively

If an user enters below in account:
MapAddressCity: San Francisco  
MapAddressCountry: United States  
MapAddressPostalCode: 94105  
MapPostalState: CA  
MapAddressStreet: One Market Street
and clicks on Save-Google API call will be made and below information wiull be stored on account:
Location-37°47'38''N 122°23'41''W  
LongValue-122.3948  
LatValue37.7939

My Question:
In our org there is already one trigger written on account and there is already one accounthandler class and we always follow best practices so I didnt write a separate trigger and class to achieve above mentioned requirement. I am reusing same accounttriggerhandler class and apex trigger which is written on account.
If I write separate class and trigger to achieve above mentioned functionality then I have no issue everything works smoothly but when I am trying to include my code in already existing account trigger handler then I am not able to make my code bulkify. I am calling my class method on after insert and after update and coordinates fields are updating perfectly but only for single account because I am not able to make my code bulkify and runs for multiple accounts.

Kindly help me. Below is trigger which is already existing on account:
//Single Master Trigger on Account using Handlers for each individual actions
trigger AccountTrigger on Account (before delete, before insert, before update, after insert, after update,after delete) {

    checkRecursive.isRunOnce = true; // ToDo : Need to change the recursive handling logic
    AccountTriggerHandler accHandler = new AccountTriggerHandler(trigger.new,trigger.old,trigger.newMap,trigger.oldMap,trigger.isUpdate);
    
 
    if(trigger.isBefore && trigger.isInsert){
        accHandler.HandleBeforeInsert();
        
    }
    if(trigger.isBefore && trigger.isUpdate){
        accHandler.HandleBeforeUpdate();
       
    }
    if(trigger.isAfter && trigger.isInsert) {  
        accHandler.HandleAfterInsert();
    
    }
  
    if(trigger.isAfter && trigger.isUpdate){
        accHandler.HandleAfterUpdate();
    
    }
 
    if(trigger.isBefore && trigger.isDelete)
        accHandler.HandleBeforeDelete();

    if(trigger.isAfter && trigger.isDelete)
        accHandler.HandleAfterDelete();       
}

My class code is as belows:
 
//Handler Class to handle Account Trigger
public class AccountTriggerHandler {
    
    public static Boolean runAccountTriggerHandler = true;
    private static Boolean geocodingCalled = false;

    
    public static boolean run = true;
    public static boolean runOnce(){
        if(run){
            run=false;
            return true;
        }else{
            return run;
        }
    }
    
    //trigger variables
    List<Account> newAccs;
    List<Account> oldAccs;
    Map<Id,Account> newAccMap;
    Map<Id,Account> oldAccMap;
    Id accountId;
    boolean isUpdate;
    
    //constructor
    public AccountTriggerHandler(List<Account> newAccs, List<Account> oldAccs, 
                                 Map<Id,Account> newAccMap, Map<Id,Account> oldAccMap, boolean isUpdate){
        this.newAccs = newAccs;
        this.oldAccs = oldAccs;
        this.newAccMap = newAccMap;
        this.oldAccMap = oldAccMap;
        this.isUpdate = isUpdate;
        this.accountId= accountId;                          
        this.consentService = new ConsentManagementService(Account.sObjectType);
    }
    

 
    
    public void HandleAfterInsert(){ 
        
		//if(!System.isFuture())	           
		//getLocation(newAccs[0].Id);
		DoAddressGeocode(newAccs[0].id);
            
            
        }    
    }
        
    public void HandleAfterUpdate(){
        
                //if(!System.isFuture())     
                    //getLocation(newAccs[0].Id);
                    DoAddressGeocode(newAccs[0].id);
                    
               
            }
               
            
        }    
    }

    public void HandleBeforeDelete(){
        
    }
 
    public void HandleAfterDelete(){
        
    }
    
    
    
    // wrapper method to prevent calling futuremethods from an existing future context
public static void DoAddressGeocode(id accountId) {
 				 if(geocodingCalled || System.isFuture()) {
                    System.debug(LoggingLevel.WARN,'***Address Geocoding Future Method Already Called - Aborting...');
   						 return;
  					}
                
  		// if not being called from future context, geocode the address
  						geocodingCalled= true;
  						getLocation(accountId);
	}
    @future (callout=true)  // future method needed to run callouts from Triggers
      static public void getLocation( id accountId){
        
        // gather account info
      Account a =[SELECT MapAddressCity__c,MapAddressCountry__c,MapAddressPostalCode__c,MapAddressStreet__c,MapPostalState__c FROM Account  WHERE id =: accountId];
      //List<Account> a= new List<Account>();
      //a= [SELECT id, MapAddressCity__c,MapAddressCountry__c,MapAddressPostalCode__c,MapAddressStreet__c,MapPostalState__c FROM Account  WHERE id =: accountId];
      
        // create an address string
        String address = '';
        if (a.MapAddressStreet__c!= null)
            address += a.MapAddressStreet__c+', ';
        if (a.MapAddressCity__c != null)
            address += a.MapAddressCity__c +', ';
        if (a.MapPostalState__c!= null)
            address += a.MapPostalState__c+' ';
        if (a.MapAddressPostalCode__c!= null)
            address += a.MapAddressPostalCode__c+', ';
        if (a.MapAddressCountry__c!= null)
            address += a.MapAddressCountry__c;

        address = EncodingUtil.urlEncode(address, 'UTF-8');

        // build callout
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint('http://maps.googleapis.com/maps/api/geocode/json?address='+address+'&sensor=false');
        req.setMethod('GET');
        req.setTimeout(60000);

        try{
            // callout
            HttpResponse res = h.send(req);

            // parse coordinates from response
            JSONParser parser = JSON.createParser(res.getBody());
            double lat = null;
            double lon = null;
            while (parser.nextToken() != null) {
                if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
                    (parser.getText() == 'location')){
                       parser.nextToken(); // object start
                       while (parser.nextToken() != JSONToken.END_OBJECT){
                           String txt = parser.getText();
                           parser.nextToken();
                           if (txt == 'lat')
                               lat = parser.getDoubleValue();
                           else if (txt == 'lng')
                               lon = parser.getDoubleValue();
                       }

                }
            }

            // update coordinates if we get back
            if (lat != null){
                a.Location__Latitude__s = lat;
                a.Location__Longitude__s = lon;
                update a;
            }

        } catch (Exception e) {
        }
    }

}

I have removed all the extra codes and methods from class and only kept my method in class to have a better visibility for code to all of you.
I am calling "DoAddressGeocode​" method on HandleAfterInsert and HandleAfterUpdate.
Right now I am not able to do bulkification of my code and wrote only for 1 accounts like DoAddressGeocode(newAccs[0].id)

Any help would be greatly appreciated.

Thanks in advance!

Kindly help

 
Tuan LuTuan Lu
You might want to for loop through newAccs instead of just processing the [0] index account. Also having hard rule of 1 trigger for an object is also not good neither from a general software dev best practice or a admin operations best practice. I might write a blog about this. Also there is a transaction CPU timeout in Salesforce of 10 seconds. You should consider using a asynch or batch process to update the account geo fields otherwise your callout will consume most of your CPU time. I provide consulting services is you want to talk about your design. LMK thanks. 
Harjeet Singh 13Harjeet Singh 13
Dear Tuan,

Thanks for your kind response.

I will be happy to get in touch with you If I will be able to solve my bulkification issue.

Many thanks in advance

Thanks & Regards,
Harjeet
 
Harjeet Singh 13Harjeet Singh 13
Dear Tuan,

I am using future callout for updating geofields.Kindly refer 85 of my class

Thanks & Regards,
Harjeet