[java] Variable is accessed within inner class. Needs to be declared final

I'm getting a compilation error inside of my onClick.

Here's the code.

public class fieldsActivity extends Activity {

Button addSiteButton;
Button cancelButton;
Button signInButton;


/**
 * Called when the activity is first created.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // to create a custom title bar for activity window
    requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);

    setContentView(R.layout.fields);
    // use custom layout title bar
    getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.topbar);

    Pager adapter = new Pager();
    ViewPager mPager = (ViewPager) findViewById(R.id.fieldspager);
    mPager.setAdapter(adapter);
    mPager.setCurrentItem(1);



    addSiteButton = (Button) findViewById(R.id.addSiteButton);
    addSiteButton.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
           mPager.setCurrentItem(2, true); //Compilation error happens here.
        }


    });


    cancelButton = (Button) findViewById(R.id.cancel_button);
    signInButton = (Button) findViewById(R.id.sign_in_button);

}

This question is related to java android

The answer is


You can declare the variable final, or make it an instance (or global) variable. If you declare it final, you won't be able to change it later.

Any variable defined in a method and accessed by an anonymous inner class must be final. Otherwise, you could use that variable in the inner class, unaware that if the variable changes in the inner class, and then it is used later in the enclosing scope, the changes made in the inner class did not persist in the enclosing scope. Basically, what happens in the inner class stays in the inner class.

I wrote a more in-depth explanation here. It also explains why instance and global variables do not need to be declared final.


If you don't want to make it final, you can always just make it a global variable.


    public class ConfigureActivity extends Activity {

        EditText etOne;
        EditText etTwo;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_configure);

            Button btnConfigure = findViewById(R.id.btnConfigure1);   
            btnConfigure.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            configure();
                        }
                    });
    }

    public  void configure(){
            String one = etOne.getText().toString();
            String two = etTwo.getText().toString();
    }
}

The error says it all, change:

ViewPager mPager = (ViewPager) findViewById(R.id.fieldspager);

to

final ViewPager mPager = (ViewPager) findViewById(R.id.fieldspager);

Here's a funny answer.

You can declare a final one-element array and change the elements of the array all you want apparently. I'm sure it breaks the very reason why this compiler rule was implemented in the first place but it's handy when you're in a time-bind as I was today.

I actually can't claim credit for this one. It was IntelliJ's recommendation! Feels a bit hacky. But doesn't seem as bad as a global variable so I thought it worth mentioning here. It's just one solution to the problem. Not necessarily the best one.

final int[] tapCount = {0};

addSiteButton.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
       tapCount[0]++;
    }

});

As @Veger said, you can make it final so that the variable can be used in the inner class.

final ViewPager pager = (ViewPager) findViewById(R.id.fieldspager);

I called it pager rather than mPager because you are using it as a local variable in the onCreate method. The m prefix is cusomarily reserved for class member variables (i.e. variables that are declared at the beginning of the class and are available to all class methods).

If you actually do need a class member variable, it doesn't work to make it final because you can't use findViewById to set its value until onCreate. The solution is to not use an anonymous inner class. This way the mPager variable doesn't need to be declared final and can be used throughout the class.

public class MainActivity extends AppCompatActivity {

    private ViewPager mPager;
    private Button mButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...

        mPager = (ViewPager) findViewById(R.id.fieldspager);

        // ...

        mButton.setOnClickListener(myButtonClickHandler);
    }


    View.OnClickListener myButtonClickHandler = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mPager.setCurrentItem(2, true);
        }
    };
}