SFDC Stop - Always the latest about Salesforce


Full Tutorial Series with videos, free apps, live sessions, salesforce consulting and much more.


Telegram logo   Join our Telegram Channel

Sunday 24 March 2024

FlowError: The number of results does not match the number of flow interviews | Building invocable actions The Right Way!

Hello Trailblazers,

In this post we're going to learn, how we can build apex invocable actions The Right Way. It might be possible that you've faced this issue in the past:

FlowError: The number of results does not match the number of flow interviews

Today, we're going to build an apex invocable action, call it from flow in a scenario such that it fails and then fix the error to make sure it works perfectly fine. Let's Begin!

Use Case

We have a custom object named Application (Application__c), the details of that object are provided below:
As you can see above, our name field is Application ID which is an auto number. We have 3 custom fields:

1. Candidate Name (CandidateName__c): This is a text field with length 255. It'll store the name of our candidate who has submitted the application
2. Candidate Phone (CandidatePhone__c): This is a phone field. It'll store the phone number of our candidate
3. Lead (Lead__c): This is a lookup to standard lead object. For each new application, if the candidate is not present as a lead in salesforce, we'll create a new lead in salesforce and will tag that lead to our application record. If a lead is already present in salesforce, we'll tag that existing lead to our application record as well.

Automation

How do we check whether we should create a new lead or use an existing lead?

We're going to do that using candidate's phone number in the application. If there exists a lead with the same phone number as that of a candidate, we're going to tag that lead to the application record as it's created. Otherwise, we're going to create a new lead with that phone number and candidate's name and we're going to tag that lead to our application record.

So, we can say that our Application's CandidatePhone__c will map to Lead's Phone field. We need an automation (flow) to create lead records automatically and tag them to application records as these are created. We'll create that flow soon.

Platform Encryption

For security reasons, we've turned on platform encryption in our salesforce org and the lead's Phone field is encrypted as shown below:
If we click on Select Fields button above in Encrypt Standard Fields section, we'll get the below page, where we've encrypted Lead's Phone field as shown below:
This encryption will create one challenge in building our flow, which we'll see soon. Let's build a flow now.

Building a Flow

We need a Record Triggered flow which will fire when an application record is created. It'll check if an existing lead record is present with Phone = CandidatePhone__c or not. If yes, it'll tag that record with the application record. Otherwise, it'll create one new lead record using CandidatePhone__c and CandidateName__c and will tag that new lead record to our application record.

Got to Setup, search for flow, and click on the New Flow button as shown below:
You'll get the below screen:
Choose Record-Triggered flow as shown above and click Create.

Choose the object as Application, this flow will run when an application record is created, in After context as shown below:
Click on the Save button to save the flow. The details are provided below:
Flow Label: Tag Lead to Application
Flow API Name: Tag_Lead_to_Application
Description: This flow will tag a lead record to the application record as it's created.

Click on Save button in the dialog to save the flow.

Now, our flow is saved. The first step is to query lead records where Phone = CandidatePhone__c. We're going to use a Get element to query records as shown below:
We only need the Lead Id here, so we're querying only that:

Problem in the Flow

Let's save the flow once by clicking the Save button. As you try to save the flow, you'll get an error as shown below:
It says: Because Lead's Phone field is encrypted, our flow cannot reference that field. We need to remove this reference in order to save the flow. This means that we cannot query our existing lead records by filtering through Phone field. In order to achieve this functionality now, it's time to move to Invocable Apex.

Resolution: Invocable Apex

We're going to create a new apex class which will help us in querying the leads by phone number. The code for the same is provided below:
public class LeadQueryAction {

    /**
    * This method will query leads by phone numbers and return the list of lead ids
    */
    @InvocableMethod(label='Get Leads by Phone Numbers' description='Return lead records filtered by phone numbers' category='Lead')
    public static List<Id> queryLeadsByPhoneNumbers(List<String> phoneNumbers) {
        Map<Id, Lead> leadsMap = new Map<Id, Lead>([SELECT Id FROM Lead WHERE Phone IN :phoneNumbers]);
        System.debug(phoneNumbers);
        System.debug(leadsMap.keySet());
        return new List<Id>(leadsMap.keySet());
    }
}
As you can see above, we have a class named LeadQueryAction. This class has a method named queryLeadsByPhoneNumbers(List<String> phoneNumbers). This method will receive the list of phone numbers from flow, will query the leads using those phone nunbers and will return the list of lead ids. We're querying lead records using SOQL query: [SELECT Id FROM Lead WHERE Phone IN :phoneNumbers];. We're converting the queried lead records list directly into a Map<Id, Lead> named leadsMap so that we can get the lead ids easily using this map. Finally, we're returning the list of lead ids by converting lead ids set into a list using new List<Id>(leadsMap.keySet());

