2

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...

2
  • well this questions sure got a lot of big rep users replying :) Commented Nov 9, 2018 at 9:11
  • added solution in large answer below
    – paulK
    Commented Nov 9, 2018 at 23:03

4 Answers 4

9

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.

3
  • Thanks, David. I have updated my code to what I had a couple hours ago. My big problem here is calling the Class correctly. I am trying to just understand how to do this in a basic way first. Looking at this update, why would I be getting the Problem still. I'm not passing anything to the Class.
    – paulK
    Commented Nov 8, 2018 at 23:51
  • Great explanation! Commented Nov 9, 2018 at 19:48
  • Thanks again for all of this info, David. I'll be applying it going forward on the architecting. This was such a valuable explanation!
    – paulK
    Commented Nov 9, 2018 at 23:04
1

You are getting the error on

Decimal.valueOf(ticker.getTicker());

Because the getTicker() method returns void and the Decimal.valueOf requires a parameter and not void

It is important to note that you cannot get any results during the trigger since the future method will run in a completely different context.

Your design of a trigger doing a callout and using the results is not possible before the trigger ends

As for your future cannot call future error you need to add a check to only make the callout if the record was updated synchronously

If(!System.isFuture())....do future call..

You also cannot pass an object. You need to pass the Ids then query for the records.

In the future, please do not keep changing the question. Once you get an answer if you still have issues, make a new question. Otherwise it makes it hard for the answers to remain in context

0

In order to be able to call your class the method signatures have to match, meaning the parameters you define in the method match the parameters you're calling it with, AND the return type matches what you're assigning the result to.

So you have this apex class

public class tickerClass {
  @future(callouts=true)
  public static void getTicker() { } 
}

That means you can call tickerClass.getTicker() but there can't be any arguments. So you're good in this case.

This method then returns a void, which you pass to Decimal.valueOf(). Since that function can't take a void as a parameter you're going to get a compiler error.

Independently of all this, @future is asynchronous, so you don't ever return anything. So what I'd expect you want something like this. 1) after the account inserts (has to be after so we have an Id), 2) you call your future method, 3) it does a callout and updates the account. This has to be async since you can't do callouts in trigger for performance reasons, so the trigger decides it needs to do the callout, tells the class which accounts need a ticker, and the ticker class performs the callout, parses the values and updates the account.

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

And your callout class

public class tickerClass {
    @future(callout=true) 
    public static void setTicker(Account[] accounts) {
        // do you callout stuff 

        // set the ticker on the accts
        for (Account acct : accounts) {
           acct.Ticker_Start__c = (String) results.get('high');
        }

        // save the changes
        update accounts;
    }
}
5
  • 1
    One small correction is future method can take array of accounts id's but not array of accounts Commented Nov 9, 2018 at 14:03
  • Thanks for the help. I'm using your code and adding in my callout. Unfortunately, I'm still getting errors. See below: Class: 'public static void getTicker(Accout[] accounts) {' That line is firing this Problem: Future method do not support parameter type of List I have to have @future though, since I am firing a callout from a trigger. Trigger 'tickerClass.getTicker(Trigger.new);' That line is firing this Problem: Method does not exist or incorrect signature: void getTicker(List) from the type tickerClass Any thoughts?
    – paulK
    Commented Nov 9, 2018 at 17:36
  • @paulK, see codeyinthecloud's comment. You need to pass a list of Ids, not Accounts. There's an example of exactly what you need to do in the documentation here.
    – David Reed
    Commented Nov 9, 2018 at 18:17
  • @David, thanks for the info. I read through this but it doesn't explain the code on the TRIGGER side which is where I'm constantly getting the error. Even using their Class code I am still getting the same error when I try and call the Class. Can you give me a quick example of a trigger that would properly call the FutureMethodExample class from the documentation you provided?
    – paulK
    Commented Nov 9, 2018 at 18:28
  • 1
    I updated my own answer, I think it's too extensive for a comment.
    – David Reed
    Commented Nov 9, 2018 at 19:27
0

Thanks everyone for the help. Solution is posted below. The trigger and class work together to pull the data from the API and put into Salesforce.

Errors I was making:

  1. Not appropriately creating the recordId List no the Trigger.
  2. Not seeting the acct.Id to the a.Id on the Class.
  3. NOT calling and creating acct List via SOQL query on the Class.

If you have any suggestions to tighten the code up, I'm all ears! :D

trigger

trigger Account on Account (before update, after insert) {

    if (Trigger.isAfter) {

        // create list to hold the Account Record ID
        List<String> recordId = new List<String>();

        // create new Account to pull the Account ID
        for (Account acct : Trigger.new) {

            // new instance of the account is set to a
            Account a = new Account();

            // set the Id to the Id of the new Account
            a.Id = acct.Id;

            // add the Account ID to the recordId List
            recordId.add(a.Id);

            // call the processRecords method and pass the recordId List
            AccountClass.processRecords(recordId); 

        }

    }

    if (Trigger.isBefore) {
    // develop in future
    }

}

class

public class AccountClass {

    @future(callout=true)
    public static void processRecords(List<ID> recordId) {

        // external callout to the api
        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 callout is successful (code: 200)
        if (response.getStatusCode() == 200) {

            // parse the JSON
            Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());

            // create empty string variable
            String retVal = null;

            // change the api value (high) to a string and assign to string variable
            retVal = string.valueof(results.get('high'));

            // create a new decimal variable and change the retVal variable to decimal
            Decimal valueHigh = decimal.valueOf(retVal);

            // get record based on ID; can also put SOQL in place of acct in for loop
            List<Account> acct = [SELECT Name
                                 FROM Account
                                 WHERE Id IN :recordId];

            // for all of accounts that have the Account ID recordId
            for (Account a : acct) {

                // set the Ticker Start field to the external callout value for 'high'
                a.Ticker_Start__c = valueHigh;

                // update the record
                update a;

            }

        }

    }

}
1
  • there are still issues with this approach, you shouldn't be creating an account in the before insert trigger to access the id, that just creates a second account everytime one is created, if you need the id you should use an after trigger Commented Nov 20, 2018 at 18:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .