My application is to support only landscape content. That content will be generated on-the-fly during runtime, i.e. there will be lots of addView calls to instantiate the UI hierarchy, with some ObjectAnimator calls to move widgets around.
At some point the user might rotate the device to reverseLandscape, insert it in a dock, etc. triggering an activity restart.
Since the appearance on screen should remain the same after the config change I am looking for a lightweight solution to retain the hierarchy.
Adding android:configChanges="orientation|screenSize|keyboardHidden" is discouraged by Google since it does not cover all configuration change cases.
I could create a fragment to contain the UI and call its setRetainState(true) - but again Google discourages that use.
Is there any kind of robust serialization approach to simply write out the current activity state and recreate it once the config change is complete ?
Related
I tried to solve this problem, searched through the Internet, no real answer was found.
I'm developing an app where the YouTube player is needed. This player is the YouTubePlayerSupportFragment since it's in a support fragment (so it's also nested). After the initial setup I realized that whenever I rotate the phone, the video stops playing and it has to be restarted again. This is obviously not what I want.
Then I found some SO answers (like this) where people say that the Activity needs to handle the config changes, so I added android:configChanges="keyboardHidden|orientation|screenSize" to the Activity in the manifest. This solved the problem, the video now keeps playing even on orientation change and the full-screen rotation doesn't look ugly. Cool, I said, only to realize that this messed up big time the other parts of the app because the config is now being updated after all the views are in place. This means that if my phone was in portrait mode and I rotated it to landscape, the layout being used was still going to be the portrait one.
I found solutions for this suggesting calling setContentView(...) in the Activity's onConfigurationChanged(...) and reinflating the view in the Fragments, but this seems to be a rather cumbersome if not terrible solution as It's not just a simple content setting I need, the fragments need to be retained (e.g. scroll position in RecyclerView) too. The built-in setRetainInstance(true) does not work in this case as the view would be recreated with that either, but the config is still the old one when the onCreateView(...) is being called.
It would be nice if I could catch the configChanges events if the user is on that screen (fragment) only but retain Activity recreation anywhere else.
Don't suggest using more Activities, I cannot use a separate Activity for this screen and the rest of the app because of UI/UX reasons.
As per this YouTube Android Player API Guidelines
It is encouraged that you handle configuration changes caused by
orientation in your activity manually, to avoid recreating this
fragment's view every time the configuration changes. For the best
user experience, you should also manually handle the fullscreen event
by changing the layout of your activity.(See the second approch)
As you are already handling on configuration change on your own, I can see two possible solutions of your problem.
Set setFullscreenControlFlags (To your YouTubePlayer object) to FULLSCREEN_FLAG_ALWAYS_FULLSCREEN_IN_LANDSCAPE (This will causes the player to automatically enter fullscreen whenever the device enters landscape orientation.) in conjunction with FULLSCREEN_FLAG_CONTROL_ORIENTATION(To enable automatic control of the orientation.) flag.
Note that this flag should only be set if your activity is locked in
portrait (and is specified as such in the manifest).
The flag is useful if you don't have a landscape layout for your
activity, but would like to enable landscape orientation solely for
fullscreen video watching.
This approach might solve your problem and it is also suggested by official documentation as I mentioned above. In setFullscreenControlFlags method set FULLSCREEN_FLAG_CUSTOM_LAYOUT, that disables the default fullscreen layout handler, enabling you to control the transition to fullscreen layout manually. As well as enabling you to implement a custom fullscreen layout, this option also provides the advantage of avoiding the rebuffering that occurs in the default fullscreen behavior.
An application implementing custom fullscreen behavior should ensure
two things
That the player's view takes up the whole available space of
application's window whenever onFullscreen(boolean) is called. That
the activity is not recreated when the orientation changes to
landscape. To achieve this for an activity that supports portrait, you
need to specify that your activity handles some configuration changes
on its own in your application's manifest, including orientation,
keyboardHidden and screenSize.
See here on how to use this flag.
As per the first link I shared, this second approach will avoid recreating fragment's view every time the configuration changes. Also you don't need to call setContentView(...) in onConfigurationChanged(...).
Hope this will help you.
A frequently asked question is "how do you maintain your activity's state between configuration changes?".
Answers to this question seem largely dependent upon the developer's preference. However, one thing does appear certain - refrain from using android:configChanges="orientation|screenSize" in the Manifest file (see LINK).
Therefore, to ensure stability we ought to retain an object during configuration change as suggested by android (see LINK). However, this requires the use of onRetainNonConfigurationInstance that has been deprecated in API 13; instead it suggests that we use setRetainInstance of the Fragment class.
Given this preference by Android for fragments, should we now be designing our activities where the main UI is itself a fragment, and the activity just serves as a 'driver' or 'fragment manager' for the 'main fragment' and any possible 'fragment children' it may have?
In addition, am I right in thinking that setting android:configChanges="orientation|screenSize" in the manifest file is actually okay providing you're using the same resources for both landscape and portrait views?
Therefore, to ensure stability we ought to retain an object during configuration change as suggested by android
That is the second-tier solution. Where possible, simply contribute to the instance state Bundle (e.g., onSaveInstanceState() in your activity or fragment). Use onRetainNonConfigurationInstance() or a retained fragment where you have instance state that cannot be stored in a Bundle.
should we now be designing our activities where the main UI is itself a fragment, and the activity just serves as a 'driver' or 'fragment manager' for the 'main fragment' and any possible 'fragment children' it may have?
You are certainly welcome to design your UI that way if you wish.
am I right in thinking that setting android:configChanges="orientation|screenSize" in the manifest file is actually okay providing you're using the same resources for both landscape and portrait views?
No, insofar as your app will then break for every other configuration change (locale change, SIM card change, keyboard change, etc.). If you use android:configChanges, usually you need to handle all configuration changes that way.
I'm referring to Why use Fragment#setRetainInstance(boolean)?
The reason I ask so is for Activity to handle rotation, Official Activity Documentation encourages us to let Activity shut-down and restart during rotation.
android:configChanges Lists configuration changes that the activity
will handle itself. When a configuration change occurs at runtime, the
activity is shut down and restarted by default, but declaring a
configuration with this attribute will prevent the activity from being
restarted. Instead, the activity remains running and its
onConfigurationChanged() method is called. Note: Using this attribute
should be avoided and used only as a last-resort. Please read Handling
Runtime Changes for more information about how to properly handle a
restart due to a configuration change.
Any attempt to change this Activity default behavior seems to be bad practice. To avoid Activity from reloading time consuming data structure during restarting, we make make use of onRetainNonConfigurationInstance and getLastNonConfigurationInstance. - Official Handling Runtime Changes
However, when comes to handling rotation in Fragment, does Google give us different recommendation? They do not want us to shut down and restart Fragment?
public Object onRetainNonConfigurationInstance ()
This method was deprecated in API level 13. Use the new Fragment API
setRetainInstance(boolean) instead; this is also available on older
platforms through the Android compatibility package.
Why does Google encourage us to shut down and restart Activity during rotation, but encourage us to retain Fragment during rotation?
If setRetainInstance(true) is good in handling rotation, why don't Google make it as Fragment's default behavior?
Configuration changes: when suddenly screen becomes much wider and much less in height (typical landscape), it is apt for a visual component to update its display and more intelligently use the screen available. Another examples of config change are user sliding the hardware keyboard, device language changing, and so on. why re-start :
Android components favor declarative layout, you load a bunch of XML layouts, and work from there. Finding every View and re-arranging/updating it in real time will be a mess, not to mention the re-wiring of all the event handlers and other custom View code. Its way easier to reload another bunch of layout files.
Also, In Android, Activities kind of live at the mercy of system, so naturally, Activity life cycle is so designed (and recommended) that it is capable of re-creating itself on demand , any time, just as it was before it was destroyed. This pattern accommodates all re-starts, those due to configuration changes as well. If you make your Activities and Fragments capable of maintaining an eternal state, configuration changes won't be that much of a problem.
Retain state data (Models), not the stuff displaying it (UI and Views).
setRetainInstance(true): It is recommended only to be used with fragments that do not hold any reference to anything, that will be recreated on rotation. This means you should not use it on any Fragment that holds Context, Views, etc. A typical Visual fragment does. But it is very useful with Fragments that hold objects like running Threads, AsyncTasks, Data Collections, loaded assets, fetched results etc. This method helps in using a non visual Fragment, as a detachable holder, for non Context-dependent objects of an Activity.
Because you are misunderstanding its use. setRetainInstance(true) should only be used in fragments that are like solo elements/modules. Fragment that handle sockets etc. an don't have a GUI really benefit from being retained. Fragments with a GUI should probably not use setRetainInstance(true). Also any fragments that goes to the backstack shouldn't use setRetainIstance(true).
You could generalize it to any fragment which handles only data/connection etc. should use setRetainInstance(true). But there is a multitude of different ways to use Fragments, which wouldn't benefit of setRetainInstance(true).
I have used both approaches:
Let the activity be destroyed on rotation
Don't let the activity be destroyed on rotation
My approach almost everytime is to catch the rotation event and if needed call the setContentView and add some components again. If not, just let it rotate and the layouts are designed to adapt.
So far I only have seen advantages on letting it be destroyed on screens with very complex construction that are very dynamic, and whenever I rotate and not destroy show some flickering when re-building the screen.
The overhead of having to pass the state with onSaveInstance, onRestoreInstace is sometimes very error prone, and somehow time consuming.
Am I missing something?
UPDATE:
I'm not doing any kind of if "Orientation.XPTO == ..." on my code. This is the logic of each of the 2 approaches (the code is reused):
When destroying
onCreate -> DrawUI() setContentView and add views -> fill() add content
When not destroyed:
onCreate -> DrawUI() setContentView and add views -> fill() add content
onRotation -> DrawUI() setContentView and add views -> fill() add content
When calling setContentView after rotation it will pick the right layout for the device orientation (Check this answer by Google's Reto Meier https://stackoverflow.com/a/456918/327011 )
And the DrawUI and fill would have to have the logic for both the portrait and landscape layouts as the activity can be created on each of the two orientations to begin with.
Am I missing something?
Yes. You are assuming that your alternative is somehow less error prone.
By not going through the destroy-and-recreate cycle, you have to ensure that you are handling changing every resource for every possible configuration change.
Don't let the activity be destroyed on rotation
Unless you are using android:screenOrientation to force your activity into a single orientation (e.g., landscape), you cannot only handle rotation-related configuration changes. You need to handle all configuration changes. Otherwise, as soon as the user drops their device into a dock, removes it from a dock, changes language from Settings, attaches or detaches a keyboard, changes the global font scaling, etc., your app will break.
This, in turn, means that on every configuration change, you need to:
update your UI for your potentially new string resources
adjust or reload your layouts (and by "adjust" that includes changing any drawables, animations, menus, etc.)
anything else tied to your resources (e.g., array lists in your PreferenceFragment)
The problem is that you are going to forget something. For example, you will miss changing a string associated with an action bar item, so now most of your UI is in Spanish and that action bar item is in English. The sorts of things you are going to forget will be less obvious (how often do you test your Spanish translation?).
Your activity is destroyed to give you the opportunity to reconfigure yourself for the new orientation.
From the developer.android.com:
When the screen changes orientation, the system destroys and recreates
the foreground activity because the screen configuration has changed
and your activity might need to load alternative resources (such as
the layout).
For example, in landscape mode you may require a completely different layout, or may want to load in graphics that would not appear stretched. The best way of doing this is allowing the activity to be created again, which will allow the linking to the layout file to change to a more orientation-friendly layout.
See http://developer.android.com/training/basics/activity-lifecycle/recreating.html for more info and how to deal with the orientation change
If you want to disable the recreation you can add
android:configChanges="orientation"
to your Activity element in AndroidManifest.xml. This way your Activity will not be reloaded.
onSaveInstance and onRestoreInstace should only be used for passing through session information, for example the current text in a TextField, and nothing generic that can just be loaded in again after onCreate.
If you, restarting the Activity, requires recovering large sets of data, re-establishing a network connection, or perform other intensive operations then using the onSaveInstanceState() could potentially cause your noted symptoms:
A poor user experience (i.e. "show some flickering")
Require consumption of a lot of memory
onSaveInstanceState() callbacks are not designed to carry large objects.
To retain an object during a runtime configuration change:
Override the onRetainNonConfigurationInstance() method to return the object you would like to retain.
When your activity is created again, call getLastNonConfigurationInstance() to recover your object.
However:
While you can return any object, you should never pass an object that is tied to the Activity, such as a Drawable, an Adapter, a View or any other object that's associated with a Context. If you do, it will leak all the views and resources of the original activity instance. (Leaking resources means that your application maintains a hold on them and they cannot be garbage-collected, so lots of memory can be lost.)
Source
Unless you are able to pass the Object(s) smoothly I personally think it is more advantageous to handle the configuration change yourself, meaning not to destroy.
If you have a target API of 13 or higher: You must include screenSize in your configChanges. Starting with API 13 the screen size also changes on orientation change and you'll need to account for this. Prior to 13 your Activity would handle this itself.
android:configChanges="orientation|screenSize"
Some time it is useful when you are using different layouts for (Landscape / Portrait ). and using different type of views for example ListView in portrait and GridView in landscape.
I guess you are not considering the standard way of creating the android layouts. Please correct me If I'm wrong. Are you using two res folders with -port,-land separately to tell android system to choose in runtime to load the different assets and layout on the basis of orientation.
This example can give you a clue to manage layouts in different orientations.
Here is the android stanard document. Please check with "land" and "port".
Hope this will help you.
I want to be able to change the layout when a device is re-orientated to landscape or portrait. For speed and resource purposes (plus other issues applicable to my app) I do NOT want my app to be destroyed and restarted. I have several objects which I wish to retain between orientation changes as there is no benefit from destroying and re-creating them! I simply just want to change the position of some buttons and TextViews so that they suit the current orientation. Easy right?
Well no it isn't. To achieve the above I included in the app Manifest the configChange option for orientation change. Then I've implemented the onConfigurationChanged() where I determine and apply the appropriate layout. Simple yes?
But now take the textview I have in my layout. How on earth, using this particular method of responding to orientation changes, do I put the same text in the previous textview to the new textview? No instance data is passed to onConfigurationChanged() method. Also for some of the buttons, they could be disabled or enabled... I need to know this after orienatation change.
If I let Android destroy and restart my activity it's first going to create unnecessary work. All I want is to just move a few buttons and textviews.. NOT restart the whole app. That's just ludicrous!
Can anyone help me achieve what need?
An easy way to maintain configuration-independent data is to make use of onRetainNonConfigurationInstance() and its companion method getLastNonConfigurationInstance(). Just return an object that contains all the data that you want to reuse when your activity is recreated.
In Honeycomb, or if you are using the Android compatibility package, you can just call Fragment.setRetainInstance(true) instead. See the docs.