Also, note that in the @InvocableMethod annotation, we've specified the label as Get Leads by Phone Numbers and description as Return lead records filtered by phone numbers. We've also specified the category as Lead. This label will be visible in our flow when we'll search for our invocable action.

Our apex class is ready now, let's get back to our flow and call this method.

First of all, let's delete the Get element as we're going to query lead records using our invocable apex method now:

Click on the + icon again and search for Get Leads by Phone Numbers action as shown below:
Notice that our invocable method's label and description are visible here. Choose this action and you'll get the New Action dialog as shown below:
Alternatively, you can also search for action, click the Action option and then search for Get Leads by Phone Numbers action as shown below:
You'll get the new Action dialog here as well as shown below:
In our New Apex Action dialog, fill the following information as shown below:
Label: Get Existing Leads
API Name: Get_Existing_Leads
Description: This element will get ids of existing lead records where Phone = CandidatePhone__c

Click on Include button to pass information to phoneNumbers parameter of our invocable apex method and let's bind it with CandidatePhone__c field of our current application record {!$Record.CandidatePhone__c}.

Click on Done.

Now, our Get Existing Leads apex action will provide us the id of the lead (if it exists) which is present in our salesforce org where Phone = CandidatePhone__c for our current application record.

We need to check, if a lead id is returned by our method or not. If a lead id is returned, the existing lead id will be populated in our application's Lead__c lookup. Otherwise, we'll create a new lead record and will populate it's id in our application's Lead__c lookup field.

To check this, we are going to add a decision element as shown below:

Label: Is lead present?
API Name: Is_lead_present
Description: This decision element will check if an existing lead with the candidate's phone number is present in the system or not

Outcomes will be Yes and No (the default outcome from the decision is renamed to No). For the outcome to be Yes, we're checking that the Text from Get_Existing_Leads {!Get_Existing_Leads} (which is nothing but our Lead Id) should not be Blank.

If the outcome of the decision is Yes, we'll update our applicaton record using an update element as shown below:

We'll choose Update Triggering Record option as we want to update lead id on the same record which fired the flow:

The details added in this Update Triggering Record element are provided below:

Label: Update existing lead id on application
API Name: Update_existing_lead_id_on_application
Description: This element will update the Lead__c on application with the lead id received from Get Existing Leads invocable action
How to Find Records to Update and Set Their Values: Use the application record that triggered the flow
Set Field Values for the Application Record: Lead__c = {!Get_Existing_Leads}

If outcome of the decision is No, we'll create a new Lead record and tag that to the application record as shown below:

We'll add a Create Records element in our No path as shown above. The details added in the Create Records element are provided below:

Label: Create new lead
API Name: Create_new_lead
Description: This element will create a new lead record using the candidate details from application record
How Many Records to Create: One
How to Set the Record Fields: Use separate resources, and literal values
Object: Lead
Set Field Values for the Lead:
LastName = {!$Record.CandidateName__c}
Phone = {!$Record.CandidatePhone__c}

As Company field is mandatory on lead, let's add that as well in our Create Records element with a value as SFDC Stop as shown below:
Company = SFDC Stop
This element will create a new lead record for us. Let's update our application record with this lead id. For this, we'll add an Update Triggering Record element again as shown below:
The details added in the Update Triggering Record element are provided below:

Label: Update new lead id on application
API Name: Update_new_lead_id_on_application
Description: This element will update the Lead__c on application with the lead id received from Create new lead action
How to Find Records to Update and Set Their Values: Use the application record that triggered the flow
Set Field Values for the Application Record: Lead__c = {!Create_new_lead}

Let's save our flow by clicking the Save button.
Once saved, activate the same by clicking the Activate button.
Now, it's time to test our flow!

