Adding Extra Power to Your Visual Flows

This post is to show you the power of Visual Flows and how they can successfully interact with code. This will help replace some of the Standard Development cycle. By using Visual Flows you write less unit tests, changes can be made rapidly, you don’t need to use up your developer’s time, and deployment can be swiftly done.

When can we use Visual Flows to accomplish this over standard Workflows or Process Builder?

salesforce workflows
salesforce visual flows

In short, you generally want to use Visual Flows when you have a requirement similar to traditional workflows but it requires some sort of User interaction. (Note: There are exceptions to this with Headless Flows)

Some specific examples of using Visual Flows:

If you are currently not familiar with Visual Flows, you might want to check out this trailhead before continuing: https://developer.salesforce.com/trailhead/business_process_automation/flow

Flows tend to break down when you need to get more dynamic past the User interaction piece.

We will be discussing 2 such examples.

  • You want to run a SOSL or dynamic query rather than a static query on an object.
  • You might want to dynamically route your User or do different operations depending on earlier actions in your flow.

Our First goal

Create a way to search the database for matches in both Leads and Contacts.

The first step is to create an Apex class that is marked as ‘global’.

global class DFInvocable
{
 
}

I also create a wrapper to put the relevant info I need from the objects inside my global class which I also need to mark as global.  The @InvocableVariable annotation is what allows the Visual Flows to access the variable.  For my current usage, I only need to know the object type and the Id of the record.

global class LookupObjWrapper
{
  @InvocableVariable(label=’Object Type’)
  public String objType;
 
  @InvocableVariable(label=’Record Id’)
  public String recordId;
}

Inside of my global class, I also need a method marked as an Invocable Method.  This is what the Visual Flow actually calls.  Even though my method uses a List of items in both the return and parameters, it is still restricted to a size of 1.  I can’t find any relevant info from Salesforce that explains why there is this size restriction if it’s expecting a collection.

@InvocableMethod(label=’Find Records’ description=’Searches the database for matches in Leads and Contacts’)
public static List<LookupObjWrapper> getObjectId(List<String> names)
{
  //SOSL query to look through both Leads and Contacts
  List<List<SObject>> soslObjs = [FIND :names[0] IN NAME FIELDS RETURNING Contact, Lead (Id)];
 
  //Wrapper declaration to return to our flow
  List<LookupObjWrapper> wraps = new List<LookupObjWrapper>();
  for (List<SObject objs: soslObjs)
  {
    //Describe method allows you to get the object type by Id
    String objectType = obj.Id.getSObjectType().getDescribe().getName();
    LookupObjWrapper wrap = new LookupObjWrapper();
    wrap.objType = objectType;
    wrap.recordId = obj.Id;
    wrap.add(wrap);
  }
}

When you put them all together:

global class DFInvocable
{
  @InvocableMethod(label=’Find Records’ description=’Searches the database for matches in Leads and Contacts’)
  public static List<LookupObjWrapper> getObjectId(List<String> names)
  {
    //SOSL query to look through both Leads and Contacts
    List<List<SObject>> soslObjs = [FIND :names[0] IN NAME FIELDS RETURNING Contact, Lead (Id)];
 
    //Wrapper declaration to return to our flow
    List<LookupObjWrapper> wraps = new List<LookupObjWrapper>();
    for (List<SObject objs: soslObjs)
    {
      //Describe method allows you to get the object type by Id
      String objectType = obj.Id.getSObjectType().getDescribe().getName();
      LookupObjWrapper wrap = new LookupObjWrapper();
      wrap.objType = objectType;
      wrap.recordId = obj.Id;
      wrap.add(wrap);
    }
  }
 
  global class LookupObjWrapper
  {
    @InvocableVariable(label=’Object Type’)
    public String objType;
 
    @InvocableVariable(label=’Record Id’)
    public String recordId;
  }
}
<pre>

Create a simple Visual Flow.

  1. Create text variables called objId and type.
  2. Create an entry screen that asks for the Name of a Lead or Contact.
  3. Have your entry screen move to the DFInvocable component. You can find this on the left side selection under Palette -> Apex.
  4. In this component, place in the input the value from the first screen into the target ‘names’ here.
  5. Move to the Outputs tab and map type to Object Type and objId to Record Id.
  6. Create another screen that simply displays the objId and type variables.
  7. Go ahead and Save. I named my Visual Flow DF_Presentation but you can name yours whatever you want.  Just make sure you replace references in your pages later to this.
