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); } }
Advertisement
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.