How to reduce a large internal view state / what is in the internal view state?

I’m building an account search page that lets users select accounts and then pass them to other pages to perform various actions on them. Unfortunately users are reporting hitting the view state limit. Which leads me to a broader question: what is in in the internal view state and how can I reduce it?

I’ve already made everything transient I can, and when I look at the view state inspector my controller only uses a modest 15kb, but the “Internal” component of the view state is upwards of 90kb. Does anyone have any tips for how to reduce the “Internal” component of the view state?

Some additional details. The page is not using any components. The page has only one form on it. The controller does reference a couple other classes. The controller does not store any state that can be regenerated in code on the postback.

EDIT: Uploaded a gist with a thoroughly edited version of the code. The code will likely not compile initially but should give some idea of the design.

EDIT2: Here’s a screenshot of what the page looks like from a related post on the UX stackexchange
Account Filter Page

EDIT3: Here’s a screenshot with a large result set loaded. If I group these results I’ll exceed the view state limit. Result set size is 500 records and 1 grouping when the limit is reached.
gigantic internal view state

Answer

I needed to reproduce this in a smaller more focused example to prove my hunch. Then further down you’ll see how I’ve approached a solution that hopefully doesnt impact your desire to structure the data as you currently have it. Yet does more work in the viewstate in order to simplify the VF markup.

Research. It looks like it is the use of nested apex:repeat and apex:outputPanel‘s, they internally appear to use quite a lot of state. In the reproduction below, I found around half of my view state allocated to internal. And I didn’t even take my example further by using apex:outputField (which I also suspect to be quite heavy). So with this in mind first, my reproduction controller and page are shown below. Followed by one possible way I considered to reduce the VF components on the page a little more but get the same output.

public with sharing class ViewStateTestController {

public List<Row> rows {get;set;}

private static final Integer scaler = 5;

public class Row
{
    public Boolean isGrouping {get;set;}
    public Boolean selected {get;set;}
    public String name {get;set;}
    public String data1 {get;set;}
    public String data2 {get;set;}
    public String data3 {get;set;}
    public List<Row> children {get;set;}        
}

public ViewStateTestController()
{
    rows = new List<Row>();
    for(Integer group1Idx=0; group1Idx<scaler; group1Idx++)     
    {
        // Add group for level 2 data
        Row group1Row = new Row();
        group1Row.isGrouping = true;
        group1Row.Name = 'Level 1 Group';
        group1Row.children = new List<Row>();
        rows.add(group1Row);
        for(Integer group2Idx=0; group2Idx<scaler; group2Idx++)     
        {
            // Add some level 2 data
            for(Integer group2DataIdx=0; group2DataIdx<group2Idx+1; group2DataIdx++)
            {
                Row group2DataRow = new Row();
                group2DataRow.isGrouping = false;
                group2DataRow.Name = 'Level 2 Data';
                group2DataRow.data1 = 'Some Level 2 Data 1';
                group2DataRow.data2 = 'Some Level 2 Data 2';
                group2DataRow.data3 = 'Some Level 2 Data 3';                                
                group1Row.children.add(group2DataRow);
            }
            // Add group for level 3 data
            Row group2Row = new Row();
            group2Row.isGrouping = true;
            group2Row.Name = 'Level 2 Group';
            group2Row.children = new List<Row>();
            group1Row.children.add(group2Row);
            for(Integer group3Idx=0; group3Idx<scaler; group3Idx++)     
            {
                // Add some level 3 data
                for(Integer group3DataIdx=0; group3DataIdx<group3Idx+1; group3DataIdx++)
                {                   
                    Row group3DataRow = new Row();
                    group3DataRow.isGrouping = false;
                    group3DataRow.Name = 'Level 3 Data';
                    group3DataRow.data1 = 'Some Level 3 Data 1';
                    group3DataRow.data2 = 'Some Level 3 Data 2';
                    group3DataRow.data3 = 'Some Level 3 Data 3';                                
                    group2Row.children.add(group3DataRow);
                }
            }           
        }           
    }            
}
}