salesforce visual flow example

Put this flow into a very simple Visualforce page.

<apex:page>
  <flow: interview name=”DF_Presentation” />
</apex:page>

Now when you run this, you will get:

Search for a Lead that exists in your system that does not share a name with a Contact or another Lead.

Add Error Handling

What happens when more than a single record is returned?  To cause this to happen, search for a Lead or Contact that shares a name with another Lead or Contact.

This is a very simple fix. Simply create a new screen and add another link from the DFInvocable to it. It automatically marks it as a ‘FAULT’ link.  Because of the way I wrote my code, I know that the error that happens will be from multiple records being added and I simply tell it to return a message specific to this. Optionally, you can display the actual internal system error message: {!$Flow.FaultMessage}.

Updated DF_Presentation.flow

salesforce visual flow example 7

Now it handles when more than a single record is found:

Dynamic Record Routing

What if we want to do something different if a Lead is found vs a Contact?  Maybe we decide that any Leads coming through this route should immediately be converted and Contacts should be taken to their view page. Go ahead and create a Controller that has a variable for your flow.

public with sharing class DFPresentationController
{
  //I make this public so that it can be referenced in the VF page
  public Flow.Interview.DF_Presentation_3 dfFlow{get; set;}
 
  public PageReference redirectUser()
  {
    if (dfFlow.type == 'Lead')
    {
      //Lead is found, we need to convert it
      return new PageReference('/' + convertLead(dfFlow.objId));
    } else if (dfFlow.type == 'Contact')
    {
      //Contact is found, let's go to it
      return new PageReference('/' + dfFlow.objId);
    } else if (dfFlow.type == 'Not Found')
    {
      //Nobody is found, let's create a new contact
      List&amp;lt;String&amp;gt; name = dfFlow.name.split(' ');
      return new PageReference('/003/e?name_lastcon2=' + name[1] + '&amp;amp;name_firstcon2=' + name[0]);
    }
 
    return null;
  }
 
  //Basic Lead conversion method
  public Id convertLead(Id leadId)
  {
    Database.LeadConvert lc = new Database.LeadConvert();
    lc.setLeadId(leadId);
 
    LeadStatus convertStatus = [SELECT MasterLabel FROM LeadStatus WHERE IsConverted = true LIMIT 1];
    lc.setConvertedStatus(convertStatus.MasterLabel);
 
    Database.LeadConvertResult lcr = Database.convertLead(lc);
    return lcr.getContactId();
  }
}

On my updated Visualforce page, this looks like it should work:

<apex:page controller=”DFPresentationController”>
  <flow:interview name=”DF_Presentation” interview=”{!dfFlow}” finishLocation=”{!redirectUser}” />
</apex:page>

Unfortunately, this doesn’t work.  The reason is that it fetches redirectUser as a property/variable.  The method would need to be called getRedirectUser, but when it actually gets called it wouldn’t know about the flow variables yet by that point.

To make this work, we need to make some slight adjustments to the controller.  I created a new method that basically just checks to see if the type has been set or not yet.

public String getFinishedStatus()
{
  if (dfFlow.type != null)
  {
    return ‘run script’;
  }
  return ‘not ready’;
}

Updated VF page to work around the System:

<apex:page controller=”DFPresentationController”>
  <flow:interview name=”DF_Presentation” interview=”{!dfFlow}” rerender=”outputPnl” />
  <apex:form>
    <apex:outputPanel id=”outputPnl”>
      <script>
        if (‘{!finishedStatus}’ == ‘run script’)
        {
          redirectUsr();
        }
      </script>
    </apex:outputPanel>
    <apex:actionFunction name=”redirectUsr” action=”{!redirectUser}” />
  </apex:form>
</apex:page>

No changes need made to your Visual Flow at this point.  It will move to the final section in your Visual Flow but after a few moments the server side code will finish executing and it will navigate you to where you want.

You should have learned how to call Apex from your Visual Flow, how to handle simple errors, and how to dynamically route your User from your Visual Flow.

Leave a Comment

Your email address will not be published. Required fields are marked *

We're celebrating 20 years! Read about our journey here.

Party horn and confetti
Scroll to Top