0

Products Products__c

Name Label Data Type Sample Data Sample Data 2 Sample Data 3
PDC PDC__c Text ACA ACG TBC
PDC Desc PDC_Desc__c Text Tire Size Rim / Wheel Disc Size Type of Toolbox
Value Desc Value_Desc__c Text 255/70R 22.5 22.5x8.25 None
Prod Order Prod_Order__c Lookup a1C2f000001jCyYEAU a1C2f000001jCyYEAU a1C2f000001jCyYEAU

Product Groups Products_Groups__c

Name Label Type Sample Data Sample Data 2 Sample Data 3
Product Code Product_Code__c Text ACA ACG TBC
Product Group Product_Group__c Text Tires / Wheels / Hubs and Drums Tires / Wheels / Hubs and Drums Tool Boxes
Product Group Seq Product_Group_Seq__c Number 2 2 5
Product Seq Product_Seq__c Number 1 2 5

Desired VFP Results based on sample data. Sorted first by Product_Group_Seq__c then Product_Seq__c:

Tires / Wheels / Hubs and Drums
Tire Size 255/70R 22.5
Rim / Wheel Disc Size 22.5x8.25
Tool Boxes
Type of Toolbox None

Below is my standard controller.

I am trying to get this into a parent-child map format where I can display on visualforce page with the Product_Group__c from Product_Groups__c being a header value showing all Product_Code__c and their child value Value_Desc__c from Products__c below in repeating format.

I am receiving attempt to de-reference null object

and I can see in debug it says AdjacentGroup|null

11:07:44:377 EXCEPTION_THROWN [23]|System.NullPointerException: Attempt to de-reference a null object

public with sharing class Productsv2Controller {
    
    public String OrderId {get;set;}
    public Map<String, Products_Groups__c> groupsMap {get;set;}
    public Productsv2Controller() {
        
        OrderId = ' ';
        
        OrderId=System.currentPageReference().getParameters().get('id');
        
        System.debug('...................OrderId=='+OrderId);
        
        Id i= Id.valueOf(OrderId);
        
        Map<String, Products_Groups__c> groupsMap = new Map<String, Products_Groups__c>();
        for(Products_Groups__c og :[SELECT Id,Name, Product_Code__c,Product_Group_Seq__c,Product_Group__c,Product_Priority_Level__c,Product_Seq__c FROM Products_Groups__c]){
            groupsMap.put(og.Product_Code__c, og);
        }
        
        for(Products__c o :[SELECT Id,PDC__c, PDC_Desc__c, Value_Desc__c from Products__c WHERE Prod_Order__c = :i]){
            Products_Groups__c adjacentGroup = groupsMap.get(o.PDC__c);
            
            system.debug('Group: '+adjacentGroup.Product_Group__c+' Code: '+adjacentGroup.Product_Code__c+' PDC: '+o.PDC__c+' Value: '+o.Value_Desc__c);
        }
        
    }
}
3
  • Please clearly indicate which line hits this error. If you edit your post to include a stack trace, there will be less guesswork involved for those interested in helping you.
    – Adrian Larson
    Mar 30 at 15:27
  • I will add stack trace. Is that the dump of the debug log? I apologize, I am very new to this and have been tossed in.
    – Wes
    Mar 30 at 15:36
  • 2
    @Wes The stack trace is the part of the error that tells you what classes/methods have been called (and on what line). Each method that gets called puts information onto "the stack" (a Computer Science concept) that tells the program where to return after the current method is done executing (among other things). In this case, I suspect it'd only be one or two additional lines, with Productsv2Controller.constructor: line 23, column 1 or something to that effect on the top of the stack.
    – Derek F
    Mar 30 at 15:44

2 Answers 2

2

