0

Given the following class with an @InvocableMethod:

public class InvocableFileCountAction { 
    public class FileCount {
        @InvocableVariable
        public Id recordId;

        @InvocableVariable
        public Integer fileCount;
        
        public FileCount(Id recId, Integer count) {
            this.recordId = recId;
            this.fileCount = count;
        }
    }
    
    @InvocableMethod(label='Get File Count' description='Gets the number of Files entries for a record' iconName='slds:standard:dashboard' callout='false') 
    public static List<FileCount> getFileCount(List<Id> recIds) {
        // cast incoming IDs to a set of Strings to query text field FauxPolymorphicLookup__c
        Set<String> textIds = new Set<String>((List<String>)recIds);
        List<FileCount> counts = new List<FileCount>();
        AggregateResult[] FileCounts = [SELECT FauxPolymorphicLookup__c recordId, Count(Id) fileCount
                                          FROM FileContainerObject__c
                                          WHERE FauxPolymorphicLookup__c IN :textIds
                                          GROUP BY FauxPolymorphicLookup__c];
        for (AggregateResult ar : FileCounts) {
            FileCount fc = new FileCount((Id)(ar.get('recordId')),(Integer)ar.get('fileCount')); 
            counts.add(fc);
        }
        return counts;
    }
}

How should I be handling the list of returned objects in the Flow? I'm calling the method from a record-triggered flow on Update to populate a field on the object. I assign the fileCount value returned from the Apex Action to the field in a single Update Records element for the 'record that triggered the Flow'. It works fine when a single record is updated, but trying to update multiple records gives the An unhandled fault has occurred in this flow error (it's just a test flow for trying the Action, so haven't put fault processing in).

What comes back to the Flow isn't a Collection, so I can't loop over it to find the object with the matching ID. Should the signature for the InvocableMethod be

public static List<Integer> getXFilesCount(List<Id> recIds)

but then return a List with a single element, the fileCount from fileCounts[0], the first AggregateResult? Or a List with all the fileCounts? with Does Salesforce magically associate the right result with the right record somehow in the bulkified scenario?

3
  • 2
    it is up to you to ensure that the 0th Id in recIds is represented as the 0th FileCount in the invocable results; same for 1st, 2nd, ... The invocable apex logic has to do this. Then SFDC will deliver the results to the correct Flow Interview. If the invocable is supposed to return a collection of values to each flow interview, then you return a list of lists
    – cropredy
    Commented Jan 5, 2023 at 18:47
  • @cropredy Thanks - so if the Flow engine takes care of matching the right result to the right record based on List order, then I don't need to return the record Id at all if it's not needed by the flow, right? And so could forgo the Apex type altogether and return a List<Integer> as long as I ensure it's in the same order as the input List<Id>? Commented Jan 5, 2023 at 18:59
  • 2
    Correct. If you have IDs a, b and c you just need to return an ordered integer list with count(a), count(b) and count(c).
    – Phil W
    Commented Jan 5, 2023 at 21:27

1 Answer 1

1

External actions in Flows are implicitly bulkified by SFDC. Invocable Apex is one such example of an external action

To take an example, imagine you have a record-triggered flow with an invocable Apex action that is triggered by Data Loader DML

  • The transaction has up to 200 Flow Interviews, one per recordId
  • These Flow Interviews run conceptually in parallel but each one waits as soon as it hits the Apex Action. When all 200 interviews hit the Apex Action, a single call is made to the invocable Apex.
  • The invocable Apex arguments are thus always a list.
  • If the invocable Apex returns values, it must always return a List, never a scalar.
  • The 0th argument in the input (in your case recIds) corresponds to the 0th element in the return list; The 1st argument in the input corresponds to the 1st element in the return list and so on.
  • When the invocable returns, SFDC distributes the results across the Flow Interviews and they resume in conceptual parallel mode

Sometimes, the job of the invocable is to return a collection to each Flow Interview. For example, if the input were a list of AccountIds, the result could be a list of Contacts for each AccountId. In this case, the invocable must be defined to return List<List<Contact>>. The 0th list goes back to the 0th Flow Interview corresponding to the 0th accountId, 1st list goes back to the 1st Flow Interview corresponding to the 1st accountId and so on

There is no requirement to return the input arg back in the result as SFDC takes care of the correspondence. Thus to answer your question:

so if the Flow engine takes care of matching the right result to the right record based on List order, then I don't need to return the record Id at all if it's not needed by the flow, right? And so could forgo the Apex type altogether and return a List as long as I ensure it's in the same order as the input List

Then Phil W's answer is correct

Correct. If you have IDs a, b and c you just need to return an ordered integer list with count(a), count(b) and count(c)

I can't stress enough that invocables should be written using proper bulkification techniques such as are used in triggers and that correlating the input elements to the result elements should be part of the invocable's logic (and should be thoroughly tested using testmethod asserts)

You must log in to answer this question.

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