I use single Activity pattern in my app using Navigation component. I use YouTube Android library for playing the video. When I click full screen icon on video player the top and bottom tool bars have to be gone and the screen has to be changed on landscape mode. But after the screen has rotated the activity was recreated and video stops and starts over. The question is how to keep playing the video after the screen has rotated?
I found one solution to add configChanges to the manifest file
<activity
android:name=".ui.MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This solved my problem, the activity stopped being recreated when the screen was rotated. But I do not want this behavior in all fragments, I need it only in the fragment where the video player is located.
This is my code in Fragment:
private fun fullScreenListener() {
val decorView = activity?.window?.decorView?.let {
val screenListener = object : YouTubePlayerFullScreenListener {
override fun onYouTubePlayerEnterFullScreen() {
binding.youtubePlayer.enterFullScreen()
hideSystemUi(it)
}
override fun onYouTubePlayerExitFullScreen() {
showSystemUi(it)
}
}
binding.youtubePlayer.addFullScreenListener(screenListener)
}
}
private fun hideSystemUi(view: View) {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
MainActivity.hideBottomNavBar()
WindowCompat.setDecorFitsSystemWindows(requireActivity().window, false)
WindowInsetsControllerCompat(requireActivity().window,view).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
private fun showSystemUi(view: View) {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
MainActivity.showBottomNavBar()
WindowCompat.setDecorFitsSystemWindows(requireActivity().window, true)
WindowInsetsControllerCompat(requireActivity().window, view).show(WindowInsetsCompat.Type.systemBars())
}
You really do not want to do this. The problem isn't just the restart on rotation, it's that there's at LEAST a dozen situations that can cause an Activity restart, and you can't block some of them. On Android this is really just something you need to live with, and learn how to code to make it cleanly restart.
And no, you can't do configChanges at runtime or only for some fragments. It works on an Activity level.
Instead, you should ask a different question- tell use what isn't working when you rotate, and ask how to fix that with restart.
Based on your new answer- I'm surprised your video view doesn't support this without work. However, if you implement onSaveInstanceState to save the seek time of the video and onRestoreInstanceState to seek to that time, it should work with at most a brief hiccup as it reads in the video.
Sorry i am not a kotlin developer, but this solution fixed mine. But before i post the codes, let me explain it to you, although it's not the most reliable but a better option.
Note: If you add these lines to your manifest, there are a lot of android configuration changes that will not be handled by below lines.
Take for instance you added this line to your manifest file.
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout">
Now, i'll prove to you that adding above line is not an option at all:
Firstly, assuming this line is still in your manifest file, then if your app targets Api level 29 and above, toggle the android system ui dark mode which is located in Settings > Display & Brightness > Dark theme then return back to your app and you'll notice that your activity has been recreated and the video restarts.
Now, to avoid that, then you'll need to add Uimode to the above line of code.
android:configChanges="uimode|orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
(Notice the difference between it and the first code)
Now that you've added uimode to configChanges, the particular activity won't be able to detect changes when the android system ui dark mode switch is toggled. But it's still not the best because it will cause the following:
A bad user experience whereby if a user switches theme but theme changes doesn't reflect in your app.
Let's assume that you have an Alert dialog that's still showing and you rotate your screen, the width tends to overlap the screen due to the smallestScreenSize | screenLayout attribute.
Let's assume that you're onMultiWindowChanged, it'll cause bad user experience too wherby the activity will want to resize and recreate the screen ui layout in order to adjust to the multi window mode but you'll end up seeing overlaps.
Anyways, there are so many configuration changes that will cause activity to restart and instead of adding this line and changing the configChanges attribute everytime just make use of the:
onSavedInsatnceState and onRestoreInstanceState attributes or
Make use of android new method of saving ui state which is viewModel and savedStateHandle
Now, if you want use method 1, you need to understand Android lifecycle architecture component first then use the onSavedInsatnceState to save and use the onRestoreInsatnceState to restore the ui states. But according to https://developer.android.com/reference/android/app/Activity
Starting with Honeycomb, an application is not in the killable state until its onStop() has returned. This impacts when onSaveInstanceState(android.os.Bundle) may be called (it may be safely called after onPause()) and allows an application to safely wait until onStop() to save persistent state.
Declare this as global variable
private final String KEY_YOUTUBE_VIDEO_LENGTH_STATE = "youtube_length_state";
Override onSavedInsatnceState method and add below codes.
#Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
// save YouTube video length state
long videoStateLong = binding.youtubePlayer.getVideoLength();
savedInstanceState.putLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE, videoStateLong);
//Call below line to save bundle
super.onSaveInstanceState(savedInstanceState);
}
Then override onRestoreInstanceState and add below lines.
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Retrieve video state and it's length.
if(savedInstanceState != null) {
binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}
Finally, incase onRestoreInstanceState is not called then override onResume method and add below lines of codes.
#Override
protected void onResume() {
super.onResume();
Bundle savedInstanceState = new Bundle();
if (savedInstanceState != null) {
binding.youtubePlayer.getVideoLength.onRestoreInstanceState(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}
}
Now, in the onCreate method, add below lines
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState==null){
playVideoFromBeginning(); // No video length is saved yet, play video from beginning
}else{
restoreVideoPreviousLength(savedInstanceState); // Restore video length found in the Bundle and pass savedInstanceState as an argument
}
}
public void restoreVideoPreviousLength(Bundle savedInstanceState) {
binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}
Note:
codes in onCreate method will only work for screen rotations but those in onResume will work for uimode change etc.
onSavedInsatnceState and onRestoreInstanceState should NEVER be used to store large datasets like fetching Recyclerview items. ViewModel should be used in cases like this instead.
Now, if you want to use the second method which is viewModel method:
Firstly, understand Android viewModel Android MVVM design overview which includes:
The Android livedata https://developer.android.com/topic/libraries/architecture/livedata
The Saved State Module for ViewModel
The ViewModel
Note: ViewModel's only responsibility is to manage the data for the UI. It should never access your view hierarchy or hold a reference back to the Activity or the Fragment.
Now you can learn more from here https://www.geeksforgeeks.org/viewmodel-with-savedstate-in-android/
Remember, i'm not a kotlin developer
Related
I've got an Android application which maintains state regarding distance traveled, time elapsed, etc. This state I can conveniently store in an object and store a reference to that object in the Bundle when Android calls onDestroy() when the user changes the screen orientation, then restore the state in onCreate(Bundle savedBundle). However, I also have some state in the Buttons and EditText objects on the screen that I want to persist through screen orientations. For example, in onStart(Bundle savedBundle) I call:
_timerButton.setBackgroundColor(Color.GREEN);
_pauseButton.setBackgroundColor(Color.YELLOW);
_pauseButton.setEnabled(false);
Then throughout the operation of my app, the colors/enabled status of these buttons will be changed. Is there a more convenient way to persist the state of user interface items (EditText, Button objects, etc) without having to manually save/restore each attribute for each button? It feels really clumsy to have to manually manage this type of state in between screen orientations.
Thanks for any help.
Add android:configChanges in the Manifest file
<activity name= ".MainActivity"
android:configChanges="orientation|screenSize"/>
By default, this does not work because changing the orientation causes the onCreate method to be called again and redraws the view.
However, if this parameter is included, the framework will handle preserving the state of the screen or layout if the orientation is changed.
Refer following official documentation for more info:
Activity Lifecycle
Handling configuration changes
To save your variable or values you should use onSaveInstanceState(Bundle); and when orientation changes then should recover values should use onRestoreInstanceState() as well, but not very common. (onRestoreInstanceState() is called after onStart(), whereas onCreate() is called before onStart().
Use the put methods to store values in onSaveInstanceState()
protected void onSaveInstanceState(Bundle icicle) {
super.onSaveInstanceState(icicle);
icicle.putLong("param", value);
}
And restore the values in onCreate():
public void onCreate(Bundle icicle) {
if (icicle != null){
value = icicle.getLong("param");
}
}
I have an app that adds to a total variable when a button is clicked.
However if I turn my phone so the layout gets turned into the horizontal layout the values all get reset, and idea why this is and how to stop it?
Thanks!
When you move your device, your device's Orientation State changes from Portrait to Landscape of from Lanscape to Portrait.
In this Orientation change, your Activity's onCreate Method is called every time.
Therefore the values in your Activity are being reset.
There are 2 ways of solving this problem:
1) Let You Activity manage it for you.
2) Managing the changes yourself by saving and restoring states.
Using 1st way of solving this problem:
Just add this line in your Activity Node in your Manifest.xml file.
android:configChanges="orientation|keyboardHidden|screenSize"
For example:
<activity
android:name=".MyMainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="#string/app_name" >
Using the second Way:
You can override these two Methods:
public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
//Manage your Data Explicitly here.
}
public void onRestoreInstanceState(Bundle bundle) {
super.onRestoreInstanceState(bundle);
//Manage your Data Explicitly here.
}
EDITAccording to Android Dev Guide:
Using android:configChanges="orientation|keyboardHidden|screenSize" is not a good practice.
Quote from this page
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.
I recommend you to follow the Android Dev Guide for Handling Run-time Changes and follow the good Practices.
you need to save the variable in OnSaveInstanceState and restore it in onRestoreInstnace state
For example;
#Override
public void onSaveInstanceState(Bundle bundle) {
bundle.putParcelableArrayList(PEOPLE, people);
super.onSaveInstanceState(bundle);
}
#Override
public void onRestoreInstanceState(Bundle bundle) {
super.onRestoreInstanceState(bundle);
people = bundle.getParcelableArrayList(PEOPLE);
}
This happens because the normal way Android handles an activity during any configuration change (including screen reorientations) is to destroy the activity and recreate it. As described in the guide topic Handling Runtime Changes, you can handle it a couple of ways. The "Android way" is to save your activity's state information by overriding onSaveInstanceState and onRestoreInstanceState methods. The details about how to use these methods can be found in the guide topic Recreating an Activity.
The other way to prevent this problem is to tell Android that your activity will handle configuration changes internally. You do this by adding android:configChanges="orientation" to the <activity> tag in the manifest for the activity and overriding the onConfigurationChanged method of the activity to actually handle the changes.
I'm currently trying to learn to work with Views and states. I'm normally able to set its color in functions like in the following:
View mColorRegion = findViewById(R.id.color_region);
mColorRegion.setBackgroundColor(Color.CYAN);
However, I can't seem to be able to set the color in an onRestoreInstanceState(), as
mColorRegion.setBackgroundColor(savedInstanceState.getInt("color"));
However, working with the same View as a TextView, I'm able to restore text as in the following:
TextView mText = (TextView)findViewById(R.id.color_region);
mText.setText(savedInstanceState.getString("text");
What's the difference, and how I can set the background color in onRestoreInstanceState()?
EDIT: Since the original post, I've noticed two things:
1) mColorRegion.setBackgroundColor(Color.CYAN) doesn't seem to work in onCreate() either.
2) Even though the following function correctly changes the View color when a button is pressed, it doesn't work if I call it directly from onRestoreInstanceState():
public void centerButton1(View clickedButton) {
mColorRegion.setBackgroundColor(Color.CYAN);
}
Hmm...
So I found a "half-solution". If you add the following line to AndroidManifest.xml, it will preserve the color during orientation changes:
android:configChanges="orientation|screenSize"
However, this still doesn't answer why I can set the text but not the color in onRetoreInstanceState() or onCreate()...
Many people frown upon using android:configChanges="orientation|screenSize", like Google's Dianne Hackborn. For one, It will make the process of switching between multiple layouts for your app very difficult (for example, if you want one layout for landscape, and one for portrait) since you'll have to do all of the work that Android normally does automatically for you, in onConfigurationChanged().
Anyway, I also had this sort of problem. I was creating a DialogPreference, and upon rotation I couldn't change the progress of a SeekBar in onRestoreInstanceState(savedInstanceBundle)...so this is what I suggest if you cannot use onCreate(savedInstanceBundle) or onActivityCreated(savedInstanceBundle) (for fragments) to restore the state of your view objects:
1) Make a private class member called "mRestoredBGColor",
private int mRestoredBGColor = -1;
2) In onRestoreInstanceState():
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
mRestoredBGColor = savedInstanceState.getInt("key_you_used_in_onSaveInstanceState");
}
3) Then in onResume(), because onRestoreInstanceState() is called after onStart(), but before onResume()
#Override
public void onResume(){
super.onResume();
if(mColorRegion != null && mRestoredBGColor != -1){
mColorRegion.setBackgroundColor(mRestoredBGColor);
mRestoredBGColor = -1; //to make sure it only sets this once per rotation.
}
}
Hope this helps somebody. I believe there is always another way (except when you want quickly rotating WebViews...) than using android:configChanges="orientation|screenSize".
I have seen the following links before posting this question
http://www.devx.com/wireless/Article/40792/1954
Saving Android Activity state using Save Instance State
http://www.gitshah.com/2011/03/how-to-handle-screen-orientation_28.html
How to save state during orientation change in Android if the state is made of my classes?
I am not getting how should i override the following function :
#Override
public Object onRetainNonConfigurationInstance() {
return someExpensiveObject;
}
In my application i have layout with one editext visible and other editext get visible when the data of first editext validates to true.I have set the visbility of all other editextes and textviews to false and make them visible after validating.
So in my activity if the screen orientation is changed then all the items having android:visibility="false" get invisible.
I have also came to know that when our activities screen orientation changes it calls onStop() followed by onDestroy() and then again starts a fresh activity by calling onCreate()
This is the cause .. But i am not getting how to resolve it ..
Here You can see the screenshots of my application :
in this image all fields are loaded
and in another image when the screen orientation is changed to landscape they are all gone
Any link to tutorial or piece of code will be highly appreciable.
And also my application crashes when a progress dialog is shown up and i try to change screen orientation.How to handle this ??
Thanks
Well if you have the same layout for both screens then there is no need to do so just add below line in your manifest in Activity node
android:configChanges="keyboardHidden|orientation"
for Android 3.2 (API level 13) and newer:
android:configChanges="keyboardHidden|orientation|screenSize"
because the "screen size" also changes when the device switches between portrait and landscape orientation.
From documentation here: http://developer.android.com/guide/topics/manifest/activity-element.html
There is another possibility using which you can keep the state as it is even on Orientation change using the onConfigurationChanged(Configuration newConfig).
Called by the system when the device configuration changes while your activity is running. Note that this will only be called if you have selected configurations you would like to handle with the configChanges attribute in your manifest. If any configuration change occurs that is not selected to be reported by that attribute, then instead of reporting it the system will stop and restart the activity (to have it launched with the new configuration).
At the time that this function has been called, your Resources object will have been updated to return resource values matching the new configuration.
There are 2 ways of doing this, the first one is in the AndroidManifest.xml file. You can add this to your activity's tag. This documentation will give you an in depth explanation, but put simply it uses these values and tells the activity not to restart when one of these values changes.
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout"
And the second one is: overriding onSaveInstanceState and onRestoreInstanceState. This method requires some more effort, but arguably is better. onSaveInstanceState saves the values set (manually by the developer) from the activity before it's killed, and onRestoreInstanceState restores that information after onStart() Refer to the official documentation for a more in depth look. You don't have to implement onRestoreInstanceState, but that would involve sticking that code in onCreate().
In my sample code below, I am saving 2 int values, the current position of the spinner as well as a radio button.
#Override
public void onSaveInstanceState(#NonNull Bundle savedInstanceState) {
spinPosition = options.getSelectedItemPosition();
savedInstanceState.putInt(Constants.KEY, spinPosition);
savedInstanceState.putInt(Constants.KEY_RADIO, radioPosition);
super.onSaveInstanceState(savedInstanceState);
}
// And we restore those values with `getInt`, then we can pass those stored values into the spinner and radio button group, for example, to select the same values that we saved earlier.
#Override
public void onRestoreInstanceState(#NotNull Bundle savedInstanceState) {
spinPosition = savedInstanceState.getInt(Constants.KEY);
radioPosition = savedInstanceState.getInt(Constants.KEY_RADIO);
options.setSelection(spinPosition, true);
type.check(radioPosition);
super.onRestoreInstanceState(savedInstanceState);
}
How do I retain the state of an activity in android? I have two layouts for portrait and landscape in layout and layout-land. I am loading the value from service at the time I am showing progress dialog. If loaded user rotates the device to landscape at the time also loading. How do I avoid that? user typed content in webview that also refreshed. How do I avoid that, can anybody provide an example?
Thanks
When orientation changes, the Activity is reloaded by default. If you do not want this behavior then add this to the Activity definition in your manifest:
android:configChanges="orientation|keyboardHidden"
For more detail, see Handling Runtime Changes
You can use the onRetainNonConfigurationChange() callback to store arbitrary data. It is called just before your application is about to be recreated.
Then, in onCreate() just check if some data were put aside by calling getLastNonConfigurationInstance() that returns the Object you put aside or null.
See this article on android developers.
Here's a sample borrowed from the link above:
#Override
public Object onRetainNonConfigurationInstance() {
//this is called by the framework when needed
//Just return what you want to save here.
return MyBigObjectThatContainsEverythingIWantToSave;
}
Automagic restore of previously saved state:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final MyDataObject MyBigObjectThatContainsEverythingIWantToSave = (MyDataObject) getLastNonConfigurationInstance();
if (MyBigObjectThatContainsEverythingIWantToSave == null) {
//No saved state
MyBigObjectThatContainsEverythingIWantToSave = loadMyData();
} else {
//State was restored, no need to download again.
}
...
}
When orientation changes, the Activity is reloaded by default. If you do not want this behavior then add this to the Activity definition in your android manifest file :
android:configChanges="orientation|screenSize|keyboardHidden"