[SalesForce] How to update a record in a trigger with value from a REST API

When an account is saved I need to update it with value from a REST API. I though my approach would work but I'm getting the following compilation errors.

Method does not exist or incorrect signature: void getTicker() from the type tickerClass

What's the proper way to make a REST API callout in a trigger to update the trigger records?

I tried getting it to work like this, but got stuck on the compilation error.

  1. Person clicks New Account, and fills in the appropriate fields. Person clicks Save.
  2. triggerTicker on Account is triggered.
  3. triggerTicker calls tickerClass.
  4. tickerClass uses REST API to grab the 'high' value.
  5. tickerClass adds the 'high' value in the valueHigh variable and then assigns valueHigh to the Ticker_Start__c field.
  6. Record is saved.

My trigger.

trigger triggerTicker on Account (before insert) {

    tickerClass.getTicker();

    // convert the output of ticker.getTicker() from String to Decimal
    Decimal newTicker = Decimal.valueOf(tickerClass.getTicker());

    // create new Account record and include required fields
    for (Account newAcc : Trigger.new) {
        Account a = new Account();
        a.Name = 'Ticker Test';
        a.Ticker_Start__c = newTicker; 
    }
}

It calls a class (tickerClass) method getTicker(). I am getting the following Problem in the on triggerTicker LINE 'Decimal newTicker = Decimal.valueOf(ticker.getTicker());:

Here is tickerClass.getTicker():

public class tickerClass {

    @future(callout=true) 
    public static void getTicker() {

        String retVal = null;

        Http http = new Http();
        HttpRequest request = new HttpRequest();

        request.setEndpoint('https://www.bitstamp.net/api/ticker/');
        request.setMethod('GET');
        HttpResponse response = http.send(request);

        if (response.getStatusCode() == 200) {
            Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
            system.debug(results);

            retVal = string.valueof(results.get('high'));
            Decimal valueHigh = decimal.valueOf(retVal);
        }
    }
}

UPDATE: UPDATED THE TRIGGER AND THE CLASS BUT STILL GETTING ERRORS. I AM HAVING ISSUES WITH JUST PASSING THE ACCOUNT FROM THE TRIGGER TO THE CLASS. IDEAS? CAN'T FIND ANYTHING IN TRAILHEAD.

Trigger (updated)

trigger triggerTicker on Account (after insert) {
    tickerClass.getTicker(Trigger.new);
}

Class (updated)

public class tickerClass {

    // have to call future method if you are going to pull the method in a trigger
    @future(callout=true) 

    // because we are looking for the accountId we need to make the trigger an after trigger
    public static void getTicker(Account[] accounts) {

        // do callout stuff
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://www.bitstamp.net/api/ticker/');
        request.setMethod('GET');
        HttpResponse response = http.send(request);

        if (response.getStatusCode() == 200) {
            Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
            String retVal = null;
            retVal = string.valueof(results.get('high'));
            Decimal valueHigh = decimal.valueOf(retVal);

                for (Account acct : accounts) {
                    acct.Ticker_Start__c = valueHigh;
                }
        }
        // save the changes
        update accounts;
    }
}

tickerClass Error
'public static void getTicker(Account[] accounts) {'
Future methods do not support parameter type of List
Ok, I'm thinking I need to pass over the Account Id, right? How do I do that?

triggerTicker Error
'tickerClass.getTicker(Trigger.new);'
Method does not exist or incorrect signature: void getTicker(List) from the type tickerClass
I get one of these errors no matter what I do it seems. I have no idea how to properly pass data to the Class it seems…

Best Answer

Specific Issues

Your method signature is

public static void getTicker(String retVal) {

but you are passing it Trigger.new, a List<sObject>. The compiler is looking for

public static void getTicker(List<sObject> retVal) {

which does not exist.

Edit: per your comments and discussion, what you are aiming at is to have a method

@future
public static void getTicker(List<Id> accountIds) {

which you can call like this from your trigger:

tickerClass.getTicker(new List<Id>(Trigger.newMap.keySet()));

This is a simple one-liner to get a list of Ids for all of the Accounts that are in your trigger context.

Then, within your method, you'll need to create a list of Account sObject instances to update, something like this:

List<Account> toUpdate = new List<Account>();
for (Id thisId : accountIds) {
    toUpdate.add(new Account(Id = thisId, Ticker_Start__c = valueHigh));
}
update toUpdate;

When you update sObjects, you only need to populate the Id field and those fields whose values you wish to change.

More General Issues

You seem very uncomfortable with core Apex syntax and type infrastructure, and I'm going to go out on a limb and guess this is your first programming language. Trailhead is a great resource, but it's not comprehensive in providing an introduction to computer programming.

The Apex Developer Guide is somewhat more systematic in introducing the Apex language and how it works. It's not easy, but the ADG provides more detail and conceptual guidance, I think, than does Trailhead. But that might be another valuable resource for you, and I'd suggest going through the first several chapters in their entirety. Concepts like typing, parameters, and syntax are things you absolutely need in order to progress, and I'm afraid you'll find yourself quite frustrated if you don't establish that foundation.

If Trailhead and the ADG don't provide enough support, you may want to consider learning another programming language alongside Apex, like Python or JavaScript or Java, that have more breadth in introducing these core CS concepts, which you can then apply back to Apex.

Limits and Robustness

This is a more architectural commentary that you may want to keep in mind for the future.

In general, you need to be wary about @future callouts in triggers, because the future limit (50) is significantly lower than the maximum number of records you can receive in one invocation of the trigger (not one transaction, note!) - 200. If somebody inserts 51 Accounts in bulk, and you fire a future method per object, your code will throw an uncatchable LimitException.

One quick fix is to pass a collection, as above, to the future method, so that you only invoke it once. Then, the next limit you have to worry about is how many callouts you can make (100) in the future method. Again, if you're making one call-out per record, you're in trouble. Luckily, here it looks like you only need to make one call-out in total, so you're probably okay.

Another strategy to manage these limits issues, especially if you do need to call out for each record, is to pass a List of Account Ids to a Queueable Apex class, which runs asynchronously and can chain itself into a new Queueable invocation when it's done.

You can loop over the Ids in your Queueable and make callouts for the first N of them, and then chain into a new Queueable to process the remaining records. You'd have to tune N based on the callout limit (100) as well as the performance of your remote API, since there is a total callout time limit as well.

This will allow you to guarantee that all of your Accounts get processed, without burdening your triggers with a low limit on records.

Related Topic