[TIL] Winter '24 - Release Notes

Photo by Luca Bravo on Unsplash

[TIL] Winter '24 - Release Notes

User mode database operations.

In the '24 Winter update, we can choose how code runs—either with system-level permissions or in a more secure user mode.

// Using user mode in a SOQL query
List<Account> acc = [SELECT Id FROM Account WITH USER_MODE];

// Inserting a new account in user mode
Account newAcc = new Account(Name='test');
insert as user newAcc;

Custom error messages in record-triggered flows.

The error messages display in a window on the overall record page or as an inline error on a specific field. The associated record change is rolled back. Error messages can be created for before-save and after-save flows.

When a user performs an action like deleting a record that triggers a flow, sometimes the flow runs into an error. Before, when the flow failed, it wasn’t possible to display a specific error message to the user.

  • Add a Custom Error Message element (1)

  • Choose where to display the error message (2)

  • Enter the error message text (3).

Flow Canvas with the Custom Error message element and the details pane to configure the element.

Improved conditional directives in Lightning web components.

Instead of using the legacy if:true and if:else directives, we can now use lwc:if, lwc:elseif, and lwc:else conditional directives. This change is applied to LWC OSS as well.

<!-- example.html -->
<template>
    <template lwc:if={expression1}>
        Statement 1
    </template>
    <template lwc:elseif={expression2}>
        Statement 2
    </template>
    <template lwc:else>
        Statement 3
    </template>
</template>

Both lwc:elseif and lwc:else must be started by either lwc:if or lwc:elseif.

Conditional directives are used on nested <template>, or <div> tags or other HTML elements, on the custom components like <c-custom-cmp>

Complex expressions are not supported. To achieve that, we have to use a getters in our JS class.

For Loops with Iterable.

We can iterate through lists or sets using an Iterable variable in a for loop.

Iterable<String> stringIterator = new List<String>{'Hello', 'World!'};
for (String str : stringIterator) {
   System.debug(str);
}
/* This example implements an Iterable interface and 
then iterates through the strings in the returned set of strings. */
public class MyIterable implements Iterable<String> {
   public Iterator<String> iterator() {
      return new Set<String>{'Hello', 'World!'}.iterator();
   }
}
for (String str : new MyIterable()) {
   System.debug(str);
}

Comparator interface and Collator class for sorting.

The List class now supports the new Comparator interface, we can implement different sort orders in our code by using List.sort() with .Comparator parameter.

Implement the compare() method of the System.Comparator interface, and specify the Comparator as a parameter to List.sort(). Your implementation must explicitly handle null inputs in the compare() method to avoid a null pointer exception. This example implements two different ways of sorting employees.

public class Employee {
    private Long id;
    private String name;
    private Integer yearJoined;
    // Constructor
    public Employee(Long i, String n, Integer y) {
        id = i;
        name = n;
        yearJoined = y;
    }
    public String getName() { return name; }
    public Integer getYear() { return yearJoined; }
}
// Class to compare Employees by name
    public class NameCompare implements Comparator<Employee> {
        public Integer compare(Employee e1, Employee e2) {
            if(e1?.getName() == null && e2?.getName() == null) {
                return 0;
            } else if(e1?.getName() == null) { 
                return -1; 
            } else if(e2?.getName() == null) {
                return 1;
            }        
            return e1.getName().compareTo(e2.getName());
        }
    }
    // Class to compare Employees by year joined
    public class YearCompare implements Comparator<Employee> {
        public Integer compare(Employee e1, Employee e2) {
            // Guard against null operands for ‘<’ or ‘>’ operators because
            // they will always return false and produce inconsistent sorting
            Integer result;
            if(e1?.getYear() == null && e2?.getYear() == null) {
                result = 0;
            } else if(e1?.getYear() == null) { 
                  result = -1; 
            } else if(e2?.getYear() == null) {
                  result = 1;
            } else if (e1.getYear() < e2.getYear()) {
                  result = -1;
            } else if (e1.getYear() > e2.getYear()) {
                  result = 1;
            } else {
                  result = 0;  
            } 
            return result;
        }
    }

@isTest
private class EmployeeSortingTest {
    @isTest
    static void sortWithComparators() {        
        List<Employee> empList = new List<Employee>();
        empList.add(new Employee(101,'Joe Smith', 2020));
        empList.add(new Employee(102,'J. Smith', 2020));
        empList.add(new Employee(25,'Caragh Smith', 2021));
        empList.add(new Employee(105,'Mario Ruiz', 2019));
        // Sort by name
        NameCompare nameCompare = new NameCompare();
        empList.sort(nameCompare);
        // Expected order: Caragh Smith, J. Smith, Joe Smith, Mario Ruiz
        Assert.areEqual('Caragh Smith', empList.get(0).getName());
        // Sort by year joined
        YearCompare yearCompare = new YearCompare();
        empList.sort(yearCompare);
        // Expected order: Mario Ruiz, J. Smith, Joe Smith, Caragh Smith
        Assert.areEqual('Mario Ruiz', empList.get(0).getName());
    }
}

Source: Trailhead