1

I've noticed that when I have an <apex:selectList/> with a value attribute that calls a setter method to set a value in the controller all my getters get called before the initial set call happens.

This is causing issues for me because in some of my getters I set default values for other select lists, which defaults my changed selectList back to what it was initially.

I've isolated the code to demonstrate this behavior. Using the form below if you select False for both of the select lists and turn on debugging, when you change the second list from false to true the debug logs show the getters being called before the setter and rerender methods.

Why are the getters called before the setter that is starting the transaction? Why are getters called multiple times?

Visualforce:

<apex:page showHeader="true" sidebar="true" controller="DummyController">

    <apex:form id="form">
        <apex:actionFunction name="rerender" action="{!rerender}" reRender="form"/>

        <apex:selectList value="{!select1Value}" onchange="rerender();" multiselect="false" size="1">
            <apex:selectOptions value="{!select1Values}"/>
        </apex:selectList>
        <apex:selectList value="{!select2Value}" onchange="rerender();" multiselect="false" size="1" rendered="{!renderSelect2}">
            <apex:selectOptions value="{!select2Values}"/>
        </apex:selectList>

        <apex:commandButton action="{!press}" disabled="{!disableButton}" reRender="form" value="Press"/>
    </apex:form>

</apex:page>

Controller:

public class DummyController {
    public String select1Value;
    public String select2Value;

    public DummyController() {

    }

    public List<SelectOption> getSelect1Values() {
        System.debug('getSelect1Values');
        SelectOption one = new SelectOption('true', 'true');
        SelectOption two = new SelectOption('false', 'false');

        return new List<SelectOption>{one, two};
    }

    public List<SelectOption> getSelect2Values() {
        System.debug('getSelect2Values');
        SelectOption one = new SelectOption('true', 'true');
        SelectOption two = new SelectOption('false', 'false');

        return new List<SelectOption>{one, two};
    }

    public String getSelect1Value() {
        System.debug('getSelect1Value');
        return this.select1Value;
    }

    public String getSelect2Value() {
        System.debug('getSelect2Value');
        return this.select2Value;
    }

    public Boolean getRenderSelect2() {
        System.debug('getRenderSelect2');
        return String.isNotBlank(this.select1Value);
    }

    public void setSelect1Value(String value) {
        System.debug('setSelect1Value');
        this.select1Value = value;
    }

    public void setSelect2Value(String value) {
        System.debug('setSelect2Value');
        this.select2Value = value;
    }

    public void rerender() {
        System.debug('Rerendering');
    }

    public Boolean getDisableButton() {
        System.debug('getDisableButton');
        return String.isBlank(this.select1Value) || String.isBlank(this.select2Value);
    }

    public void press() {
        System.debug('Button Pressed');
        this.select1Value = null;
        this.select2Value = null;
    }
}

Debugs:

13:00:47.0 (7925137)|USER_DEBUG|[36]|DEBUG|getRenderSelect2
13:00:47.0 (8142382)|USER_DEBUG|[55]|DEBUG|getDisableButton
13:00:47.0 (8354192)|USER_DEBUG|[10]|DEBUG|getSelect1Values
13:00:47.0 (8542779)|USER_DEBUG|[26]|DEBUG|getSelect1Value
13:00:47.0 (8658064)|USER_DEBUG|[18]|DEBUG|getSelect2Values
13:00:47.0 (8792814)|USER_DEBUG|[31]|DEBUG|getSelect2Value
13:00:47.0 (8952937)|USER_DEBUG|[41]|DEBUG|setSelect1Value
13:00:47.0 (9016151)|USER_DEBUG|[36]|DEBUG|getRenderSelect2
13:00:47.0 (9111275)|USER_DEBUG|[46]|DEBUG|setSelect2Value <---- I would expect this to be at the top of the call stack. This event is essentially what is kicking off this transaction.
13:00:47.0 (9247100)|USER_DEBUG|[51]|DEBUG|Rerendering
13:00:47.0 (10992618)|USER_DEBUG|[36]|DEBUG|getRenderSelect2
13:00:47.0 (11661385)|USER_DEBUG|[10]|DEBUG|getSelect1Values
13:00:47.0 (11912811)|USER_DEBUG|[26]|DEBUG|getSelect1Value
13:00:47.0 (12036977)|USER_DEBUG|[18]|DEBUG|getSelect2Values
13:00:47.0 (12216899)|USER_DEBUG|[31]|DEBUG|getSelect2Value
13:00:47.0 (13380418)|USER_DEBUG|[55]|DEBUG|getDisableButton
3

The change occurs because Visualforce has a built-in mechanism that validates that the incoming values are within the list of legal values for the page. More generally speaking, the platform makes no assurances about the precise order or number of times any given getter or setter will be called, so modifying other parts of the state may have unpredictable effects between each time the page is deployed or otherwise recompiled (e.g. after a maintenance or release upgrade). Here's some simple code that demonstrates what happens:

public class q234101 {
    public String value { get; set; }
    public SelectOption[] getOptions() {
        Decimal random = Math.random();
        return new SelectOption[] {
            new SelectOption(random+'',random+'')
        };
    }
    public void action() {
        System.debug(value);
    }
}

<apex:page controller="q234101" showHeader="true" >
    <apex:form>
        <apex:pageMessages />
        <apex:selectList value="{!value}">
            <apex:selectOptions value="{!options}"/>
        </apex:selectList>
        <apex:commandButton value="Update" action="{!action}" />
    </apex:form>
</apex:page>

When you click on "update", you get an error like:

j_id0:j_id1:j_id28: Validation Error: Value is not valid

Also note that your action method is never called (no debug statement will occur).


In the long run, the general rule is that you cannot assume the order or number of times getters and setters can be called, and you should be very careful setting or getting other values that depend on the order of getters and setters being called. It's very easy to trick yourself in to this situation, and even worse, it can work correctly most of the time, but then randomly fail later.

Always try to use constructors and action methods to set defaults or build lists of values. Avoid changing values in a getter or setter if at all possible. If you absolutely must do so, always assume that the order and number of times the method may be called can change randomly for no reason whatsoever, and design your methods around this assumption.

| improve this answer | |
1

Check for nulls before setting the values in your getters:

public Boolean getSomeVar() {
    if (this.someVar == null) {
        this.someVar = true;
    }

    return this.someVar;
}

A few other notes:

  • This Post goes into the view state of a visualforce page and explains that the controller is actually re-instantiated with each call which could explain why the getters are called before your setter. The server does not actually keep a running track of whats going on in your VF page.
  • The getters are called each time the variable appears on the page. This means you don't want to do too much in the getters or it can SIGNIFICANTLY slow down your page
  • This Post has some interesting bits about getters and setters specifically in the context of components that you may want to look at
  • This Component takes heavy advantage of getters and setters (far more than I would generally recommend people to use but it actually works really well for it's use case).

I think the view state post explains most of what you're looking for. Essentially, the view state is sent back to the server, the server creates a new instance of the controller. The server uses the view state data to pre-populate the values of the variables. The server then reads the data you send via your form post and sets the values of the variables you reference on your page, this effectively calls the setters.

All of that stuff that happens first very likely runs checks on the variables calling the getters. All of this extra stuff is hidden from you for the sake of "convenience" I know that the debug logs track much of this so you can go through them and try to follow along.

| improve this answer | |

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.