Let's go to the Applications tab in our salesforce org and create a new Application record as shown below:
We've kept the Lead lookup as empty. The Candidate Name is Richard Hendricks and Candidate's Phone is 9999999999. As we click on Save, our flow will create a new lead record for us, as a lead record with this phone number is not already present in salesforce and it'll tag the newly created lead to our application record as shown below:
As you can see above, a lead record named Richard Hendricks is tagged. The Phone is the same as Candidate Phone in our application record and the Company is SFDC Stop.

Now, let's try creating another application record using the same details as shown below:
You'll notice that as the record is created, the same (already existing) lead is tagged to this application record:
Note: You can verify the lead ids of both the lead records tagged to application records to make sure that the same lead record is tagged to both the application records.

This means that our flow and invocable apex method is working perfectly fine. But What will happen if we have more than one application records? Let's test it!

Flow: Bulk Testing

Let's consider a scenario where we're creating two application records in a single transaction. Both the application records should be connected to the same lead record. The code snippet to create these applications is provided below:
List<Application__c> applications = new List<Application__c>();
applications.add(new Application__c(CandidateName__c = 'Richard Hendricks', CandidatePhone__c = '9999999999'));
applications.add(new Application__c(CandidateName__c = 'Richard Hendricks', CandidatePhone__c = '9999999999'));
insert applications;
As you can see above, we're inserting a list of applications with 2 records. Both the records are by the same candidate, so, ideally the same lead record should be tagged to both the applications. When we execute this code snippet, we get the result as shown below:
As you can see above, our flow is failing and we're getting an error. In our case, it's not showing the exact issue but in some scenarios it'll show the actual error like: FlowError: The number of results does not match the number of flow interviews

This error basically means that the invocable method is not returning the list of same size as it was passed to the method while calling it from flow. Let's debug this by using the below code snippet:
List<String> phoneNumbers = new List<String>{'9999999999','9999999999'};
System.debug(LeadQueryAction.queryLeadsByPhoneNumbers(phoneNumbers));
In the above snippet, we're calling our invocable action method directly and passing a list of two phone numbers which are exactly the same as of our Richard Hendricks lead record. The output when the above code snippet is executed is provided below:
If you remember, our invocable method has some System.debug() statements, I'm sharing those again below for your reference:
The first debug statement is displaying the list of phone numbers received by our invocable method in parameter. It's showing both the phone numbers as: (9999999999, 9999999999). This is perfectly all right. 

We queried our lead records using these phone numbers and got the following ids in our leadsMap.keySet() which is used in the second debug statement: {00QH40000010giZMAQ}. As you can see, we're getting only one lead id in this set as there's a single lead record present in the system with this phone number.

The third debug statement which is coming from System.debug(LeadQueryAction.queryLeadsByPhoneNumbers(phoneNumbers)); is showing the same lead id in a list instead of a set as: (00QH40000010giZMAQ). This means that the list of lead ids which is returned from our invocable method is having only one lead id, however, we passed 2 phone numbers in this method to query and get our related lead records. 

This is why our flow is failing because when our flow is running for 2 records created in the same transaction, we have 2 flow interviews. As the flow is bulkified, the phone numbers list passed to our invocable method consist of 2 phone numbers however our method returned only a single lead id in the list of ids. Therefore, The number of results (lead ids) does not match the number of flow interviews. These metrics (debug statements) are also visible in the earlier logs when we tried to create our application records as shown below:
You can see above that the phoneNumbers list has two phone numbers whereas the leadsMap.keySet() has a single lead id.

In order to make our invocable method work correctly, we need to return the list of lead ids of same size as the size of our input list i.e. the list of phone numbers. Let's fix our invocable method now.

Issue Resolution for "FlowError: The number of results does not match the number of flow interviews"

In order to fix our invocable method, we'll do the following:

1. Query our lead records.
2. Create a map of lead phone to lead id
3. Loop our phone numbers and check: If we have a lead id corresponding to the current phone number, add it to the lead ids list, otherwise, add null to the lead ids list.
4. Return the list of lead ids.

This will ensure that the order of phone numbers received and the lead ids corresponding to those phone numbers remains aligned, it's important to ensure that our flow behaves correctly. The updated code for our invocable action class is provided below:
public class LeadQueryAction {

