How to Use an Apex-Defined Object with my Datatable Flow Component

How to Use an Apex-Defined Object with the Datatable Flow Component

I’ve updated my Datatable Lightning Web Component for Flow Screens to support a User Defined (also know as an Apex-Defined) object.

See my Flow and Process Builder List View with Batch Delete App for an example.

To work with an Apex-Defined object in your Flow, you need to create an Apex Descriptor Class for the object.

SampleClassDescriptor.cls

// Apex-Defined Variable Sample Descriptor Class
public with sharing class SampleClassDescriptor {

    // @AuraEnabled annotation exposes the methods to Lightning Components and Flows
    @AuraEnabled
    public String field1;

    @AuraEnabled
    public String field2;

    @AuraEnabled
    public Boolean field3;

    @AuraEnabled
    public Integer field4;    

    // Define the structure of the Apex-Defined Variable
    public SampleClassDescriptor(
            String field1,
            String field2,
            Boolean field3,
            Integer field4
    ) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
    }

    // Required no-argument constructor
    public SampleClassDescriptor() {}
}

In your Flow, you can create and use Apex-Defined record and record collection variables by referencing your Apex Class.

All of the fields in your variable will be available to use in the Flow.

In this sample Flow, I am setting field values in individual records as seen above.

I then add each record to the Apex-Defined record collection variable.

The Datatable component expects a serialized string of the object’s records and fields like the text seen here.

[{"field1":"StringRec1Value1","field2":"StringRec1Value2","field3":false,"field4":10},
{"field1":"StringRec2Value1","field2":"StringRec2Value2","field3":true,"field4":20},
{"field1":"StringRec3Value1","field2":"StringRec3Value2","field3":true,"field4":30}]

Since you can create Apex Flow Actions to work with your Apex-Defined object, I created an action that converts an Apex-Defined record collection to a serialized string that can be passed to the Datatable component. The action will also convert a serialized string back to a record collection. This can be useful in a Flow where you want to act on a collection of selected or edited records that get passed back to the Flow by the Datatable component.

You can use this code as a template for your own Apex actions designed to work with a Flow.

TranslateApexDefinedRecords.cls

/** 
 * 
 *  Sample Apex Class Template to get data from a Flow, 
 *  Process the data, and Send data back to the Flow
 * 
 *  This example translates an Apex-Defined Variable 
 *  between a Collection of Object Records and a Seraialized String
 * 
 *  Eric Smith - May 2020
 * 
 * 
**/ 

public with sharing class TranslateApexDefinedRecords {         // *** Apex Class Name ***

    // Attributes passed in from the Flow
    public class Requests {
    
        @InvocableVariable(label='Input Record String')
        public String inputString;

        @InvocableVariable(label='Input Record Collection')
        public List<SampleClassDescriptor> inputCollection;     // *** Apex-Defined Class Descriptor Name ***

    }

    // Attributes passed back to the Flow
    public class Results {

        @InvocableVariable
        public String outputString;

        @InvocableVariable
        public List<SampleClassDescriptor> outputCollection;    // *** Apex-Defined Class Descriptor Name ***
    }

    // Expose this Action to the Flow
    @InvocableMethod
    public static List<Results> translateADR(List<Requests> requestList) {

        // Instantiate the record collection
        List<SampleClassDescriptor> tcdList = new List<SampleClassDescriptor>();    // *** Apex-Defined Class Descriptor Name ***

        // Prepare the response to send back to the Flow
        Results response = new Results();
        List<Results> responseWrapper = new List<Results>();

        // Bulkify proccessing of multiple requests
        for (Requests req : requestList) {

            // Get Input Value(s)
            String inputString = req.inputString;
            tcdList = req.inputCollection;


// BEGIN APEX ACTION PROCESSING LOGIC

            // Convert Serialized String to Record Collection
            List<SampleClassDescriptor> collectionOutput = new List<SampleClassDescriptor>();   // *** Apex-Defined Class Descriptor Name ***
            if (inputString != null && inputString.length() > 0) {
                collectionOutput = (List<SampleClassDescriptor>)System.JSON.deserialize(inputString, List<SampleClassDescriptor>.class);    // *** Apex-Defined Class Descriptor Name ***
            }

            // Convert Record Collection to Serialized String
            String stringOutput = JSON.serialize(tcdList);

// END APEX ACTION PROCESSING LOGIC


            // Set Output Values
            response.outputString = stringOutput;
            response.outputCollection = collectionOutput;
            responseWrapper.add(response);

        }
        // Return values back to the Flow
        return responseWrapper;
    }
}

TranslateApexDefinedRecordsTest.cls

@isTest
public with sharing class TranslateApexDefinedRecordsTest {

    static testMethod void test() {

        List<SampleClassDescriptor> inputList = new List<SampleClassDescriptor>();

        TranslateApexDefinedRecords.Requests testRequest = new TranslateApexDefinedRecords.Requests();

        testRequest.inputString = '[{"field1":"value1","field2":"value2"},{"field1":"value31","field2":"value4"}]';
        testRequest.inputCollection = inputList;

        List<TranslateApexDefinedRecords.Requests> testRequestList = new List<TranslateApexDefinedRecords.Requests>();
        testRequestList.add(testRequest);

        List<TranslateApexDefinedRecords.Results> testResponseList = TranslateApexDefinedRecords.translateADR(testRequestList);
        system.debug('RESPONSE - '+testResponseList);
        system.assertEquals(testResponseList[0].outputCollection.size(), 2);
    }

}

When you configure the attributes for the Datatable in your Flow, you need to be aware of these settings:

  1. Because of how the component is designed you still need to pick an SObject here. It doesn’t matter what you select because the User Defined object will be displayed instead.
  2. Set this attribute to True when using a User Defined object.
  3. This attribute is not used for User Defined objects.
  4. Specify your serialized record string here instead of using a SObject collection in #3.
  5. When you are using a SObject collection, the Datatable component gets information about all of the fields from the system. For a User Defined object you need to specify the type of data for each column. This can be left blank if all of the columns are text fields.
  6. For Currency, Number and Percent fields you can specify the number of places to use after the decimal point, The default is 0.

There are separate output parameters for Selected and Edited User Defined objects as well.

The (User Defined) outputs will be serialized record strings rather than SObject collections. Be sure to reference the correct ones based on how you assigned the True or False value for the (User Defined) attribute.


Get the Sample Flow and Source Code here:

https://github.com/ericrsmith35/Apex-Defined-Example