I would restructure this controller to use an inner class (sometimes called a wrapper class so your data model is easier to handle in the VF page

public inherited sharing class Productsv2Controller {
    
    public String orderId { // orderId lazy loader
       get {
         if (orderId == null) {
            orderId = System.currentPageReference().getParameters().get('id');
         } set;
    }

    // inner class that represents the parent-children for a single parent
    public class ProductGroup { 
       public Products_Groups__c pg {get; private set;}
       public List<Product2> products {get; private set;}

       ProductGroup withProductGroup(Products_Groups__c val) {this.pg = val; return this;}
       ProductGroup withProducts(Products__c[] val) {this.products = val; return this;}
    }

    // Map used by VF page to display all parents and children
    public Map<String, ProductGroup> productGroupsByProductCode {
       get {
          if (productGroupsByProductCode == null) { // lazy load

            // Pivot order's Products__c by PDC
            productGroupsByProductCode = new Map<String,ProductGroup>();
            Map<String,Products__c[]> productsByPDC = new Map<String,Products__c[]>();
            
            for(Products__c p :[SELECT Id,PDC__c, PDC_Desc__c, Value_Desc__c 
                                  FROM Products__c WHERE Prod_Order__c = :this.orderId]) {
                String pdcLower = p.Pdc__c.toLowerCase();
                if (!productsByPDC.containsKey(pdcLower)) {
                  productsByPDC.put(pdcLower,new List<Products__c>())
                }
                productsByPDC.get(pdcLower).add(p); // build list
            }

            // Construct the parent-child
            for (Products_Groups__c pg: [SELECT Id, Product_Code__c, ...
                                            FROM Products_Groups__c]
                                            ) {
                productGroupsByProductCode.put(pg.Product_Code__c, new ProductGroup()
                  .withProductGroup(pg)
                  .withProducts(productsByPDC.containsKey(pg.Product_Code__c.toLowerCase()
                    ? productsByPDC.get(pg.Product_Code__c.toLowerCase())
                    : new List<Products__c>());                                
            }

          }
       } private set;
    }
    public Productsv2Controller() {}
        
        
}

VF markup (based on VF doc) snippet

<apex:repeat value="{!productGroupsByProductCode}" var="productCode">
   <apex:outputText value="{!productGroupsByProductCode[productCode].pg.XXX__c}"/>
   
   <apex:repeat value="{!productGroupsByProductCode[productCode].products" var="prod">
      <apex:outputText value="{!prod.PDC_Desc__c}"/> // or whatever field(s) you want from Products__c object
   </apex:repeat>
</apex:repeat> 

Some notes

  1. SObject names with plurals are a really bad idea. If this is de novo development, I'd really encourage you to singularize the SObject names
  2. I renamed your variables and changed to camelCase
  3. The inner class uses a fluent pattern (sometimes called a builder pattern) to set its properties. This is not strictly necessary but useful to learn for a variety of reasons
  4. Everything is done with collections which is a good way to avoid NPE;
  5. I agree with Derek's advice about map keys which are case sensitive
  6. VF controllers should ideally have empty or near empty constructors and where all the logic is either in properties/methods referenced by the page's merge fields or page action methods for action="{!doSomething} on the page.
  7. I didn't test this so YMMV
4
  • Thanks for all the help @cropredy. I was able to use the above to get my map and list. I am still struggling to iterate through it appropriately. I am trying to use apex repeat as follows. Am I going about this the correct way? <apex:repeat value="{!productGroupsByProductCode}" var="pKey"> <apex:repeat value="{!pKey}" var = "pKey2"> <apex:outputText value="{!productGroupsByProductCode[oKey2]}" /><br/> </apex:repeat> </apex:repeat>
    – Wes
    Mar 31 at 17:09
  • 1
    I added the VF markup
    – cropredy
    Mar 31 at 17:50
  • I appreciate you adding that markup. I can see I was a little bit off in my understanding of how to access the values. I attached an example of the visualforce table I am tryign to build in my original post. I am trying to group codes under their respective group as shown. Is that possible with the map you have helped/provided?
    – Wes
    Mar 31 at 19:11
  • 1
    yes ... (assuming the map is built as I expected - you can see by a system.debug) that is what I more or less provided without the CSS - my suggestion is to create a new question that includes just the inner class and map, the VF markup you have, the desired result, and what the VF markup currently generates. No need to include how the map is built or the rest of the controller as that will just clutter the question
    – cropredy
    Mar 31 at 22:18
4

When you run into an NPE (Null Pointer Exception), you always have something in your code that partially evaluates to null.<something else>. null is a special type/value thing. It has no variables or methods (instance or static), so when Salesforce encounters null.<something else> execution cannot continue and you get an exception.

Line 23 for you is

system.debug('Group: '+adjacentGroup.Product_Group__c+' Code: '+adjacentGroup.Product_Code__c+' PDC: '+o.PDC__c+' Value: '+o.Value_Desc__c);

So either adjacentGroup is null, or o is null.

o cannot be null because it's being fed from a query, and queries never return null objects. If your query returned no rows, the rest of your for loop would not be executed.

So adjacentGroup must be null. That variable is set from the result of groupsMap.get(o.PDC__c). The issue you're running into is that the value of o.PDC__c does not exist in your map.

We don't have enough information to tell you much beyond that. Whatever your PDC__c value is does not exactly match any of the Product_Code__c values from the Product_Group__c records you queried.

While most things in Apex are case-insensitive, Sets and the keys of Maps are the two biggest exceptions. Both of them are extremely case-sensitive, so "prodcode_123" and "PRodCOde_123" are different in this context.

3
  • 1
    extremely case sensitive :-)
    – cropredy
    Mar 30 at 16:28
  • Thanks for the detailed assistance Derek. Is there a simple way to handle this null error when manually building this parent-child map? I expect that I will have many adjacentGroup.Product_Group__c in which the product code is not on the o.PDC__c. The Product_Group__c object stores all the PDC__c and their group. So it will return many more attributes than were likely selected.
    – Wes
    Mar 30 at 17:56
  • @Wes Again, there's not enough context here (and the field names are opaque) so I can't give targeted advice. One general principle you could use here is check to see if something is null before you try to use it. if(adjacentGroup == null){ continue; } as the second line in your final for loop would avoid the issue (continue; causes you to skip the rest of the loop for the current iteration and start processing the next record). Whether or not that's what you actually want to do, I can't say, but it is a way to deal with it.
    – Derek F
    Mar 30 at 23:43

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.