Contact
Back to Articles
article cover
Andrew Hanna
4 years ago
Writing

The Secrets of Salesforce Second Generation Managed Package

We will cover a couple of topics to make your life easier whether you already are using second generation managed packages or you are new to this topic.

Whenever you create a new release, it could take a lot of time & effort, and when it fails, sometimes it does not provide all that meaningful errors to help you resolve the issue! I really wish it gave more detailed error messages like the sfdx deploy command. Still, that’s not the ugly part yet, It gets even more tricky when your package is already created and ready for use, however when you try installing it in your test org, you find out that there are a lot of features not working as expected!

If you want to avoid this mess or already facing it, then you are in the right place to learn from our challenges and mistakes saving you the needless headache.

Apex Access Modifiers

Let's start by getting to know the different access modifiers in apex classes so it can help you choose how to implement your code:

Private

This access modifier is the default, and means that the method or variable is accessible only within the Apex class in which it’s defined. If you don’t specify an access modifier, the method or variable is private.

Protected

This means that the method or variable is visible to any inner classes in the defining Apex class, and to the classes that extend the defining Apex class. You can only use this access modifier for instance methods and member variables. This setting is strictly more permissive than the default (private) setting, just like Java.

Public

This means that the method or variable is accessible by all Apex within a specific package.

In Apex, the public access modifier isn’t the same as it is in Java. This was done to discourage joining applications, to keep the code for each application separate. In Apex, if you want to make something public like in Java, you must use the global access modifier.

@NamespaceAccessible annotation

For accessibility by all second-generation (2GP) managed packages that share a namespace, use public with the @NamespaceAccessible annotation. Using the public access modifier in no-namespace packages implicitly renders the Apex code as @NamespaceAccessible.

Global

This means the method or variable can be used by any Apex code that has access to the class, not just the Apex code in the same application. This access modifier must be used for any method that must be referenced outside of the application, either in SOAP API or by other Apex code. If you declare a method or variable as global, you must also declare the class that contains it as global.

We recommend using the global access modifier rarely, if at all. Cross-application dependencies are difficult to maintain.

AuraEnabled Annotation

The @AuraEnabled annotation enables client-side and server-side access to an Apex controller method. Providing this annotation makes your methods available to your Lightning components (both Lightning web components and Aura components). Only methods with this annotation are exposed.

In API version 44.0 and later, you can improve runtime performance by caching method results on the client by using the annotation @AuraEnabled(cacheable=true). You can cache method results only for methods that retrieve data but don’t modify it. Using this annotation eliminates the need to call setStorable() in JavaScript code on every action that calls the Apex method.

In API version 55.0 and later, you can use the annotation @AuraEnabled(cacheable=true scope='global') to enable Apex methods to be cached in a global cache.

If you have an Apex class in your base package 

global with sharing class TestClass{ 
@AuraEnabled global static void testMethod{} }
You can reference this method from a different package in your Lightning Web Component like this

import logExceptionLWC from "@salesforce/apex/TestClass.testMethod";

Global access modifier would is the trick to be able to reference it from another package. However if you're referencing the Lightning Web Component from an Apex Class within the same package, then a Public access modifier would be fine in this case.

JSON Access Annotation

One of the most important tricks is when you create a custom model in your apex code and you might need to serialize or deserialize it later, then you will definitely need to add the Json Access annotation for your custom model. Hint: make sure to add the annotation to your child objects as well ;)

So what is Json Access annotation? And what is it's serialization & deserialization options and considetaions?

The @JsonAccess annotation defined at Apex class level controls whether instances of the class can be serialized or deserialized. If the annotation restricts the JSON serialization and deserialization, a runtime JSONException exception is thrown.

The serializable and deserializable parameters of the @JsonAccess annotation enforce the contexts in which Apex allows serialization and deserialization.

You can specify one or both parameters, but you can’t specify the annotation with no parameters. The valid values for the parameters to indicate whether serialization and deserialization are allowed:

  • never: never allowed
  • sameNamespace: allowed only for Apex code in the same namespace
  • samePackage: allowed only for Apex code in the same package (impacts only second-generation packages)
  • always: always allowed for any Apex code

JSON Access Considerations

