I'm working on an app which has just one activity (which extends BaseGameActivity), and switch between multiple fragments (much like Google's sample code states).
I'm testing a multi-player game right now, on 2 separate devices. Both users can successfully log-in, send messages to each other, etc. However, the instant one user rotates their device, they get kicked out of the room.
I think this makes sense because the activity is getting destroyed and recreated. But what I don't understand is what do we need to do to allow the user to rotate their device and KEEP the game state (logged in, joined to a room, etc) in tact?
One thought: android:configChanged="orientation|screenSize" - But Android discourages that (for good reasons, in most cases) - but is this the way we have to go with Google Play Game Services to stay in a room on device orientation change?
What about using "onRetainNonConfigurationInstance()" to save the GameHelper instance, and use it again when the activity is recreated?
Or somehow implement the game connection (sign-in, room joining, etc) in a Service?
Or am I thinking about this all the wrong way?! Thanks for your thoughts & help. Code example(s) would also be much appreciated if possible.
Thank you to @Sheldon for pointing me in the right direction regarding
setRetainInstance(true)
on a 'headless' fragment. That's the route I took in solving this problem, and now I'd like to paste my code here to hopefully help others. But first:Verbal Explanation
As stated in the Question, a device orientation change will destroy the
MainActivity extends BaseGameActivity
, and with it your game state (ie. your connection to Google Play Services). However, we can put all our GameHelper code into a 'headless' Fragment (a fragment without a UI), withsetRetainInstance(true)
declared. Now, when ourMainActivity extends FragmentActivity
is destroyed on an orientation change, the headless fragment gets stopped, and even detached, but not destroyed! (onDestroy()
is not called) WhenMainActivity
is re-created by Android, our headless fragment gets re-attached to it automatically. At this time, in our headless fragment,onCreate()
is NOT called. SoonCreate()
is the place we connect to GameHelper. We can disconnect from GameHelper inonDestroy()
because this will never get called, except when the Application finishes (which, at that time, it's ok to kill our connection).Note: I think
GameHeaderFragment.java
should probably be broken up into an Abstract class and a game-specific class which inherits from it (but I didn't do that here).Here's what I came up with (please forgive the areas where my game-specific code interweaves):
GameHeaderFragment.java
MainActivity.java
I have encountered exactly the same issue and I am currently working on fixing this.
Initially I had just implemented using
in the manifest because neither my game nor my fragment based implementation of the Game Services stuff correctly supported orientation changes. i.e. I am confident that the shortcomings are in my own code rather than the Google supplied stuff.
In my case, the users are kicked out of the game because when one or other of the players "rotate", I do not restart my fragments correctly. I then receive the onLeftRoom callback and I choose to finish at this point.
I am taking the opportunity to improve and simplify my own fragment based implementation of the Game Services stuff and this is my basic plan:
A fragment activity (ABS) that comprises:
Some simple UI fragment tabs for "Quick Game", "Leaderboards" etc.
A "headless" (no UI) setRetainInstance(true) fragment that is my equivalent of the "BaseGameActivity" sample and which performs all the calls to GameHelper.
GameHelper - unchanged supplied version.
The advantage of this approach is that by using the "headless" fragment I should avoid some of the tricky problems that I see at the moment. e.g. even after restarting some fragments I still get getActivity() null errors and I find myself trying to solve problems that seem far too complicated for such a simple little game .
By the way, I would not be happy if any game (least of all a dumb one like mine) was running as a service on my phone / tab - but that's just my opinion. I think that a setRetainInstance(true) fragment which dies with its parent activity is perfectly adequate.
I'm interested to hear anybody elses thoughts on this.
For those that are not aware (source http://www.vogella.com/articles/AndroidFragments/article.html#headlessfragments1):
Here's an idea. But before that, a bit of illustration.
Android apps can be killed by Android Resource Manager at any time due to memory or whatever reasons it decides. So, to retain a "always on" permanent deamon, we use services.
A service would be neat to have here because your app can communicate it's state to the service, which in turn holds all the real data (is connected, connected to which server, server connection etc) and just reconnects to the service.
Having this service will add an aditional benefit of potentially telling you that your remote client has disconnected (if the service isn't bound to an app, the user def disconnected) and could help with fine-graining connectivity, as the service mediates between your server and the GUI client. For all intents and purposes, the service is the real client that plays the game, and is just being driven by a gui client which tells the service what it should try to do. This way the service is seemless to the user and his playstate is always retained.
But first I'd try to make my app a singleton, via AndroidManifest to only use one instance of the application (singleTop) or to always use the same process (sameProcess but unsure if that even helps tiny bit).
Then if that fails, I'd take good, less painful routes, until finally I see that the service is the way to go. So maybe there's a lightweight fix for your issue, maybe you just need a simple deamon service and call it a day.