    /**
    * This method will query leads by phone numbers and return the list of lead ids
    */
    @InvocableMethod(label='Get Leads by Phone Numbers' description='Return lead records filtered by phone numbers' category='Lead')
    public static List<Id> queryLeadsByPhoneNumbers(List<String> phoneNumbers) {
        List<Id> leadIds = new List<Id>();
        List<Lead> leads = [SELECT Id, Phone FROM Lead WHERE Phone IN :phoneNumbers];
        Map<String, Id> leadPhoneNumberToIdMap = new Map<String, Id>();
        for(Lead lead : leads) {
            leadPhoneNumberToIdMap.put(lead.Phone, (Id) lead.Id);
        }
        for(String phoneNumber : phoneNumbers) {
            if(leadPhoneNumberToIdMap.containsKey(phoneNumber)) {
                leadIds.add(leadPhoneNumberToIdMap.get(phoneNumber));
            } else {
                leadIds.add(null);
            }
        }
        return leadIds;
    }
}
As you can see above, we created a list of lead ids named leadIds. This list will store our lead ids which we'll return from our method. Then we queried the lead records as we were doing earlier. This time, we're storing it in a list named leads.

Now, we created a Map<String, Id> named leadPhoneNumberToIdMap. This map will store our lead phone number and the corresponding lead id. We looped all the queried lead records to populate this map. Finally, we looped the phone numbers which we received in our method, we then checked: if our leadPhoneNumberToIdMap contains the current phone number as a key, we'll add the corresponding lead id to the leadIds list, otherwise, we'll add null to our leadIds list. Adding null basically means that for the current phone number, we don't have any lead present in salesforce. Note that a list can have multiple null values.

Let's save our updated apex class and run the below code snippet to test our flow once again. This time we'll create 4 application records as shown below:
List<Application__c> applications = new List<Application__c>();
applications.add(new Application__c(CandidateName__c = 'Richard Hendricks', CandidatePhone__c = '9999999999'));
applications.add(new Application__c(CandidateName__c = 'Erlich Bachman', CandidatePhone__c = '9999999991'));
applications.add(new Application__c(CandidateName__c = 'Richard Hendricks', CandidatePhone__c = '9999999999'));
applications.add(new Application__c(CandidateName__c = 'Gavin Belson', CandidatePhone__c = '9999999992'));
insert applications;
If you notice above, the first and third application records have the same candidate Richard Hendricks with the same phone number as: 9999999999, so, our flow should link the existing lead to both the application records. However, the second and fourth application records have different candidates i.e. Erlich Bachman and Gavin Belson with different phone numbers. So, our flow should create new leads and link them with our application records.

Currently, we only have 2 application records in the system that we created earlier as shown below:

Now, as we execute our code snippet, we get the below output:
Notice that we got 4 new applications created as shown above. The first and third application records are linked with the same lead Richard Hendricks, however, the second and fourth records are linked with new lead records i.e. Erlich Bachman and Gavin Belson

This time our invocable method is working Perfectly Fine! and the flow is not failing. You can add debug statements in this method to see how it works. However, I'm displaying the output we get from our invocable method as it's called internally using the below code snippet:
List<String> phoneNumbers = new List<String>{'9999999999', '9999999993', '9999999999', '9999999994'};
System.debug(LeadQueryAction.queryLeadsByPhoneNumbers(phoneNumbers));
If you notice above, the first and third phone numbers are the same i.e. 9999999999 however, the second and fourth phone numbers are different i.e. 9999999993 and 9999999994 as was there in our application records. I didn't use 9999999991 and 9999999992 here because now we have leads present in the system linked with these phone numbers. When we execute this code snippet, the output list of lead ids which we got from our method is as shown below:
As you can see above, in our output, the first and third entry in our list is the lead id of Richard Hendricks lead and it's the same. However, the second and fourth entry in our list is coming as null because there are no leads for these phone numbers present in salesforce.

Therefore, we passed 4 phone numbers and got a list of 4 elements i.e. lead ids. Now, we can say that: The number of results match the number of flow interviews.

That's all for this tutorial everyone, I hope you liked it, let me know your feedback in the comments down below.

Happy Holi! Get my Mastering Lightning Datatable in Salesforce LWC course at best price using the coupon code "HAPPYHOLI". You can get the same by clicking this link: https://www.udemy.com/course/mastering-lightning-datatable-in-salesforce-lwc/?couponCode=HAPPYHOLI

Happy Trailblazing!!

No comments:

Post a Comment