And the VF page…

<apex:page controller="ViewStateTestController" sidebar="false">
<style type="text/css">
    table { border-collapse:collapse; }
    table.myTable td, table.myTable th { border:1px solid black;padding:5px; }
    table.myTable td.padding { border:0px; }
</style>
<apex:form >
<table class="myTable">
    <tr>
        <td><b>Select</b></td>
        <td colspan="3"><b>Name</b></td>
        <td><b>Data 1</b></td>
        <td><b>Data 2</b></td>
        <td><b>Data 3</b></td>
    </tr>
    <apex:repeat value="{!rows}" var="rowlevel1">
        <tr>
            <apex:outputPanel rendered="{!rowLevel1.isGrouping}">
                <apex:outputPanel layout="none">
                    <td><input type="checkbox"/></td>
                </apex:outputPanel>
                <td colspan="6">{!rowlevel1.Name} ({!rowlevel1.children.size})</td>
            </apex:outputPanel>
        </tr>
        <apex:repeat value="{!rowlevel1.children}" var="rowlevel2">
            <tr>
                <apex:outputPanel rendered="{!rowLevel2.isGrouping}">
                    <apex:outputPanel layout="none">
                        <td><input type="checkbox"/></td>
                    </apex:outputPanel>
                    <td class="padding">&nbsp;&nbsp;</td>
                    <td colspan="5">{!rowlevel2.Name} ({!rowlevel2.children.size})</td>
                </apex:outputPanel>
                <apex:outputPanel rendered="{!NOT(rowlevel2.isGrouping)}">
                    <td><apex:inputCheckbox value="{!rowlevel2.selected}"/></td>
                    <td class="padding">&nbsp;&nbsp;</td>
                    <td colspan="2">{!rowlevel2.name}</td>
                    <td>{!rowlevel2.data1}</td>
                    <td>{!rowlevel2.data2}</td>
                    <td>{!rowlevel2.data3}</td>
                </apex:outputPanel>                 
            </tr>           
            <apex:repeat value="{!rowlevel2.children}" var="rowlevel3">
                <tr>
                    <td><apex:inputCheckbox value="{!rowlevel3.selected}"/></td>
                    <td class="padding">&nbsp;&nbsp;</td>
                    <td class="padding">&nbsp;&nbsp;</td>
                    <td colspan="1">{!rowLevel3.name}</td>
                    <td>{!rowlevel3.data1}</td>
                    <td>{!rowlevel3.data2}</td>
                    <td>{!rowlevel3.data3}</td>
                </tr>
            </apex:repeat>              
        </apex:repeat>
    </apex:repeat>
</table>
</apex:form>
</apex:page>

Results in this…

enter image description here

Proposed Solution. So my solution revolves around flattening the nested lists into one and providing the bindings to control the layout in a single apex:repeat with simplified markup within. The pattern should allow you to retain your existing structure just be sure to produce this view of the data before rerendering the page. Of course if you want to adapt your internal viewstate structure this would make things easier.

I extended the above controller to add this…

public List<ViewRow> viewRows {get;set;}

public class ViewRow
{
    public Row row {get;set;}
    public String name {get;set;}
    public Integer level {get;set;}
    public Integer colspan {get;set;}
}

private void makeViewRows(List<Row> rows, Integer level)
{
    for(Row row : rows)
    {       
        ViewRow viewRow = new ViewRow();
        viewRow.row = row;
        viewRow.name = row.isGrouping ?
            String.format('{0} ({1})', new String[] { row.Name, String.valueOf(row.children.size()) }) : row.Name;
        viewRow.level = level;
        viewRow.colSpan = row.isGrouping ? 
            (level == 1 ? 6 : 5) :
            (level == 2 ? 2 : 1); 
        viewRows.add(viewRow);
        if(row.isGrouping)
            makeViewRows(row.children, level + 1); 
    }
}

Then add these lines to the bottom of the constructor to produce the new list.

