android - Google Play Game Services Multi-Player Device Orientation change kicks user out of room -


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