Reputation: 91
People, I'm getting quite stuck on a new Android application I'm developing. For the application (card game) I need to save some data. I use Serialization to get that done.
Now the problem: When I try to implement a interface I made to keep track of the payers turn, the application returns a NoSerializableException from the class Game (main activity). Every thing works when I remove the interface.
The turn class contains the following code:
public class Turn<T> implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
public interface OnTurnEndedListener<T>{
void onTurnEnded(T currentPlayer);
}
private ArrayList<T> players;
private int turnIndex;
private int rounds;
private ArrayList<OnTurnEndedListener<T>> turnEndListenerList;
public Turn() {
throw new UnsupportedOperationException("cannot init without players");
}
public Turn(ArrayList<T> players, int startingPlayerIndex) {
this.players = players;
this.turnIndex = startingPlayerIndex;
this.rounds = 0;
turnEndListenerList = new ArrayList<OnTurnEndedListener<T>>();
}
public int getRounds() {
return rounds;
}
public T next() {
turnIndex = (turnIndex + 1) % players.size();
if (turnIndex == 0) {
rounds++;
}
T retVal = players.get(turnIndex);
for (OnTurnEndedListener<T> l : turnEndListenerList) {
l.onTurnEnded(retVal);
}
return retVal;
}
public T peek() {
return players.get(turnIndex);
}
public void addOnTurnEndedListener(OnTurnEndedListener<T> l) {
this.turnEndListenerList.add(l);
}
}
When I add the following code in the main activity (Game) I get an exception every time I close the activity.
gameData.getTurn().addOnTurnEndedListener(new Turn.OnTurnEndedListener<Hand>() {
@Override
public void onTurnEnded(Hand hand) {
turnEndedHandler(hand);
}
});
You can find the full code of the Game and GameData class, as well as the error log below.
import java.util.ArrayList;
import java.util.Collections;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class Game extends Activity implements OnTouchListener{
private Deck deck;
private GameData gameData;
Hand playerHand, oppHand;
private ImageView ivDeckClosed, ivDeckOpen, ivPlayerCard1, ivPlayerCard2,ivPlayerCard3, ivPlayerCard4, ivPlayerCard5, ivPlayerCard6,ivPlayerCard7, ivPlayerCard8, ivPlayerCard9, ivPlayerCard10,ivPlayerCard11, ivPlayerCard12, ivPlayerCard13, ivPlayerCard14;
private ImageView[] playerCards;
private TextView tvOpp1;
private ArrayList<Hand> playersInOrder;
private LinearLayout llPlayGround,llPlayGroundRow1,llPlayGroundRow2,llCardDeck;
private ArrayList<PlayedSet> playedSets;
public static final String SAVE_FILENAME = "jokerensave.ser";
private SaveHandler savehandler;
private Hand currentHand;
private int defaultStartingPlayer = 0;
public static enum STATES {
start, resume, end
};
public static final int START_CODE = 0;
public static final int RESUME_CODE = 1;
public static final String GAME_STATE = "STATE";
public static final String GAME_DATA = "GameData";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("PUKI","onCreate");
// Get save game
savehandler = SaveHandler.getInstance(this);
gameData = savehandler.readLastState();
setContentView(R.layout.gamescreen);
// Load which state was given by the mainscreen
switch ((STATES) getIntent().getExtras().get(GAME_STATE)) {
case start:
gameData.setFirstRun(true);
Log.i("ONCREATE", "Received state: start");
break;
case resume:
gameData.setFirstRun(false);
Log.i("ONCREATE", "Received state: resume");
break;
default:
gameData.setFirstRun(true);
Log.i("ONCREATE", "Received state: none");
break;
}
// Transferring game data to MainScreen
Bundle b = new Bundle();
b.putInt("int", 5);
b.putSerializable(GAME_DATA, gameData);
Intent i = new Intent();
i.putExtras(b);
setResult(0, i);
}
@Override
protected void onStart() {
super.onStart();
Log.d("PUKI","onStart");
Log.i("FIRSTRUN", "Firstrun = "+gameData.getFirstRun());
init(gameData.getFirstRun());
gameData.getTurn().addOnTurnEndedListener(new Turn.OnTurnEndedListener<Hand>() {
@Override
public void onTurnEnded(Hand hand) {
turnEndedHandler(hand);
}
});
}
private void init(boolean first) {
initGraphics(first);
Log.i("INIT", "Game init graphics");
if (first) {
Log.i("INIT", "Game init core");
initGameCore();
}
}
private void initGameCore() {
deck = new Deck();
playedSets = new ArrayList<PlayedSet>();
// Create array with players and their hand
playersInOrder = new ArrayList<Hand>();
playerHand = new PlayerHand(playerCards, "Player name");
playersInOrder.add(playerHand);
oppHand = new OppHand(new GameStrategy(), null, "Opponent");
playersInOrder.add(oppHand);
// Push all data to gamedata class
gameData.init(playerHand, oppHand, playersInOrder, deck, playedSets, new Turn<Hand>(playersInOrder,defaultStartingPlayer));
gameData.setGameInProgress(true);
// Deal cards to players
dealCards();
}
//TODO
protected void turnEndedHandler(final Hand hand) {
if(hand.isAwaitingInput()){
// This means the turn is for a human player, so do nothing.
Log.i("TURN", "The turn is for the human player: "+hand.getPlayerName());
}
else{
// This means the turn is for a AI. Decide!
Log.i("TURN", "The turn is for the AI player: "+hand.getPlayerName());
gameData.getTurn().next();
// Update players hand size for human player
this.updateOppScore();
}
}
(I removed a lot of code from the above example, because that code isn't needed to solve this problem)
import java.io.Serializable;
import java.util.ArrayList;
public class GameData implements Serializable {
/**
*
*/
private static final long serialVersionUID = -3796450525724090900L;
private Hand playerHand, oppHand;
private ArrayList<Hand> playersInOrder;
private Deck deck;
private boolean gameInProgress,grabbedCard,playerMustThrow,firstRun;
private ArrayList<PlayedSet> playedSets;
private Turn<Hand> turn;
private static GameData instance = new GameData();
public GameData(){
// Do nothing
}
public static GameData getInstance(){
return instance;
}
public void init(Hand playerHand, Hand oppHand, ArrayList<Hand> playersInOrder, Deck deck, ArrayList<PlayedSet> playedSets, Turn<Hand> turn) {
this.playerHand = playerHand;
this.playersInOrder = playersInOrder;
this.oppHand = oppHand;
this.deck = deck;
this.grabbedCard = false;
this.playerMustThrow = false;
this.playedSets = playedSets;
this.firstRun = false;
this.turn = turn;
}
public Hand getPlayerHand(){
return playerHand;
}
public Hand getOppHand(){
return oppHand;
}
public Deck getDeck(){
return deck;
}
public ArrayList<Hand> getPlayersInOrder(){
return playersInOrder;
}
public void setGrabbedCard(boolean set){
this.grabbedCard = set;
}
public boolean getGrabbedCard(){
return grabbedCard;
}
public void setGameInProgress(boolean progress) {
this.gameInProgress = progress;
}
public boolean isGameInProgress(){
return gameInProgress;
}
public void createNewPlaySet(PlayedSet newSet){
playedSets.add(newSet);
}
public ArrayList<PlayedSet> getAllPlayedSets(){
return playedSets;
}
public void setPlayerCanThrow(boolean set){
this.playerMustThrow = set;
}
public boolean canPlayerThrow(){
return playerMustThrow;
}
public boolean getFirstRun(){
return firstRun;
}
public void setFirstRun(boolean set){
this.firstRun = set;
}
public Turn<Hand> getTurn(){
return turn;
}
}
Log:
01-20 21:05:16.678: W/dalvikvm(27035): threadid=1: thread exiting with uncaught exception (group=0x40c5c1f8)
01-20 21:05:16.693: E/AndroidRuntime(27035): FATAL EXCEPTION: main
01-20 21:05:16.693: E/AndroidRuntime(27035): java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = nl.dirkgroenen.jokeren.GameData)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.os.Parcel.writeSerializable(Parcel.java:1181)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.os.Parcel.writeValue(Parcel.java:1135)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.os.Parcel.writeMapInternal(Parcel.java:493)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.os.Bundle.writeToParcel(Bundle.java:1612)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.os.Parcel.writeBundle(Parcel.java:507)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.content.Intent.writeToParcel(Intent.java:6224)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.app.ActivityManagerProxy.finishActivity(ActivityManagerNative.java:1831)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.app.Activity.finish(Activity.java:3709)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.app.Activity.onBackPressed(Activity.java:2124)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.app.Activity.onKeyUp(Activity.java:2099)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.view.KeyEvent.dispatch(KeyEvent.java:2633)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.app.Activity.dispatchKeyEvent(Activity.java:2334)
01-20 21:05:16.693: E/AndroidRuntime(27035): at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1958)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.view.ViewRootImpl.deliverKeyEventPostIme(ViewRootImpl.java:3565)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.view.ViewRootImpl.handleFinishedEvent(ViewRootImpl.java:3538)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2646)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.os.Handler.dispatchMessage(Handler.java:99)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.os.Looper.loop(Looper.java:137)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.app.ActivityThread.main(ActivityThread.java:4511)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.lang.reflect.Method.invokeNative(Native Method)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.lang.reflect.Method.invoke(Method.java:511)
01-20 21:05:16.693: E/AndroidRuntime(27035): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
01-20 21:05:16.693: E/AndroidRuntime(27035): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
01-20 21:05:16.693: E/AndroidRuntime(27035): at dalvik.system.NativeStart.main(Native Method)
01-20 21:05:16.693: E/AndroidRuntime(27035): Caused by: java.io.NotSerializableException: nl.dirkgroenen.jokeren.Game$6
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1364)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1671)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1517)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1481)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.util.ArrayList.writeObject(ArrayList.java:644)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.lang.reflect.Method.invokeNative(Native Method)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.lang.reflect.Method.invoke(Method.java:511)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1053)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1404)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1671)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1517)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1481)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:979)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:368)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1074)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1404)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1671)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1517)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1481)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:979)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:368)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1074)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1404)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1671)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1517)
01-20 21:05:16.693: E/AndroidRuntime(27035): at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1481)
01-20 21:05:16.693: E/AndroidRuntime(27035): at android.os.Parcel.writeSerializable(Parcel.java:1176)
01-20 21:05:16.693: E/AndroidRuntime(27035): ... 23 more
Upvotes: 5
Views: 2574
Reputation: 9584
Your Turn interface has a ArrayList and a ArrayList. If either of them are not serializable you will get this error. The fact that T is decided at runtime creates another debugging
challenge. Although its a good design, I recommend that you javadoc it so that developers to use your Turn interface know that the T has to be Serializable too, else it can break during runtime.
In your case class
Hand
is not Serializable in most probability.
Upvotes: -1
Reputation: 311023
the application returns a NoSerializableException from the class Game (main activity).
No. It throws a NotSerializableException
mentioning the class Game$6, which is an anonymous instance of OnTurnEndedListener,
that you created in the line starting
gameData.getTurn().addOnTurnEndedListener(...)
which doesn't extend Serializable
. So either you have to fix that or make the
private ArrayList<OnTurnEndedListener<T>> turnEndListenerList;
into a transient
variable, whichever it is that you want.
Upvotes: 1
Reputation: 3415
Someone correct me if I am wrong, but I would not recommend using Serialization for saving data. Since any change to the class will render any previously saved versions of the data unusable. You should consider using a independent format for structuring the data, for example JSON or XML, or a custom one.
Using JSON is very easy and maps very easily to (and from) Java objects for example, and can easily be saved as a String Preference in the App. And you can also handle any changes to the Data pojo that holds the saved state in case you want to add or remove stuff in the future.
Upvotes: 2