Final variable alters itself in OnClickListener [Android]

522 Views Asked by At

I am not an expert in Android programming, but this one is killing me. I am just trying to pass a structure "Challenge" from a Fragment to an Activity, nothing fancy. I then create a final version of my structure, which is fine. But when I get in the OnClickListener, it is not the same anymore, as it lacks an ArrayList[Score] ("Score" is another structure). To illustrate, I have a function that prints the structure and I can clearly see that in the onCreate() there is no problem, and 2 lines below it doesn't match anymore. Been scratching my head for a while, but I gotta ask for your help now, my keyboard is going to fly otherwise.

DetailsFragment :

public class DetailsFragment extends Fragment {

    public DetailsFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.fragment_details, container, false);

        // Get challenge details
        Bundle b = getArguments();
        Challenge challenge = (Challenge) b.getSerializable("challenge");
        Log.v("APP", "DetailsFragment : " + challenge.printToFile());

        // Buttons
        final Challenge finalC = challenge;
        Log.v("APP", finalC.printToFile());
        ((Button) rootView.findViewById(R.id.btn_Go)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.v("APP", finalC.printToFile());
                Intent intent = new Intent(getActivity(), ChallengeActivity.class);
                intent.putExtra("challenge", finalC);
                startActivity(intent);
            }
        });
        ((Button) rootView.findViewById(R.id.btn_Cancel)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getActivity().finish();
            }
        });

        // Set up layout
        if (challenge != null) {

            // Details
            ((TextView) rootView.findViewById(R.id.label_challengeName)).setText(challenge.name);
            ((TextView) rootView.findViewById(R.id.label_Distance2)).setText(String.valueOf(challenge.distance) + " m");
            ((TextView) rootView.findViewById(R.id.label_Lap2)).setText(String.valueOf(challenge.lap) + " m");
            ((TextView) rootView.findViewById(R.id.label_Objective2)).setText(challenge.printObjective());

            // Averages
            double avg = (challenge.distance * 1.0 / challenge.objective) * 3.6;
            ((TextView) rootView.findViewById(R.id.label_AvgSpeed2)).setText(String.format("%.2f", avg) + " km/h");
            int avg_sec = (int) (challenge.lap / (challenge.distance * 1.0 / challenge.objective));
            int sec = avg_sec % 60;
            int min = (avg_sec - sec) / 60;
            String obj = "";
            if (min > 0)
                obj += String.valueOf(min) + "'";
            obj += String.format("%02d", sec) + "\"";
            ((TextView) rootView.findViewById(R.id.label_AvgTime2)).setText(obj);

        }

        return rootView;

    }

}

And the log :

01-09 12:16:26.569 23926-23926/domk.mychallenge V/APP: DetailsFragment : Course Police:2400/400:720:10-12-2016/104,127,135,130,125,138:14-12-2016/122,128,128,132,130,108:24-12-2016/126,130,133,134,130,125
01-09 12:16:26.571 23926-23926/domk.mychallenge V/APP: Course Police:2400/400:720:10-12-2016/104,127,135,130,125,138:14-12-2016/122,128,128,132,130,108:24-12-2016/126,130,133,134,130,125
01-09 12:16:36.528 23926-23926/domk.mychallenge V/APP: Course Police:2400/400:720

The structures are loaded from a file in MainActivity, then passed to DetailsActivity :

public class DetailsActivity extends AppCompatActivity {

    Challenge challenge;
    TabLayout tabLayout = null;
    ViewPager viewPager = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_details);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        tabLayout = (TabLayout) findViewById(R.id.tab_layout);
        if (tabLayout != null) {
            tabLayout.addTab(tabLayout.newTab().setText("Details"));
            tabLayout.addTab(tabLayout.newTab().setText("Scores"));
            tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
        } else {
            Toast.makeText(getApplication().getBaseContext(), "Internal error", Toast.LENGTH_LONG).show();
            finish();
        }

        Bundle b = getIntent().getExtras();
        challenge = (Challenge) b.getSerializable("challenge");

        viewPager = (ViewPager) findViewById(R.id.pager);
        viewPager.setAdapter(new PagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount(), challenge));
        viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));

    }

}

The PagerAdapter is as follows :

public class PagerAdapter extends FragmentStatePagerAdapter {

    int mNumOfTabs;
    Challenge challenge;

    public PagerAdapter(FragmentManager fm, int NumOfTabs, Challenge challenge) {
        super(fm);
        this.mNumOfTabs = NumOfTabs;
        this.challenge  = challenge;
    }

    @Override
    public int getCount() {
        return mNumOfTabs;
    }

