0

I am trying to setup a flow that allows a user to input two values: StartingNumber and EndingNumber.

I then what the user to press "Finish" and have all numbers between StartingNumber and EndingNumber to be created as Values within a Collection.

The end goal is to loop through the Collection and create records for each number between the StartingNumber and EndingNumber

How do I iterate through these two numbers and create Collection Values for each?

0

You don't actually need a collection to do this. To prove this out, I built a flow that creates tasks -- the first named "Task_[StartingNumber]"; the last, "Task_[EndingNumber]", based on the user's input. Here's how:

  • Set up your starting screen with fields for "starting_number" and "ending_number"
  • Create a variable, "current_number", and use an Assignment element to set it equal to the "starting number" screen input field.
  • Use a decision element to determine whether current_number is greater than ending_number. If it is, display an "all done" screen.

  • If current_number is less than or equal to ending_number, ...

  • ...use the Record Create element to generate a Task record

  • ...use an Assignment element to add 1 to the current_number value

  • ...return to the decision point

Visual Flow, not very "bulkified"

This works, but there's a problem: GOVERNOR LIMITS!

As sfdcfox pointed out below, this Flow would burn through your DML limit if you were creating > 150 records. In order to avoid this, we can use an InvocableMethod to call an Apex method from within your Flow.

public with sharing class CreateTasks {

    @InvocableMethod(label='Bulk create tasks' description='Create multiple task records and insert them all at once.')
    public static list<Integer> createRecords(List<InfoContainer> containers) {
        Integer numRecordsCreated = 0;
        Integer currentNum;

        List<Task> records = new list<Task>();
        List<Integer> returnVal = new List<Integer>();

        for(InfoContainer c : containers){
            system.debug('Container :: ' + c);
            if(c.startNum == null || c.endNum == null){
                throw new InfoContainerException('startNum and endNum are required.');
            }
            if(c.startNum > c.endNum){
                throw new InfoContainerException('startNum must be greater than or equal to endNum.');
            }

            currentNum = c.startNum;

            while(currentNum <= c.endNum){
                Task record = new Task();
                record.Name = 'Record ' + currentNum;
                records.add(record);

                numRecordsCreated ++;
                currentNum ++;

            }
            returnVal.add(numRecordsCreated);
        }
        insert records;

        return returnVal;
    }

    public class InfoContainer {
        @InvocableVariable(label='Start Number')
        public Integer startNum;

        @InvocableVariable(label='End Number')
        public Integer endNum;

    }

    private class InfoContainerException extends Exception {}

}

Use the InvocableMethod just like any other element in your Flow pallete, setting its Start Number and End Number values like this:

Visual Flow with InvocableMethod

(Not pictured here: A Flow fault path to handle exceptions thrown by the InvocableMethod. You should include one!)

Unit Tests:

@isTest
public class CreateTasks_Test {

    @isTest
    static void createTasks_happy() {
        List<CreateTasks.InfoContainer> containers = new List<CreateTasks.InfoContainer>();
        CreateTasks.InfoContainer container = new CreateTasks.InfoContainer();
        container.startNum = 1;
        container.endNum = 4;

        containers.add(container);
        List<Integer> returnValue = CreateTasks.createRecords(containers);
        system.assertEquals(returnValue[0], 4);

        confirmTaskCount(4);

    }

    @isTest
    static void createTasks_sad(){
        List<CreateTasks.InfoContainer> containers = new List<CreateTasks.InfoContainer>();
        CreateTasks.InfoContainer container = new CreateTasks.InfoContainer();
        container.endNum = 4;

        containers.add(container);
        system.assert(confirmExceptionThrown(containers));

    }

    @isTest
    static void createTasks_bad(){
        List<CreateTasks.InfoContainer> containers = new List<CreateTasks.InfoContainer>();
        CreateTasks.InfoContainer container = new CreateTasks.InfoContainer();
        container.startNum = 4;
        container.endNum = 1;

        containers.add(container);
        system.assert(confirmExceptionThrown(containers));

    }

    @isTest
    static void createLotsOfTasksInOneTransaction() {
        List<CreateTasks.InfoContainer> containers = new List<CreateTasks.InfoContainer>();
        CreateTasks.InfoContainer container = new CreateTasks.InfoContainer();
        container.startNum = 1;
        container.endNum = 400;

        containers.add(container);
        List<Integer> returnValue = CreateTasks.createRecords(containers);
        system.assertEquals(returnValue[0], 400);

        confirmTaskCount(400);

    }

    @isTest
    static void createTasksBulkified() {
        List<CreateTasks.InfoContainer> containers = new List<CreateTasks.InfoContainer>();
        CreateTasks.InfoContainer container;

        for(Integer i=100; i > 0; i--){
            container = new CreateTasks.InfoContainer();
            container.startNum = 1;
            container.endNum = 4;

            containers.add(container);

        }
        List<Integer> returnValue = CreateTasks.createRecords(containers);
        system.assertEquals(returnValue.size(), 100);

        confirmTaskCount(400);

    }

    private static void confirmTaskCount(Integer expectedCount){
        Integer taskCount = [SELECT COUNT() FROM Task];
        system.assertEquals(taskCount, expectedCount, taskCount + ' records were found, but we expected to find ' + expectedCount);

    }

    private static Boolean confirmExceptionThrown(List<CreateTasks.InfoContainer> containers){
        try{ 
            CreateTasks.createRecords(containers);

        } catch(CreateTasks.InfoContainerException e){
            return true;
        }
        return false;
    }


}
  • The problem with this approach is that DML limits apply, but is probably the best solution given the limitations off low. – sfdcfox Sep 24 '18 at 0:50
  • @sfdcfox -- Ooh. I thought the records would only be written to the database at the end of the flow. I must have been thinking about the way it works when multiple interviews of the same flow are running in the same execution context. (Reference: developer.salesforce.com/docs/…). Isn't there a way to create records, add them to a collection, and write them to the DB all at once? – Shane Steinfeld Sep 24 '18 at 1:16
  • If you find one, let me know. Last time I tried, I literally had to resort to using an InvocableMethod to achieve decent results. Flow really isn't meant to handle situations like this, as far as I can tell. – sfdcfox Sep 24 '18 at 1:20
  • @sfdcfox -- I'll play with it for a bit. An invocableMethod might be the way to go. I'll write one as penance if I can't find a way to do this without code. ;) Thanks! – Shane Steinfeld Sep 24 '18 at 1:22
  • 1
    you'll need a flow fault path as well since invocable is throwing exceptions – cropredy Sep 24 '18 at 5:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.