I provide theme option for my app user. But I found the theme will reset back to default theme after rotation.
Here is my code:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
config = PreferenceManager.getDefaultSharedPreferences(this);
theme = config.getString("theme", "");
setActionBarTheme(theme);
setContentView(R.layout.main);
}
private void setActionBarTheme(String theme)
{
if(theme.equals("Holo Red"))
{
setTheme(R.style.onTime_Theme_Holo_Red);
}else
{
setTheme(R.style.onTime_Theme_Default);
}
}
This activity is hosting fragments, when device is rotated, fragment remain but theme reset.
If device config changed, it should recreate the activity. Is it bug or I miss something?
As #CBergson pointed out, you can save the theme and recreate it whe the Activity is recreated.
On the other hand you can prevent Android from destroying your Activity by adding the following line to your AndroidManifest.xml:
<activity android:name="YourActivity"
android:configChanges="orientation|screenSize" />
Further reading here.
You should save/update the current theme in your preferences, so that It wont be the default value. Activity life-cycle will repeat itself (stop-destroy-start etc...) when the rotation happens. You need to save it before the cycle is completed before the next activity cycle starts.
Related
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
Could someone tell me how i can switch the the theme from holo to holo light in my application on runtime ?
I would like to have two buttons in settings to choose light or black theme.
How can it be set applicationwide and not only for the activity ?
I already tried a few things with setTheme() but i wasn't able to change the theme when i click a button.
This is my Settings activity where i would like to set the theme:
public class SettingsActivity extends Activity {
#SuppressLint("NewApi")
#Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(android.R.style.Theme_Holo_Light);
super.onCreate(savedInstanceState);
setContentView(R.layout.settings);
}
well this works and my Theme is set but as i am saying i would like to change it systemwide by pressing a button.
Thanks !
As you can see theme is setting on the onCreate() and before setContentView(). So you should call the oncreate() method again when you want to change the theme. But the onCreate() function will be called only once in the life cycle of an Activity.
There is a simple way to do this. I am not sure that it is the best way.
Suppose you want to apply new theme to Activity 1 on a button click.
inside the onClick event
Save the theme to be applied such that it should be retained even after the application restart (preferences or static volatile variables can be used).
Finish the current activity (Activity 1) and call a new activity (Activity 2).
Now in Activity 2
Call Activity 1 and finish current activity (Activity 2).
In Activity 1
Apply the saved theme inside onCreate.
Hope it is not confusing.. :)
You cannot change the theme of other applications (thank goodness).
The only way to somewhat accomplish this would be to create your own build of the operating system with your own theme as the device default theme. However, applications that do not use the device default theme (i.e. they explicitly set the theme to Holo, Holo light, etc) will not get the device default theme.
Edit- To accomplish this application-wide using a base Activity, create an Activity that looks like this:
public class BaseActivity extends Activity {
private static final int DEFAULT_THEME_ID = R.id.my_default_theme;
#Override
public void onCreate(Bundle savedInstanceState) {
int themeId = PreferenceManager.getDefaultSharedPreferences(context)
.getInt("themeId", DEFAULT_THEME_ID);
setTheme(themeId);
super.onCreate(savedInstanceState);
}
}
Then all of your Activities should extend this BaseActivity. When you want to change the theme, be sure to save it to your SharedPreferences.
this code runs after startActivity, but setting it after:
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
keep old (default) android animation. Why?
Because by the time your activity starts and has a chance to overridePendingTransition, the transition has already taken place. You should change the window transitions in a Theme, and set the Activity theme at the manifest.
I have a simple activity that loads a bitmap in onCreate. I find that if I rotate the device I can see from the logs that onCreate called again. In fact, because all instance variables are set to default values again I know that the entire Activity has been re-instantiated.
After rotating 2 times I get an FC because not enough memory can be allocated for the bitmap. (Are all instances of the activty still alive somewhere? Or does the GC not clean up fast enough?)
#Override
public void onCreate(Bundle savedInstanceState) {
File externalStorageDir = Environment.getExternalStorageDirectory();
File picturesDir = new File(externalStorageDir, "DCIM/Camera");
File[] files = picturesDir.listFiles(new FilenameFilter(){
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".jpg");
}});
if (files.length > 0) {
Bitmap bm = BitmapFactory.decodeStream(new FileInputStream(files[0]));
ImageView view = (ImageView) findViewById(R.id.photo);
view.setImageBitmap(bm);
}
}
From all that I read, onCreate should be called once during the lifetime of an application. Am I wrong about this? How can re-orienting the device cause the activity to be recreated?
android:configChanges="keyboardHidden|orientation|screenSize"
Caution: Beginning with Android 3.2 (API level 13), the "screen size"
also changes when the device switches between portrait and landscape
orientation. Thus, if you want to prevent runtime restarts due to
orientation change when developing for API level 13 or higher (as
declared by the minSdkVersion and targetSdkVersion attributes), you
must include the "screenSize" value in addition to the "orientation"
value. That is, you must decalare
android:configChanges="orientation|screenSize". However, if your
application targets API level 12 or lower, then your activity always
handles this configuration change itself (this configuration change
does not restart your activity, even when running on an Android 3.2 or
higher device).
From docs: http://developer.android.com/guide/topics/resources/runtime-changes.html
What happen when orientation changed
Life Cycle of orientation
onPause();
onSaveInstanceState();
onStop();
onDestroy();
onCreate();
onStart();
onResume();
---- app recreated and now is running ---
If you do long operation in onCreate() and want prevent re-create your activity add configChanges attribute in your mainfest
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="#string/app_name">
screenSize if you targeting api >= 13
Activity is recreated after each rotation by default. You can override this behaviour with configChanges attribute of the activity tag in AndroidManifest. For further details and different options, see http://developer.android.com/guide/topics/resources/runtime-changes.html
Actvity Lifecycle when you rotate screen
onPause
onSaveInstanceState
onStop
onDestroy
onCreate
onStart
onRestoreInstanceState
onResume
If you want to prevent FC from not enough memory, you need to deallocate resources in onStop() or onPause(). this allows you to use fresh memory in onCreate().
This is an alternate solution to preventing the recreation of the activity by using
android:configChanges="keyboardHidden|orientation"
As sometimes your activity's layout is different in portrait and landscape (layout, layout-land).
preventing recreate on orientation change will prevent your activity from using the other orientation's layout.
Yes, activity's onCreate() is called everytime when the orientation changes but you can avoid the re-creation of Activity by adding configChanges attribute of Activity in your AndroidManifest file in the activity tag.
android:configChanges="keyboardHidden|orientation"
On Create method will call everytime when you do orientation, to avoid this you have to use
//Define Below in you Manifest file.
<activity
android:name="com.ecordia.activities.evidence.MediaAttachmentView"
android:configChanges="keyboardHidden|orientation|screenSize"
</activity>
//Define Below in your activity.
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
//your code
} else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
//your code
}
}
It will works like a charm!!
Manifest XML activity Tag:
android:configChanges="keyboardHidden|orientation"
#Override
public void onConfigurationChanged(Configuration newConfig) {
// TODO Auto-generated method stub
super.onConfigurationChanged(newConfig);
}
Use the above code to perform changes related to orientation in your Activity Java Code
Cheers!!!
One of the most common and suggested “solutions” to dealing with orientation changes is to not deal with them. You can do this by setting the android:configChanges flag on your Activity in AndroidManifest.xml as shown below:
<activity
android:name=".MyActivity"
android:label="#string/title_my_activity"
android:configChanges="orientation|screenSize|keyboardHidden" />
This is NOT the correct way to deal with orientation changes.
CORRECT way is to implement the onSaveInstanceState method (this could be in your Activity, Fragment or both) and place the values you need to save in the Bundle argument that gets passed to the method.
It is nicely described here: http://code.hootsuite.com/orientation-changes-on-android/
While it may seem a bit tedious to implement, handling orientation changes properly provides you with several benefits: you will be able to easily use alternate layouts in portrait and landscape orientations, and you will be able to handle many exceptional states such as low memory situations and interruptions from incoming phone calls without any extra code.
While the Manifest way may work, there is a better and proper solution for these types of problems. The ViewModel class. You should have a look here: https://developer.android.com/topic/libraries/architecture/viewmodel
Basically, you extend the ViewModel class and define all the data members in it which we want to be unchanged over re creation of the activity (in this case orientation change). And provide relevant methods to access those from the Activity class. So when the Activity is re created, the ViewModel object is still there, and so are our data!
Kindly see my way of doing it:-
http://animeshrivastava.blogspot.in/2017/08/activity-lifecycle-oncreate-beating_3.html
snippet is:-
#Override
protected void onSaveInstanceState(Bundle b) {
super.onSaveInstanceState(b);
String str="Screen Change="+String.valueOf(screenChange)+"....";
Toast.makeText(ctx,str+"You are changing orientation...",Toast.LENGTH_SHORT).show();
screenChange=true;
}
#Override
public void onCreate(Bundle b) {
super.onCreate(b);
ctx=getApplicationContext();
if(!screenChange) {
String str="Screen Change="+String.valueOf(screenChange);
// ...
}
}
I had the same problem, in which my onCreate is called multiple times when the screen orientation is changed. My problem got solved when i add android:configChanges="orientation|keyboardHidden|screenSize" in the activity tag in manifest
I had the same problem and I did some workaround
Define didLoad boolean variable with false value
private boolean didLoad = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
if (!this.didLoad){
// Your code...
this.didLoad = true;
}
I simply need nothing to change when the screen is rotated. My app displays a random image when it first loads and rotating the device should not select another random image.
How can I (simply) make this behavior stop?
There are generally three ways to do this:
As some of the answers suggested, you could distinguish the cases of your activity being created for the first time and being restored from savedInstanceState. This is done by overriding onSaveInstanceState and checking the parameter of onCreate.
You could lock the activity in one orientation by adding android:screenOrientation="portrait" (or "landscape") to <activity> in your manifest.
You could tell the system that you meant to handle screen changes for yourself by specifying android:configChanges="orientation|screenSize" in the <activity> tag. This way the activity will not be recreated, but will receive a callback instead (which you can ignore as it's not useful for you).
Personally I'd go with (3). Of course if locking the app to one of the orientations is fine with you, you can also go with (2).
Xion's answer was close, but #3 (android:configChanes="orientation") won't work unless the application has an API level of 12 or lower.
In API level 13 or above, the screen size changes when the orientation changes, so this still causes the activity to be destroyed and started when orientation changes.
Simply add the "screenSize" attribute like I did below:
<activity
android:name=".YourActivityName"
android:configChanges="orientation|screenSize">
</activity>
Now, when you change orientation (and screen size changes), the activity keeps its state and onConfigurationChanged() is called. This will keep whatever is on the screen (ie: webpage in a Webview) when the orientation changes.
Learned this from this site:
http://developer.android.com/guide/topics/manifest/activity-element.html
Also, this is apparently a bad practice so read the link below about Handling Runtime Changes:
http://developer.android.com/guide/topics/resources/runtime-changes.html
You just have to go to the AndroidManifest.xml and inside or in your activities labels, you have to type this line of code as someone up there said:
android:configChanges="orientation|screenSize"
So, you'll have something like this:
<activity android:name="ActivityMenu"
android:configChanges="orientation|screenSize">
</activity>
Hope it works!
<activity android:name="com.example.abc"
android:configChanges="orientation|screenSize"></activity>
Just add android:configChanges="orientation|screenSize" in activity tab of manifest file.
So, Activity won't restart when orientation change.
It's my experience that it's actually better to just deal with the orientation changes properly instead of trying to shoehorn a non-default behavior.
You should save the image that's currently being displayed in onSaveInstanceState() and restore it properly when your application runs through onCreate() again.
This solution is by far the best working one. In your manifest file add
<activity
android:configChanges="keyboardHidden|orientation|screenSize"
android:name="your activity name"
android:label="#string/app_name"
android:screenOrientation="landscape">
</activity
And in your activity class add the following code
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
//your code
} else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
//your code
}
}
In manifiest file add to each activity this. This will help
android:configChanges = "orientation|keyboard|keyboardHidden|screenLayout|screenSize"
add android:configChanges="keyboardHidden|orientation|screenSize" for all the app activities tags in manifest.
Just add this to your AndroidManifest.xml
<activity android:screenOrientation="landscape">
I mean, there is an activity tag, add this as another parameter. In case if you need portrait orientation, change landscape to portrait. Hope this helps.
just use : android:configChanges="keyboardHidden|orientation"
As Pacerier mentioned,
android:configChanges="orientation|screenSize"
All above answers are not working for me. So, i have fixed by mentioning the label with screenOrientation like below. Now everything fine
<activity android:name=".activity.VideoWebViewActivity"
android:label="#string/app_name"
android:configChanges="orientation|screenSize"/>
http://animeshrivastava.blogspot.in/2017/08/activity-lifecycle-oncreate-beating_3.html
#Override
protected void onSaveInstanceState(Bundle b)
{
super.onSaveInstanceState(b);
String str="Screen Change="+String.valueOf(screenChange)+"....";
Toast.makeText(ctx,str+"You are changing orientation...",Toast.LENGTH_SHORT).show();
screenChange=true;
}
Prevent Activity to recreated
Most common solution to dealing with orientation changes by setting the android:configChanges flag on your Activity in AndroidManifest.xml. Using this attribute your Activities won’t be recreated and all your views and data will still be there after orientation change.
<activity
android:name="com.example.test.activity.MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden"/>
this is work for me😊😊😊
Save the image details in your onPause() or onStop() and use it in the onCreate(Bundle savedInstanceState) to restore the image.
EDIT:
More info on the actual process is detailed here http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle as it is different in Honeycomb than previous Android versions.
I dont know if the a best solution, but i describe it here:
First of all, you need certificate with you class Application of your app is in your manifest of this:
<application
android:name=".App"
...
Second, in my class App i did like this:
public class App extends Application {
public static boolean isOrientationChanged = false;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onConfigurationChanged(#NotNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ||
newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
isOrientationChanged = true;
}
}
}
Third, you need to set a flag to Orientation Change, in my case, I always set it when the previous activity within the app navigation is called, so only calling once when the later activity is created.
isOrientationChanged = false;
So every time I change the orientation of my screen in that context, I set it every time it changes this setting, it checks if there is a change in orientation, if so, it validates it based on the value of that flag.
Basically, I had to use it whenever I made an asynchronous retrofit request, which he called every moment that changed orientation, constantly crashing the application:
if (!isOrientationChanged) {
presenter.retrieveAddress(this, idClient, TYPE_ADDRESS);
}
I don't know if it's the most elegant and beautiful solution, but at least here it's functional :)
Add this code after the onCreate ,method in your activity containing the WebView
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
}