    @Override
    public Fragment getItem(int position) {

        Bundle bundle = new Bundle();
        bundle.putSerializable("challenge", challenge);

        switch (position) {

            case 0:
                DetailsFragment tab1 = new DetailsFragment();
                tab1.setArguments(bundle);
                return tab1;

            case 1:
                ScoresFragment tab2 = new ScoresFragment();
                tab2.setArguments(bundle);
                return tab2;

            default:
                return null;

        }
    }

}

Structure Score :

public class Score implements Serializable {

    Date date;
    ArrayList<Integer> laps;

    public Score() {

        Calendar c = Calendar.getInstance();
        date = c.getTime();
        laps = new ArrayList<Integer>();

    }

    public Score(Date d, ArrayList<Integer> l) {

        date = d;
        laps = l;

    }

    public void newLap(int score) { laps.add(score); }

    public String printLaps() {

        String s = "";
        for (int i = 0; i < laps.size(); i++) {
            s += "Lap " + String.valueOf(i + 1) + " : " + String.valueOf(laps.get(i)) + "s";
            if (i != (laps.size() - 1))
                s += "\n";
        }

        return s;

    }

    public String printToFile() {

        String s = ":" + new SimpleDateFormat("dd-MM-yyyy").format(date) + "/";
        for (int i = 0; i < laps.size(); i++) {
            s += String.valueOf(laps.get(i));
            if (i < (laps.size() - 1))
                s += ",";
        }

        return s;
    }

    public int getTime() {

        int sum = 0;
        for (int i = 0; i < laps.size(); i++)
            sum += laps.get(i);
        return sum;

    }

}

Structure Challenge :

public class Challenge implements Serializable {

    String           name;
    int              distance;
    int              lap;
    int              objective;
    ArrayList<Score> scores;

    public Challenge(String n, int d, int l, int o) {
        name      = n;
        distance  = d;
        lap       = l;
        objective = o;
        scores    = new ArrayList<Score>();
    }

    public String printToFile() {
        String s = name + ":" + Integer.valueOf(distance) + "/" + Integer.valueOf(lap) + ":" + Integer.valueOf(objective);
        for (int i = 0; i < scores.size(); i++)
            s += scores.get(i).printToFile();
        return s + "\n";
    }

    public String printObjective() {

        // Default format : XX'XX"
        if (format.length() < 2)
            format = "'\"";

        String time = "";

        int sec = objective % 60;
        int min = (objective - sec) / 60;

        if (min > 0)
            time +=  String.valueOf(min) + format.charAt(0);
        time += String.format("%02d", sec) + format.charAt(1);

        return time;

    }

    public void newScore(Score s) { scores.add(s); }


    public ArrayList<Integer> getTimes() {

        ArrayList<Integer> t = new ArrayList<Integer>();

        for (int i = 0; i < scores.size(); i++)
            t.add(scores.get(i).getTime());

        return t;

    }

}

I basically can't understand how my final variable changes between the 2nd and 3rd Log. I don't even know what to look for on the web, as this looks stupid as can be.

I apologize if it is a duplicate of some sort, and hope my english is at least understandable...

2

There are 2 best solutions below

4
On

Java Final keyword:

The final keyword in java is used to restrict the user. The java final keyword can be used in many context.

The final keyword can be applied with the variables, a final variable that have no value it is called blank final variable or uninitialized final variable. It can be initialized in the constructor only. The blank final variable can be static also which will be initialized in the static block only. We will have detailed learning of these. Let's first learn the basics of final keyword.

You might look into something like Static variable for keeping the variable the same while you try to get it.

1
On

final simply stops you reassigning the variable, i.e. you can't write finalC = anything once the variable is assigned. But you (or anybody else with a reference to the same object) can still change its fields or invoke methods that change its fields:

final List<String> finalList = new ArrayList<>(Array.asList("hello"));
System.out.println(finalList);  // Prints [hello]
finalList.clear();
System.out.println(finalList);  // Prints []

This illustrates that adding final to the variable has no affect upon the mutability of the variable's value.

It would appear that something else is similarly changing your variable, in code that you're not showing.

If you want to make sure that the value of finalC isn't different when onClick is invoked, you need to take a copy of the thing you're assigning to it. Exactly how you do that depends upon the value you're trying to copy; for a list it would be:

final List<String> finalList = new ArrayList<>(Array.asList("hello"));
final List<String> copyOfFinalList = new ArrayList<>(finalList);

System.out.println(finalList);        // Prints [hello]
System.out.println(copyOfFinalList);  // Prints [hello]

finalList.clear();

System.out.println(finalList);        // Prints []
System.out.println(copyOfFinalList);  // Prints [hello]