// Produce a flat list to binding to
viewRows = new List<ViewRow>();
makeViewRows(rows, 1);

My Visualforce page now looks like this.

<apex:page controller="ViewStateTestController" sidebar="false">
<style type="text/css">
    table { border-collapse:collapse; }
    table.myTable td, table.myTable th { border:1px solid black;padding:5px; }
    table.myTable td.padding { border:0px; }
</style>
<apex:form >
<table class="myTable">
    <tr>
        <td><b>Select</b></td>
        <td colspan="3"><b>Name</b></td>
        <td><b>Data 1</b></td>
        <td><b>Data 2</b></td>
        <td><b>Data 3</b></td>
    </tr>
    <apex:repeat value="{!viewrows}" var="viewrow">
        <tr>
            <td><apex:inputCheckbox value="{!viewrow.row.selected}"/></td>
            <td style="display:{!IF(viewrow.level>=2, 'table-cell', 'none')}" class="padding">&nbsp;&nbsp;</td>         
            <td style="display:{!IF(viewrow.level>=3, 'table-cell', 'none')}" class="padding">&nbsp;&nbsp;</td>         
            <td colspan="{!viewrow.colspan}">{!viewrow.name}</td>
            <td style="display:{!IF(viewrow.row.isGrouping, 'none', 'table-cell')}">{!viewrow.row.data1}</td>
            <td style="display:{!IF(viewrow.row.isGrouping, 'none', 'table-cell')}">{!viewrow.row.data2}</td>
            <td style="display:{!IF(viewrow.row.isGrouping, 'none', 'table-cell')}">{!viewrow.row.data3}</td>               
        </tr>
    </apex:repeat>
</table>
</apex:form>
</apex:page>

Results in the following reduction on internal viewstate from 5.22kb (48%) to 2.85kb (34%).

enter image description here

With the apex:inputCheckbox component commented out, it reduces to 1.27kb (20%).

enter image description here

Summary: So it seems using the VF components in large tables can have a more noticeable impact on your internal view state. The general feeling I get is that VF is best at form entry, but struggles with lots of VF components (be they read or write) on the page. And large tables certainly stress this a lot more, especially in nested situations like this, where the output needs to vary from row to row. Some suggestions then…

  • Try to avoid having nested VF component related repeats / panels / rendered logic in the table in the VF page. In this case it seems to help to predetermine some of the rendering aspects in the view state as I have done in my proposed solution above. BTW I did notice at least one outputPanel that just wrapped a checkbox, so while that might give a small gain, I think that could be safely removed from your current solution.
  • Given this example is largely output driven, with mostly a single VF checkbox component per row it might be best to consider dropping VF components all together for the rows. And outputting a standard HTML checkbox. Then using JavaScript Remoting or a parameterised apex:actionFunction to get the selected rows back to the controller?
  • Building on the above, for total control you might want to consider building the entire table HTML in your controller Apex logic yourself. This can be output with apex:outputText, setting escape=”false”. Take a look at this for a brief explanation.
  • Consider if you need the apex:form tag, sometimes this is just carried over, copy paste style as general tags needed. But if you are not accepting any input via the traditional VF input components then you don’t need it and hence you don’t need viewstate.
  • Finally of course consider ways to help the users reduce the number of records displayed via a paging solution perhaps.

Update: 22nd Jan, added point relating to use of apex:form tag.

Update: 21st Feb, added further insight as to relative weight of internal viewstate based on field type and use of fieldsets. Thanks Ralph!

“Ran into this in a couple more cases. One interesting conclusion is that the weight of apex:outputField varies by the field type, with lookups not surprisingly added a lot more view state relative to test fields. Also found that looping over field sets as for the columns also generated a lot of extra view state as opposed to hard-coded columns”

Updated: 25th July 2013

This answer from sfdcfox, represents another option (so long as you take into account the advice above as well). Basically he proposes using Dynamic Visualforce. For trees with variable depths this could be an option.

Attribution
Source : Link , Question Author : Ralph Callaway , Answer Author :
13 revs

Leave a Comment