i'm working on app has 1 activity (which extends basegameactivity), , switch between multiple fragments (much google's sample code states).
i'm testing multi-player game right now, on 2 separate devices. both users can log-in, send messages each other, etc. however, instant 1 user rotates device, kicked out of room.
i think makes sense because activity getting destroyed , recreated. don't understand what need allow user rotate device , keep game state (logged in, joined room, etc) in tact?
one thought: android:configchanged="orientation|screensize" - android discourages (for reasons, in cases) - way have go google play game services stay in room on device orientation change?
what using "onretainnonconfigurationinstance()" save gamehelper instance, , use again when activity recreated?
or somehow implement game connection (sign-in, room joining, etc) in service?
or thinking wrong way?! thoughts & help. code example(s) appreciated if possible.
thank @sheldon pointing me in right direction regarding setretaininstance(true)
on 'headless' fragment. that's route took in solving problem, , i'd paste code here others. first:
verbal explanation
as stated in question, device orientation change destroy mainactivity extends basegameactivity
, , game state (ie. connection google play services). however, can put our gamehelper code 'headless' fragment (a fragment without ui), setretaininstance(true)
declared. now, when our mainactivity extends fragmentactivity
destroyed on orientation change, headless fragment gets stopped, , detached, not destroyed! (ondestroy()
not called) when mainactivity
re-created android, our headless fragment gets re-attached automatically. @ time, in our headless fragment, oncreate()
not called. oncreate()
place connect gamehelper. can disconnect gamehelper in ondestroy()
because never called, except when application finishes (which, @ time, it's ok kill our connection).
note: think gameheaderfragment.java
should broken abstract class , game-specific class inherits (but didn't here).
here's came (please forgive areas game-specific code interweaves):
gameheaderfragment.java
public class gamehelperfragment extends fragment implements gamehelperlistener, oninvitationreceivedlistener, roomupdatelistener, roomstatusupdatelistener, realtimemessagereceivedlistener { protected mainactivity mactivity = null; // game helper object. class wrapper around object. protected gamehelper mhelper; final static int max_num_players = 4; // request codes uis show startactivityforresult: final static int rc_select_players = 10000; final static int rc_invitation_inbox = 10001; final static int rc_waiting_room = 10002; // expose these constants here because don't want users of class // have know gamehelper @ all. public static final int client_games = gamehelper.client_games; public static final int client_appstate = gamehelper.client_appstate; public static final int client_plus = gamehelper.client_plus; public static final int client_all = gamehelper.client_all; // requested clients. default, that's games client. protected int mrequestedclients = client_games; protected string msigninginmessage = "signing in google"; protected string msigningoutmessage = "signing out"; // custom members string mmyid = ""; string mroomid = ""; arraylist<participant> mparticipants = null; int mcurrentlyplayingidx = 0; // idx mparticipants boolean mismultiplayer = false; boolean mwaitroomdismissedfromcode = false; public interface gamehelperfragmentlistener { void onsigninfailed(); void onsigninsucceeded(); void oninvitationreceived(invitation invitation); void showmainmenu(); void showwaitscreen(); void startgame(); void participantleftatidx(int idx); void handlerealtimemessage(realtimemessage rtm); } gamehelperfragmentlistener mlistener; public gamehelperfragment() { super(); log.d("mab", "ghfrag.constructor()"); } /** * sets requested clients. preferred way set requested clients * via constructor, method available if reason code * cannot in constructor. must called before oncreate in order * have effect. if called after oncreate, method no-op. * * @param requestedclients combination of flags client_games, client_plus * , client_appstate, or client_all request available clients. */ protected void setrequestedclients(int requestedclients) { mrequestedclients = requestedclients; } @override public void onattach(activity activity) { log.d("mab", + ": onattach(" + activity + ")"); super.onattach(activity); mactivity = (mainactivity) activity; mlistener = (gamehelperfragmentlistener) activity; } @override public void oncreate(bundle b) { log.d("mab", + ": oncreate()"); super.oncreate(b); setretaininstance(true); mhelper = new gamehelper(mactivity); mhelper.setup(this, mrequestedclients); //'this' => gamehelperlistener mhelper.setsigninginmessage(msigninginmessage); mhelper.setsigningoutmessage(msigningoutmessage); mhelper.onstart(mactivity); } @override public view oncreateview(layoutinflater inflater, viewgroup container, bundle savedinstancestate) { return null; // headless fragment } @override public void onactivitycreated(bundle savedinstancestate) { log.d("mab", + ": onactivitycreated()"); super.onactivitycreated(savedinstancestate); } @override public void ondestroy() { log.d("mab", + ": ondestroy()"); super.ondestroy(); mhelper.onstop(); } @override public void onactivityresult(int requestcode, int responsecode, intent data) { log.d("mab", + ": onactivityresult(" + requestcode + ")"); super.onactivityresult(requestcode, responsecode, data); mhelper.onactivityresult(requestcode, responsecode, data); switch (requestcode) { case rc_select_players: // got result "select players" ui -- ready create room handleselectplayersresult(responsecode, data); break; case rc_invitation_inbox: // got result "select invitation" ui (invitation inbox). we're // ready accept selected invitation: handleinvitationinboxresult(responsecode, data); break; case rc_waiting_room: // ignore result if dismissed waiting room code: if (mwaitroomdismissedfromcode) break; // got result "waiting room" ui. if (responsecode == activity.result_ok) { } else if (responsecode == gamesactivityresultcodes.result_left_room) { // player actively indicated want leave room leaveroom(); } else if (responsecode == activity.result_canceled) { leaveroom(); } break; } } // handle result of "select players ui" launched when user clicked // "invite friends" button. react creating room players. private void handleselectplayersresult(int responsecode, intent data) { if (responsecode != activity.result_ok) { log.w("mab", "*** select players ui cancelled, " + responsecode); showmainmenu(); return; } log.d("mab", "select players ui succeeded."); // invitee list final arraylist<string> invitees = data.getstringarraylistextra(gamesclient.extra_players); log.d("mab", "invitee count: " + invitees.size()); // automatch criteria bundle automatchcriteria = null; int minautomatchplayers = data.getintextra(gamesclient.extra_min_automatch_players, 0); int maxautomatchplayers = data.getintextra(gamesclient.extra_max_automatch_players, 0); if (minautomatchplayers > 0 || maxautomatchplayers > 0) { automatchcriteria = roomconfig.createautomatchcriteria( minautomatchplayers, maxautomatchplayers, 0); log.d("mab", "automatch criteria: " + automatchcriteria); } // create room log.d("mab", "creating room..."); roomconfig.builder rtmconfigbuilder = roomconfig.builder(this); rtmconfigbuilder.addplayerstoinvite(invitees); rtmconfigbuilder.setmessagereceivedlistener(this); rtmconfigbuilder.setroomstatusupdatelistener(this); if (automatchcriteria != null) { rtmconfigbuilder.setautomatchcriteria(automatchcriteria); } showwaitscreen(); keepscreenon(); getgamesclient().createroom(rtmconfigbuilder.build()); log.d("mab", "room configured, waiting created..."); } // handle result of invitation inbox ui, player can pick invitation // accept. react accepting selected invitation, if any. private void handleinvitationinboxresult(int response, intent data) { if (response != activity.result_ok) { log.d("mab", "*** invitation inbox ui cancelled, " + response); showmainmenu(); return; } log.d("mab", "invitation inbox ui succeeded."); invitation inv = data.getextras().getparcelable(gamesclient.extra_invitation); // accept invitation acceptinvitetoroom(inv.getinvitationid()); } protected gamesclient getgamesclient() { return mhelper.getgamesclient(); } protected appstateclient getappstateclient() { return mhelper.getappstateclient(); } protected plusclient getplusclient() { return mhelper.getplusclient(); } protected boolean issignedin() { return mhelper.issignedin(); } protected void beginuserinitiatedsignin() { mhelper.beginuserinitiatedsignin(); } protected void signout() { mhelper.signout(); } protected void showalert(string title, string message) { mhelper.showalert(title, message); } protected void showalert(string message) { mhelper.showalert(message); } protected void enabledebuglog(boolean enabled, string tag) { mhelper.enabledebuglog(enabled, tag); } protected string getinvitationid() { return mhelper.getinvitationid(); } protected void reconnectclients(int whichclients) { mhelper.reconnectclients(whichclients); } protected string getscopes() { return mhelper.getscopes(); } protected boolean hassigninerror() { return mhelper.hassigninerror(); } protected connectionresult getsigninerror() { return mhelper.getsigninerror(); } protected void setsigninmessages(string signinginmessage, string signingoutmessage) { msigninginmessage = signinginmessage; msigningoutmessage = signingoutmessage; } public void setroomid(string rid) { mroomid = rid; } public string getroomid() { return mroomid; } @override public void onrealtimemessagereceived(realtimemessage rtm) { mlistener.handlerealtimemessage(rtm); } // called when connected room. we're not ready play yet! (maybe not connected yet). @override public void onconnectedtoroom(room room) { log.d("mab", "onconnectedtoroom."); // room id, participants , id: mroomid = room.getroomid(); mparticipants = room.getparticipants(); mmyid = room.getparticipantid(getgamesclient().getcurrentplayerid()); // print out list of participants (for debug purposes) log.d("mab", "room id: " + mroomid); log.d("mab", "my id " + mmyid); log.d("mab", "<< connected room>>"); log.d("mab", " number of joined participants: " + getnumjoinedparticipants()); } // called when disconnected room. return main screen. @override public void ondisconnectedfromroom(room room) { mismultiplayer = false; mroomid = null; showgameerror("disconnected room"); } @override public void onjoinedroom(int statuscode, room room) { log.d("mab", "onjoinedroom(" + statuscode + ")"); if (room != null) { log.d("mab", " roomid: " + room.getroomid()); } if (statuscode != gamesclient.status_ok) { mismultiplayer = false; log.e("mab", "*** error: onjoinedroom, status " + statuscode); showgameerror("joined room unsuccessfully: " + statuscode); return; } mroomid = room.getroomid(); // show waiting room ui showwaitingroom(room); } // called when we've left room (this happens result of voluntarily leaving // via call leaveroom(). if disconnected, ondisconnectedfromroom()). @override public void onleftroom(int statuscode, string roomid) { // have left room; return main screen. log.d("mab", "onleftroom, code " + statuscode); mroomid = null; //????? right? showmainmenu(); } // called when room connected. @override public void onroomconnected(int statuscode, room room) { log.d("mab", "onroomconnected(" + statuscode + ")"); if (room != null) { log.d("mab", " roomid: " + room.getroomid()); } if (statuscode != gamesclient.status_ok) { mismultiplayer = false; log.d("mab", "*** error: onroomconnected, status " + statuscode); showgameerror("roon connected unsuccessfully: " + statuscode); return; } mroomid = room.getroomid(); mparticipants = room.getparticipants(); // not sure if need here again, shouldn't hurt (or maybe want here) mismultiplayer = true; // set 1st player take turn mcurrentlyplayingidx = 0; // start game! mlistener.startgame(); } // called when room has been created @override public void onroomcreated(int statuscode, room room) { log.d("mab", "onroomcreated(" + statuscode + ")"); if (room != null) { log.d("mab", " roomid: " + room.getroomid()); } if (statuscode != gamesclient.status_ok) { mismultiplayer = false; log.e("mab", "*** error: onroomcreated, status " + statuscode); showgameerror("room not created successfully: " + statuscode); return; } mroomid = room.getroomid(); // show waiting room ui showwaitingroom(room); } // called when invitation play game. react showing user. @override public void oninvitationreceived(invitation invitation) { log.d("mab", "ghfrag.oninvitationreceived()"); mlistener.oninvitationreceived(invitation); } @override public void onsigninfailed() { mlistener.onsigninfailed(); } @override public void onsigninsucceeded() { // install invitation listener notified if receive invitation play game. getgamesclient().registerinvitationlistener(this); if (getinvitationid() != null) { acceptinvitetoroom(getinvitationid()); return; } mlistener.onsigninsucceeded(); } // accept given invitation. void acceptinvitetoroom(string invid) { // accept invitation log.d("mab", "accepting invitation: " + invid); keepscreenon(); roomconfig.builder roomconfigbuilder = roomconfig.builder(this); roomconfigbuilder.setinvitationidtoaccept(invid) .setmessagereceivedlistener(this) .setroomstatusupdatelistener(this); showwaitscreen(); getgamesclient().joinroom(roomconfigbuilder.build()); } // sets flag keep screen on. it's recommended during handshake when setting game, because if screen turns off, game cancelled. void keepscreenon() { getactivity().getwindow().addflags(windowmanager.layoutparams.flag_keep_screen_on); } // clears flag keeps screen on. void stopkeepingscreenon() { getactivity().getwindow().clearflags(windowmanager.layoutparams.flag_keep_screen_on); } public void invitefriends() { // show list of invitable players intent intent = getgamesclient().getselectplayersintent(1, 3); showwaitscreen(); startactivityforresult(intent, rc_select_players); } // leave room. void leaveroom() { log.d("mab", "leaving room."); mismultiplayer = false; stopkeepingscreenon(); if (mroomid != null) { getgamesclient().leaveroom(this, mroomid); mroomid = null; showwaitscreen(); } else { showmainmenu(); } } // show waiting room ui track progress of other players enter // room , connected. void showwaitingroom(room room) { log.d("mab", "ghfrag.showwaitingroom()"); mwaitroomdismissedfromcode = false; int minplayers = max_num_players; // means "start" menu item never enabled (waiting room exit automatically once has made decision) intent = getgamesclient().getrealtimewaitingroomintent(room, minplayers); // show waiting room ui getactivity().startactivityforresult(i, rc_waiting_room); } // forcibly dismiss waiting room ui (this useful, example, if realize // game needs start because else starting play). void dismisswaitingroom() { mwaitroomdismissedfromcode = true; getactivity().finishactivity(rc_waiting_room); //getactivity() ????? } // show error message game being cancelled , return main screen. void showgameerror(string msg) { showalert("error", "game error: " + msg); showmainmenu(); } private void showmainmenu() { mlistener.showmainmenu(); } private void showwaitscreen() { mlistener.showwaitscreen(); } }
mainactivity.java
public class mainactivity extends fragmentactivity implements mainmenufragment.listener, playfragment.listener, gamehelperfragmentlistener, alertdialogfragmentlistener { public static final string main_menu_fragment = "mainmenufragment"; public static final string play_fragment = "playfragment"; public static final string wait_fragment = "waitfragment"; // fragments mainmenufragment mmainmenufragment; playfragment mplayfragment; waitfragment mwaitfragment; gamehelperfragment gamehelperfragment = null; string mincominginvitationid = null; @suppresslint("newapi") @override public void oncreate(bundle savedinstancestate) { log.d("mab", "mainactivity.oncreate()"); super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); // add headless fragment (if not retained) gamehelperfragment = (gamehelperfragment) getsupportfragmentmanager().findfragmentbytag("gamehelperfragment"); if (gamehelperfragment == null) { log.d("mab", + ": existing fragment not found.!!!"); gamehelperfragment = new gamehelperfragment(); gamehelperfragment.setsigninmessages("signing in google", "signing out"); getsupportfragmentmanager().begintransaction().add(gamehelperfragment, "gamehelperfragment").commit(); } else { log.d("mab", + ": existing fragment found.!!!"); } } @override public void onsigninfailed() { log.d("mab", "mainactivity.onsigninfailed()"); if (mmainmenufragment != null) { mmainmenufragment.updateui(); } } @override public void onsigninsucceeded() { log.d("mab", "mainactivity.onsigninsuccedded()"); if (mmainmenufragment != null) { mmainmenufragment.updateui(); } } @override public void onsigninbuttonclicked() { log.d("mab", "mainactivity.onsigninbuttonclicked()"); // start sign-in flow beginuserinitiatedsignin(); } @override public void onsignoutbuttonclicked() { log.d("mab", "mainactivity.onsignoutbuttonclicked()"); signout(); if (mmainmenufragment != null) { mmainmenufragment.updateui(); } } @override public void oninvitationreceived(invitation invitation) { mincominginvitationid = invitation.getinvitationid(); // show accept/decline dialog box here. string dispname = invitation.getinviter().getdisplayname(); dialogfragment alertinvitationreceived = alertdialogfragment.newinstance("invitation received", dispname + " inviting play yahtzee blast.", "accept", "decline", null); alertinvitationreceived.show(getsupportfragmentmanager(), dlg_invitation_recvd); } @override protected void onpause() { log.d("mab", "mainactivity.onpause()"); super.onpause(); } @override protected void onstop() { log.d("mab", "mainactivity.onstop()"); super.onstop(); } @override protected void onstart() { log.d("mab", "mainactivity.onstart()"); super.onstart(); } @override protected void onresume() { log.d("mab", "mainactivity.onresume()"); super.onresume(); } @override protected void ondestroy() { log.d("mab", "mainactivity.ondestroy()"); super.ondestroy(); mhelper = null; } @override protected void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); outstate.putstring("mincominginvitationid", mincominginvitationid); // ? need ? } @override public void oninvitefriendsclicked() { log.d("mab", "mainactivity.oninvitefriendsclicked()"); gamehelperfragment.invitefriends(); } @override public void onseeallinvitationsclicked() { log.d("mab", "mainactivity.onseeallinvitationsclicked()"); gamehelperfragment.seeallinvitations(); } @override public void onactivityresult(int requestcode, int responsecode, intent intent) { log.d("mab", + ": onactivityresult(requestcode: " + requestcode + ", responsecode: " + responsecode + ")"); super.onactivityresult(requestcode, responsecode, intent); // call gamehelper's onactivityresult in case result pertains gamehelperfragment.onactivityresult(requestcode, responsecode, intent); } public void onalertdialogfragmentpositiveclicked(string tag) { log.d("mab", "mainactivity.onalertdialogfragmentpositiveclicked(" + tag + ")"); if (tag == dlg_invitation_recvd) { gamehelperfragment.acceptinvitetoroom(mincominginvitationid); } } // called when receive real-time message network. public void handlerealtimemessage(realtimemessage rtm) { log.d(tag, "mainactivity.onrealtimemessagereceived()"); // handle here... } // headless fragment functions private void setsigninmessages(string signinginmessage, string signingoutmessage) { gamehelperfragment.setsigninmessages(signinginmessage, signingoutmessage); } private gamesclient getgamesclient() { return gamehelperfragment.getgamesclient(); } private string getinvitationid() { return gamehelperfragment.getinvitationid(); } private void beginuserinitiatedsignin() { gamehelperfragment.beginuserinitiatedsignin(); } private void signout() { gamehelperfragment.signout(); } private void showalert(string message) { gamehelperfragment.showalert(message); } private void showalert(string title, string message) { gamehelperfragment.showalert(title, message); } public gamehelperfragment getgamehelperfragment() { return gamehelperfragment; } @override public void showmainmenu() { switchtofragment(main_menu_fragment, false); } @override public void showwaitscreen() { switchtofragment(wait_fragment, false); } @override public void participantleftatidx(int idx) { // handle here, if there's need do. } }
Comments
Post a Comment