Recently I needed to populate multiple values in a junction object by an end-user. While a flow wasn’t required, it was suggested to drive user adoption and ensure that the correct records were added.
In this case, a user would need to tie multiple accounts to an opportunity to calculate a roll-up of fields for a CPQ calculation. In this case, an opportunity would exist at the ultimate parent level.
While we could build a flow that parses the multi-picklist, the logic becomes complex, and the Flow becomes bloated and hard to read. For maintainability and performance, we chose to use Apex to split the picklist and return it for the Flow to process the rest. This is an excellent example of click and code, not just clicks.
Invocable Methods and Variables
Only one method in a class can be annotated as an InvocableMethod. An Invocable Method must be static and public or global. Invocable methods must also be an outer class and cannot have any other annotations.
We use Invocable Variables in this case since Invocable Methods can only have one input parameter. That parameter can only be a list or a list of lists of a primitive data type or sObject.
An Invocable Method can only return a list or list of lists of a primitive data type or sObject.
We will use an invocable action with invocable variables to call the Apex from within our Flow. The invocable variables will allow us to have some flexibility in how we use this code across multiple scenarios.
Only one method in our class can be annotated as an Invocable Method, so we will separate our methods. Our Requests class serves as a vehicle to bring in our Invocable Variables.
showOutputs returns a List of Lists as this is what our Flow is expecting. We grab our inputs, split our ids, and then query for our expected output. We create a
responseWrapper to add our Lists to a new List to satisfy the Flow requirement.
splitValues simply splits the String it is given and returns the list of Ids.
This class, as written, will only parse IDs, this satisfies the requirements of this Flow, but we may need to refactor this class to split other values.
The test for our code is relatively simple. We will create some test accounts so that we may use them later.
For the test itself, we’ll concatenate Account IDs together, separated by ‘;’ to simulate a string being passed in from the Flow.
We’ll then call our class and pass in simulated values; our test then asserts that our class is returning the values we want.
This test isn’t complete as we’re only checking one value and haven’t checked to see how the class would perform with improper values. We will refactor this test outside of this post.
The Flow for this scenario will display all accounts that share the same Ultimate Parent as the Account on the Opportunity.
These accounts are displayed in a screen flow with a multi-picklist. If Accounts are selected, we get the Accounts already added to the junction object to ensure we don’t add them repeatedly.
We then call our Apex action to use the class we created.
We utilize the Invocable Variables we created to use in our class within the Apex action. Notice that the text above the input value fields is the same language that we defined in the label parameter of the invocable variables.
As we’ve built the class to be reusable, notice that we can declare which fields we want to return from which object in the query.
Since we return a List of a List of Account objects in this case, we can now loop through this list, adding the Account to a Flow Variable Collection. Now that we have a collection, we can use the bulk create element that Flow provides for us.
In the future, we would probably want to build a custom LWC so that more data could be displayed for the user selecting records. An LWC will allow us to create a datatable that shows additional information and uses an interface similar to a list view. This will reduce the risk of our users picking the wrong Account if it has a similar name.
Using a custom LWC would eliminate the need for a multi-picklist in the screen Flow, since the datatable would be handling most of the record collecting.
We went with the solution described above to get a feature in production to solve the business’ requirements. We would have gone the LWC route given a longer time to develop this feature.