java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = [[Landroid.widget.Button;)

Tags: ,



I am coding a sudoku solver app. However it keeps crashing for some unknown reason.

When I try to debug the app, I don’t get a specific line that tells me what it is in my code that makes it crash. However, I see that it goes through the entire solverActivity class without crashing and shows the correct suoku board for about half a second then it crashes.

I have interpreted the error message as if im trying to send an object (Button) that is not serializable. However, I do not find in my code where I try to do that.

I have googled but have not found any solution that has helped me. Which has led me to write this post.

Here is the stacktrace.

2020-03-08 09:40:19.559 15499-15499/com.example.sodukosolver E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.sodukosolver, PID: 15499 java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = [[Landroid.widget.Button;) at android.os.Parcel.writeSerializable(Parcel.java:1833) at android.os.Parcel.writeValue(Parcel.java:1780) at android.os.Parcel.writeArrayMapInternal(Parcel.java:928) at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1584) at android.os.Bundle.writeToParcel(Bundle.java:1253) at android.app.IActivityTaskManager$Stub$Proxy.activityStopped(IActivityTaskManager.java:4505) at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:145) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) Caused by: java.io.NotSerializableException: androidx.appcompat.widget.AppCompatButton at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1240) at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1434) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1230) at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1434) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1230) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:354) at android.os.Parcel.writeSerializable(Parcel.java:1828) at android.os.Parcel.writeValue(Parcel.java:1780)  at android.os.Parcel.writeArrayMapInternal(Parcel.java:928)  at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1584)  at android.os.Bundle.writeToParcel(Bundle.java:1253)  at android.app.IActivityTaskManager$Stub$Proxy.activityStopped(IActivityTaskManager.java:4505)  at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:145)  at android.os.Handler.handleCallback(Handler.java:883)  at android.os.Handler.dispatchMessage(Handler.java:100)  at android.os.Looper.loop(Looper.java:214)  at android.app.ActivityThread.main(ActivityThread.java:7356)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 

Here is my MainActivity which I think works fine (Sorry for not following CLEAN CODE :/ ).

import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import java.util.HashMap;

public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener {

    private Button[][] verticalButtons = new Button[9][9];
    private Button[][] landscapeButtons = new Button[9][9];
    private Button solveButton, clearButton;
    private HashMap<String, Integer> solveMap = new HashMap<>();
    private Spinner spinner;
    private String number;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        spinner = findViewById(R.id.number_spinner);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.numbers, android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(this);

        solveButton = findViewById(R.id.solve_button);
        clearButton = findViewById(R.id.clear_button);
        solveButton.setOnClickListener(v -> loadResult());

        if(savedInstanceState != null && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
            solveMap = (HashMap<String, Integer>) savedInstanceState.getSerializable("solveMap");
            verticalButtons = findButtons(verticalButtons,"button", false);
            clearButton.setOnClickListener(v -> clearAllButtons(verticalButtons));
            loadButtonValue(verticalButtons);
        }
        else if(savedInstanceState != null && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
            solveMap = (HashMap<String, Integer>) savedInstanceState.getSerializable("solveMap");
            landscapeButtons = findButtons(landscapeButtons,"buttons", false);
            clearButton.setOnClickListener(v -> clearAllButtons(landscapeButtons));
            loadButtonValue(landscapeButtons);
        }
        else if(savedInstanceState == null && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
            verticalButtons = findButtons(verticalButtons,"button", true);
            clearButton.setOnClickListener(v -> clearAllButtons(verticalButtons));
        }else{
            landscapeButtons = findButtons(landscapeButtons,"buttons", true);
            clearButton.setOnClickListener(v -> clearAllButtons(landscapeButtons));
        }
    }

    /**
     * This method finds the buttons of the board based after the orientation of the device
     * @param sudokuButtons 2D Array of Buttons[9][9]
     * @param orientation String referring to xml id, button = PORTRAIT(xml), buttons = LANDSCAPE(xml)
     * @param boardIsEmpty boolean used to determine if code is being created for the first time or being recreated
     * @return a 2D Array of buttons[9][9]
     */
    private Button[][] findButtons(Button[][] sudokuButtons, String orientation, boolean boardIsEmpty){
        for(int i = 0; i < 9; i++)
            for (int j = 0; j < 9; j++){
                String buttonID = orientation + i + j;
                int resId = getResources().getIdentifier(buttonID, "id", getPackageName());
                sudokuButtons[i][j] = findViewById(resId);
                sudokuButtons[i][j].setOnClickListener(v -> updateButtonValue(v));

                if(boardIsEmpty) {
                    solveMap.put("key" + i + j , 0);
                }
            }
        return sudokuButtons;
    }

    /**
     * This method updates the values in a button after it has been clicked
     * @param view
     */
    private void updateButtonValue(View view){
        Button button = findViewById(view.getId());
        String buttonName = getResources().getResourceEntryName(view.getId());
        String numberString = buttonName.replaceAll("\D+","");
        if(number.equals("Select a number")) {
            button.setText("");
            solveMap.put("key" + numberString, 0);
        }
        else {
            button.setText(number);
            solveMap.put("key" + numberString, Integer.valueOf(number));
        }
    }

    /**
     * This method recreates the board after the device's orientation has changed
     * @param sudukoButtons Empty 2D Array of buttons
     */
    private void loadButtonValue(Button[][] sudukoButtons) {
        for (int i = 0; i < 9; i++)
            for (int j = 0; j < 9; j++) {
                String number = String.valueOf(solveMap.get("key" + i + j));
                if (number.equals("0"))
                    sudukoButtons[i][j].setText("");
                else
                    sudukoButtons[i][j].setText(number);
            }
    }

    /**
     * This method changes the activity and sends some parameters to the next activity
     */
    private void loadResult(){
        long time = System.currentTimeMillis();
        int [][] resultBoard = new int[9][9];
        resultBoard = populateBoard(resultBoard);
        Intent intent = new Intent(this, SolverActivity.class);
        Bundle bundle = new Bundle();
        bundle.putSerializable("board", resultBoard);
        intent.putExtras(bundle);
        intent.putExtra("start", time);
        startActivity(intent);
    }

    /**
     * This method retrieves all the values from the buttons and places them in a 2D Array of the type int
     * @param resultBoard Empty 2D Array
     * @return A populated 2D Array, which the user wants to have solved
     */
    private int[][] populateBoard(int[][] resultBoard){
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                int number = solveMap.get("key" + i + j);
                resultBoard[i][j] = number;
            }
        }
        return resultBoard;
    }

    /**
     * Clears all the buttons according the device orientation
     * @param sudokuButtons 2D Array of Buttons which is linked to xml file (PORTRAIT or LANDSCAPE)
     */
    private void clearAllButtons(Button[][] sudokuButtons){
        solveMap.clear();
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                sudokuButtons[i][j].setText("");
                solveMap.put("key" + i + j, 0);
            }
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable("solveMap", solveMap);
        outState.putSerializable("verticalButtons", verticalButtons);
        outState.putSerializable("landscapeButtons", landscapeButtons);
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        this.number = parent.getItemAtPosition(position).toString();
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
}

