androidarraysandroid-fragmentslistenerandroid-dialogfragment

An array of boolean sent to a method changes one of the array booleans value inexplicably


This is very weird and hard to explain, so screenshots may help.

I have a Fragment that launches a DialogFragment to filter the results of a RecyclerView. The dialog shows three options that the user can check or uncheck.

If the user cancels the dialog by taping the back button or touching outside the dialog, I want to revert the selection made by user, so the choices will be checked or unchecked as when the dialog was open.

To achieve that I keep the current filters applied to the RecyclerView in a boolean array (test) in the Fragment. Therefore, I overwritten the onCancel() method of DialogFragment to call another method in the Fragment that sets again the current filters to the instance of the DialogFragment.

The problem I have is that the second time the user opens the dialog and cancels it, the boolean object in the array of my Fragment that is in the position of the option unchecked/checked, changes the value.

By default, all options are checked:

enter image description here

Then the user unchecks an option but do not click on accept:

enter image description here

As the user made changes to the dialog options but did not apply them, I revert the changes. Logcat shows the values before and after applying again the same filters:

2024-01-18 20:37:33.205 31320-31320 Filters
com.example.futbol D Values of test array before calling setSeleccion: [true, true, true]

2024-01-18 20:37:33.206 31320-31320 Filters
com.example.futbol D Values of test array after calling setSeleccion: [true, true, true]

Then, the user opens the dialog again, and as expected, all options are checked. So far, so good:

enter image description here

Now the user unchecks another option, and as before, do not accept:

enter image description here

However, when I check the logs, I see test array changed even before calling setSeleccion():

2024-01-18 20:41:14.832 31320-31320 Filters
com.example.futbol D Values of test array before calling setSeleccion: [true, true, false]

2024-01-18 20:41:14.832 31320-31320 Filters
com.example.futbol D Values of test array after calling setSeleccion: [true, true, false]

Indeed, if the user opens the dialog again, the option he unchecked last time is unchecked:

enter image description here

In my code, the test array is used two times only, one to declare it, and the second one as a parameter of setSeleccion().

My Fragment code (I removed unrelated lines):

public class ListaFragment extends BaseTemporadaFragment 
        implements Adaptador.OnEventClickListener, 
        FiltroFragment.FiltroListener {
    public static final String TAG = "ListaFragment";
    private boolean[] test = new boolean[]{true, true, true};
    private FiltroFragment dialogFragment;

    public ListaFragment() {
        
    }

    public static ListaFragment newInstance(int temporada, boolean[] filtros) {
        ListaFragment fragment = new ListaFragment();
        Bundle args = new Bundle();
        args.putInt("temporada", temporada);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        this.dialogFragment = new FiltroFragment();
        this.dialogFragment.setListener(this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_lista, container, false);
        
        ((AppCompatActivity) this.getActivity()).getSupportActionBar()
                .setTitle(R.string.ver_lista);
        
        this.setHasOptionsMenu(true);
        
        return v;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
    }

    // TODO
    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        inflater.inflate(R.menu.filtro, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    // TODO
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (item.getItemId() == R.id.menu_filtro) {
            this.dialogFragment.show(MainActivity.fragmentManager, FiltroFragment.TAG);
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onDialogPositiveClick(DialogFragment dialog) {
    }

    @Override
    public void OnCancelListener(DialogFragment dialog) {
        Log.d("Filters", "Values of test array before calling setSeleccion: " + Arrays.toString(this.test));
        this.dialogFragment.setSeleccion(this.test);
        Log.d("Filters", "Values of test array after calling setSeleccion: " + Arrays.toString(this.test));
    }

    @Override
    public void onClick(View view) {
    }

    // TODO
    @Override
    public void onItemClick(Partido partido) {

    }
}

My DialogFragment:

public class FiltroFragment extends DialogFragment {

    public interface FiltroListener {
        public void onDialogPositiveClick(DialogFragment dialog);
        public void OnCancelListener(DialogFragment dialog);
    }

    private FiltroListener listener;
    public static final String[] TIPOS = new String[]{"Liga", "Copa", "Amistosos"};
    private boolean[] seleccion = new boolean[]{true, true, true};

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this.getActivity());
        builder.setTitle(this.getResources().getString(R.string.titulo));
        builder.setMultiChoiceItems(FiltroFragment.TIPOS, this.seleccion,
                new DialogInterface.OnMultiChoiceClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i, boolean b) {
                        //cambiarSeleccion(i, b);
                    }
                });
        builder.setPositiveButton(R.string.aplicar, 
                new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                listener.onDialogPositiveClick(FiltroFragment.this);
        });
        return builder.create();
    }

    @Override
    public void onCancel(@NonNull DialogInterface dialog) {
        super.onCancel(dialog);
        listener.OnCancelListener(FiltroFragment.this);
    }

    public boolean[] getSeleccion() {
        return this.seleccion;
    }

    public void setSeleccion(boolean[] seleccion) {
        this.seleccion = seleccion;
    }

    public void setListener(FiltroListener listener) {
        this.listener = listener;
    }
}

As you can see my setSeleccion() method just changes the array of booleans of my dialog, but it does not modify test array.

I also tried to debug my app breaking on value change of test array, but it does not break!!!

How is it possible that test array is changing the value?


Solution

  • I'm fairly sure this is how you explain the behaviour:

    1. You open the dialog, modify it, cancel it, in OnCancelListener you call this.dialogFragment.setSeleccion(this.test) and at this point your dialog now has a reference to your test variable.

    2. You open the dialog again, everything looks fine (because test = [true, true, true]).

    3. You unchecked one of the options, changing the value of DialogFragment.seleccion AND the value of ListaFragment.test (because it was a reference to the same array).

    In summary, Java passed your array by reference to your dialog, so when you updated the value, the original array was updated.