Maintain Your Platform Developer I Certification for Winter ’24
Learning Objectives
After completing this unit, you’ll be able to:
- Secure Apex code with user mode database operations.
- Create custom error messages in record-triggered flows.
- Use improved conditional directives in Lightning web components.
- Iterate within For Loops more easily with Iterable.
- Use the Comparator interface and Collator class for sorting.
Salesforce Certification
If you hold the Platform Developer I credential, keep in mind that you need to complete this module by the due date to maintain your certification. Another important part of maintaining your credential is ensuring your Trailhead and Webassessor accounts are linked.
Interested in learning more about getting certified? Check out the Salesforce Platform Developer I credential.
Note: While anyone can earn this badge, this module is designed for those who hold the Platform Developer I certification.
Protect the Integrity of Your Certification
The quality of our certification exams and the value our credentials provide are our highest priority. Protecting the security and confidentiality of our exams is essential to providing our customers with credentials that are respected and industry-leading.
As a participant of the Salesforce Certification Program, you’re required to accept the terms of the Salesforce Credential and Certification Program Agreement. Please review the Salesforce certification exam-taking policies in the Salesforce Credential and Certification Program Agreement and Code of Conduct Salesforce Help article for more details.
Salesforce introduced a ton of great feature enhancements over the past year. Let’s take a look at some of the more important ones.
Secure Apex Code with User Mode Database Operations
The new Database
and Search
methods support an accessLevel
parameter that lets you run database and search operations in user mode instead of in the default system mode. Apex code runs in system mode by default, which means that it runs with substantially elevated permissions over the user running the code. To enhance the security context of Apex, you can specify user mode access for database operations. Field-level security (FLS) and object permissions of the running user are respected in user mode and it always applies sharing rules. In system mode, the class sharing keywords control the sharing rules.
Where: This change applies to Lightning Experience and Salesforce Classic in Enterprise, Performance, Unlimited, and Developer editions.
How: You can indicate the mode of the operation by using WITH USER_MODE
or WITH SYSTEM_MODE
in your SOQL query. This example specifies user mode.
List<Account> acc = [SELECT Id FROM Account WITH USER_MODE];
Database operations can specify user or system mode. This example inserts a new account in user mode.
Account acc = new Account(Name='test');insert as user acc;
The new AccessLevel
class represents the two modes in which Apex runs database operations. Use this new class to define the execution mode as user mode or system mode.
Create Custom Error Messages in Record-Triggered Flows
Use the new Custom Error Message element to create targeted error messages for your end users to explain what went wrong or how to correct it. 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. We delivered this feature thanks to your ideas on IdeaExchange.
Where: This change applies to Lightning Experience and Salesforce Classic in Essentials, Professional, Enterprise, Performance, Unlimited, and Developer editions.
Why: 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. Now you can inform the user exactly what caused their operation to fail, so they can fix the problem and try again.
How: Add a Custom Error Message element (1), choose where to display the error message (2), and enter the error message text (3).
Use Improved Conditional Directives in Lightning Web Components
The lwc:if
, lwc:elseif
, and lwc:else
conditional directives supersede the legacy if:true
and if:false
directives.
Where: This change applies to custom Lightning web components in Lightning Experience, Experience Builder sites, and all versions of the Salesforce mobile app. This change also applies to Lightning web components in Open Source.
How: With the lwc:if
, lwc:elseif
, and lwc:else
conditional directives, the property getters are accessed only one time per instance of the directive.
<!-- conditionalExample.html --> <template> <template lwc:if={isTemplateOne}> This is template one. </template> <template lwc:else> This is template two. </template> </template>
Both lwc:elseif
and lwc:else
must be immediately preceded by a sibling lwc:if
or lwc:elseif
.
Pass in an expression to lwc:if
and lwc:elseif
, but not to lwc:else
.
If expression1
returns a truthy, none of the other property getters are accessed.
<!-- example.html --> <template> <template lwc:if={expression1}> Statement 1 </template> <template lwc:elseif={expression2}> Statement 2 </template> <template lwc:else> Statement 3 </template> </template>
Consider these guidelines when working with the lwc:if
, lwc:elseif
, and lwc:else
conditional directives.
- Use the conditional directives on nested
<template>
tags,<div>
tags, or other HTML elements, and on your custom components tags like<c-custom-cmp>
.
- You can’t precede
lwc:elseif
orlwc:else
with text or another element. Whitespace is ignored between the tags when the whitespace is a sibling of the conditional directive. For example, you can’t have a<div>
tag that comes afterlwc:if
and beforelwc:else
.
- Complex expressions like
!condition
orobject?.property?.condition
aren’t supported. To evaluate complex expressions, use a getter in your JavaScript class.
Iterate Within for Loops More Easily with Iterable
You can now easily iterate through lists or sets using an Iterable variable in a for
loop.
Where: This change applies to Lightning Experience and Salesforce Classic in Enterprise, Performance, Unlimited, and Developer editions.
How: This example iterates through a list of strings.
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); }
Use the Comparator Interface and Collator Class for Sorting
The List
class now supports the new Comparator
interface, so you can implement different sort orders in your code by using List.sort()
with a Comparator
parameter. To perform locale-sensitive comparisons and sorting, use the getInstance
method in the new Collator
class. Because locale-sensitive sorting can produce different results depending on the user running the code, avoid using it in triggers or in code that expects a particular sort order.
Where: This change applies to Lightning Experience and Salesforce Classic in Enterprise, Performance, Unlimited, and Developer editions.
How: 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()); } }
This example performs a default list sort and then uses Collator to sort based on the user locale.
@IsTest static void userLocaleSort() { string userLocale = 'fr_FR'; User u = new User(Alias = 'standt', Email='standarduser@testorg.com', EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US', LocaleSidKey=userLocale, TimeZoneSidKey='America/Los_Angeles', ProfileId = [SELECT Id FROM Profile WHERE Name='Standard User'].Id, UserName='standarduser' + DateTime.now().getTime() + '@testorg.com'); System.runAs(u) { List<String> shoppingList = new List<String> { 'épaule désosé Agneau', 'Juice', 'à la mélasse Galette 5 kg', 'Bread', 'Grocery' }; // Default sort shoppingList.sort(); Assert.areEqual('Bread', shoppingList[0]); // Sort based on user Locale Collator myCollator = Collator.getInstance(); shoppingList.sort(myCollator); Assert.areEqual('à la mélasse Galette 5 kg', shoppingList[0]); Assert.areEqual('Bread', shoppingList[1]); Assert.areEqual('épaule désosé Agneau', shoppingList[2]); Assert.areEqual('Grocery', shoppingList[3]); Assert.areEqual('Juice', shoppingList[4]); } }
Resources
- Salesforce Help: Secure Apex Code with User Mode Database Operations
- Apex Developer Guide: Enforce User Mode for Database Operations
- Salesforce Help: Create Custom Error Messages in Record-Triggered Flows
- Salesforce Help: Use the Improved Conditional Directives
- Salesforce Help: Iterate Within for Loops More Easily with Iterable
- Apex Developer Guide: Custom Iterators