Here is my SolverActivity which i think is causing the problem (Sorry for the long onCreate, im planning on refactoring the class after i get this solved).

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class SolverActivity extends AppCompatActivity {

    private Button[][] buttons = new Button [9][9];
    private Button backButton;
    private int[][] board, solvedBoard;
    private TextView timeView;
    private long test;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_solver);
        timeView = findViewById(R.id.result_time);
        backButton = findViewById(R.id.back_button);
        backButton.setOnClickListener(v -> onBackPressed());

        board = (int[][]) getIntent().getExtras().getSerializable("board");
        long time = getIntent().getLongExtra("start", 0);

        if(savedInstanceState == null) {
            SudokuSolver sudokuSolver = new SudokuSolver(board);
            if (sudokuSolver.solve())
                solvedBoard = sudokuSolver.getBoard();
            else
                solvedBoard = new int[9][9];
        }
        else{
            solvedBoard = (int[][]) savedInstanceState.getSerializable("solvedBoard");
        }

        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                String buttonId = "button" + i + j;
                int resId = getResources().getIdentifier(buttonId, "id", getPackageName());
                buttons[i][j] = findViewById(resId);
                buttons[i][j].setText(String.valueOf(solvedBoard[i][j]));

            }
        }
        if (savedInstanceState == null)
            test = System.currentTimeMillis();
        else
            test = savedInstanceState.getLong("test");
        long stop = test - time;
        timeView.append(stop + "ms");

    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable("solvedBoard", solvedBoard);
        outState.putLong("test", test);
    }
}

Answer

I found what was caused my app to crash, and I wanted to share my solution if someone else might have an similar problem.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable("solveMap", solveMap);
    // The two lines beneath caused the app to crash
    outState.putSerializable("verticalButtons", verticalButtons);
    outState.putSerializable("landscapeButtons", landscapeButtons);
}

I don’t even know, why I had these to begin with since the where not used. Anyway since i did not need them is simply deleted the two lines, making it look like this.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable("solveMap", solveMap);
}

I think what caused the problem was saving the 2D Array of buttons in this manner. For people that have similar problem I would recommend that look over how you save in OnsaveInstanceState.



Source: stackoverflow