[SalesForce] Invocable Class can’t run call to API method

I have a process builder executing the following Apex class when the criteria is met. Everything runs nicely until the last line, where the API method is called and a map is passed.

public class Cls_Location {

    @InvocableMethod(label='Get Device Id')
    public static void getCoordinates(List<Id> dList) {

        List<Id> ids = new List<Id>();
        for(Id i : dList) {
            ids.add(i);
            system.debug(i);
        }

        //sensor coordinates map and respective list
        Map<Id, List<Double>> sCoord = new Map<Id, List<Double>>();
        List<Double> sCoordList = new List<Double>();
        //device coordinates map and respective list
        Map<Id, List<Double>> dCoord = new Map<Id, List<Double>>();
        List<Double> dCoordList = new List<Double>();

        for(Device__c qDevice : [SELECT Id, Name, 
                                   Geo_Ip_Location__c, Geo_Ip_Location__Latitude__s, Geo_Ip_Location__Longitude__s
                                    FROM Device__c
                                    WHERE Id IN :ids]) {



                                        system.debug('Query: ' + qDevice);        
                                    Double dLat  = qDevice.Geo_Ip_Location__Latitude__s;
                                    Double dLgtd = qDevice.Geo_Ip_Location__Longitude__s;
                                        system.debug('dLat: ' + dLat);
                                        system.debug('dLgtd: ' + dLgtd);

                                    dCoordList.add(dLat);
                                    dCoordList.add(dLgtd);
                                    dCoord.put(qDevice.Id, dCoordList);
                                        system.debug('dCoord: ' + dCoord);
                                         } 


        if (dCoord.size() > 0) {

        for (Id c : dCoord.keySet()) {
            system.debug('Device Lat: ' + dCoord.get(c)[0]);
            system.debug('Device Longtd: '+ dCoord.get(c)[1]);
        }

        Cls_GoogleMapsCalloutService.runCallout(dCoord);

        } 
    }
}

Here's a snippet of the API callout class and method:

public class Cls_GoogleMapsCalloutService {

public static HttpResponse runCallout(Map<Id, List<Double>> m1) {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint(MAPS_URL);
        request.setMethod('GET');
        HttpResponse response = http.send(request);



            if (response.getStatusCode() == 200) {

//JSON PARSING is done here

Is there any way around this?

Grateful for your time.

Best Answer

So the error you would get in your debug logs is Uncommited Work Pending:.

There is a limitation in SF, if you do a dml, you cannot do a callout in same transaction. This is just the way SF works.

So if your process builder is running, that means you already did a DML. Now from Process Builder you calling an Invocable method, which does callout.

Thus it will fail. Is there a workaround for this?

ofc. You have to use future method.

public class Cls_GoogleMapsCalloutService  {

@future(callout=true)

public static void runCallout(String input) {
        Map<Id, List<Double>> m1 =(Map<Id, List<Double>>) JSON.deserialize(input, Map<Id, List<Double>>.class);
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        //request.setEndpoint(MAPS_URL);
        request.setMethod('GET');
        HttpResponse response = http.send(request);
}
}

Calling from your invocable method like

 Cls_GoogleMapsCalloutService.runCallout(JSON.serialize(dCoord));

As future method can only accept primitive datatype, I have to convert your Map of (Map<Id, List<Double>>) to JSON string and then deserialize and use it.

Edit : If you want to use queuable Apex then Main class will be

public class Cls_GoogleMapsCalloutService  implements  Queueable,Database.AllowsCallout{

    private Map<Id, List<Double>> m1;


    public Cls_GoogleMapsCalloutService(Map<Id, List<Double>> m1){
        this.m1 = m1;
    }

    public void execute(QueueableContext qc) {

            Http http = new Http();
            HttpRequest request = new HttpRequest();
            request.setEndpoint(MAPS_URL);
            request.setMethod('GET');
            HttpResponse response = http.send(request);
    }
¬}

And then you would call your queuable like :

 System.enqueueJob(new Cls_GoogleMapsCalloutService  (dCoord));

Source: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_queueing_jobs.htm

Source: http://cloudyworlds.blogspot.com/2015/03/invoking-apex-callout-from-process.html