If an Apex class annotated with JsonAccess is extended, the extended class doesn’t inherit this property. If the toString method is applied on objects that mustn't be serialized, private data can be exposed. You must override the toString method on objects whose data must be protected. For example, serializing an object stored as a key in a Map invokes the toString method. The generated map includes key (string) and value entries, thus exposing all the fields of the object.

@JsonAccess(serializable='always' deserializable='always')
global class FullDetails{
  global String fullName {get; set;} 
  global Address address {get; set;} 

@JsonAccess(serializable='always' deserializable='always')
global class Address{
  global String streetName {get; set;} 
  global String postalCode {get; set;} 
  }
}

Referencing Apex Classes from a Managed Package

What if you are creating some unpackaged apex classes in your project and you might reference another apex class or an interface that is part of the managed package? Just make sure to set these classes as global and reference the class by <Namespace>.<ClassName>

Named Credentials

The usual callout reference would be callout:<NamedCredentialsApiName>, but if you're using named credentials as part of your package for your callout classes, you'll need to update it to callout:<NampeSpace>__<NamedCredentialsApiName>

Connected App

If you are adding the connected app component to your package, here are the exact steps to do it and avoid some common bugs

  1. Create a developer org
  2. Go to package manager
  3. Check namespace availability then add it
  4. Link namespace developer org to your DevHub (If while trying to connect, it fails after the part of the allow access popup, make sure to wait for 10 to 15 minutes then re-try)
  5. Go to app manager and create a new connected app
  6. Assign either full permissions or api, web, and refresh permissions
  7. After creation of the connected app, you can add metadata in your dx project
<?xml version="1.0" encoding="UTF-8"?>
<ConnectedApp xmlns="http://soap.sforce.com/2006/04/metadata">
  <developerName>YourNamespace__ConnectedAppName</developerName>
  <contactEmail>ConnectedAppContactEmail</contactEmail>
  <label>ConnectedAppLabelName</label>
  <version>ConnectedAppVersion</version>
</ConnectedApp>

If you followed the exact same steps and in the process of creation of package version it fails with the error that says

ERROR running force:package:version:create: <ConnectedAppName>: Installing an app (<ConnectedAppName>) that has been deleted.

Trust me we've been there, you will need to create a new package first, then create a new package version! To validate this before starting, create a new dummy dx project and create a new package, add the same connected app you already created, and finally create your package version and let me know if that works with you!

Rest of Salesforce Components

Finally you need to go through all your fields, objects, triggers, permission sets, custom metadata...etc and add <Namespace>__

Installation of Managed Package Challenges and Tricks

If you found a similar error resulting in failure of installation your package in any environment, instead of spending hours and days trying to figure it out, just remove those standard Quick Actions from your layout, and then re-add them once the package is installed successfully.

(ObjectName-Layout) In field: QuickAction - no QuickAction named NewCase found
(ObjectName-Layout) In field: QuickAction - no QuickAction named NewTask found
(ObjectName-Layout) In field: QuickAction - no QuickAction named LogACall found
Component [flexipage:filterListCard] attribute [filterName]: Error retrieving filter [My_ChatterGroups] for entity [CollaborationGroup]

Another one to think about, if your source code is dependant on Salesforce built-in Components, then ensure to give access to those components as part of your pre-deployment steps before installation of the package version. Like in here, enabling of omni channel.

Your org doesn't have access to component runtime_service_omnichannel:omniWidget.

Finally, if you have standardValueSets in the your source code and your package version metadata is dependant on it, then ensure to deploy those value sets as pre-deployment step before installation of package version. Like in here, deploying custom status pick-list value named TestValue.

Component [lst:dynamicRelatedList] attribute [adminFilters]: TestValue isn't a valid picklist value.

You can reference Metadata Coverage Documentation to know which components is packageable!

We're glad to share any helpful information and would recommend you knowing more about our impact to the release cycle in Salesforce

Schedule a demo to find out more about how Serpent can supercharge your Salesforce DevOps Strategy today! Serpent by Tekunda offers some excellent tools to help your DevOps teams, including built-in version control, continuous deployment and release systems, merge tools, and tons of different testing tools.

Contact Us

Commitment free !