Does anyone know the rationale behind the design of Android to destroy and re-create activities on a simple orientation change?
Shouldn't allowing the activity to just redraw itself (if it so chooses) be a better, simpler and more practical design?
BTW, I'm well aware of how to disable my app from orientation change effects, but what I don't really get is the reason for this design in Android
In docs,
http://developer.android.com/guide/topics/resources/runtime-changes.html
it states that,
The restart behavior is designed to help your application adapt to new configurations by automatically reloading your application with alternative resources.
Dont know exactly why, my guess is because it has to reconstruct the activity on the screen.
OnCreate takes in a variable "Bundle savedInstanceState". You can save the data in there for faster reloading.
package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class HelloAndroid extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
if (savedInstanceState == null) {
mTextView.setText("Welcome to HelloAndroid!");
} else {
mTextView.setText("Welcome back.");
}
setContentView(mTextView);
}
private TextView mTextView = null;
}
This is pretty simple. Android destroys and recreates an activity on orientation changes so that you (the developer) have the option to keep on using the previous layout or use and implement another one.
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
I'm looking over some code on the android developer's site and have a quick question about the example show here - http://developer.android.com/guide/components/fragments.html
In particular, I'm looking at this piece of code -
public static class DetailsActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE) {
// If the screen is now in landscape mode, we can show the
// dialog in-line with the list so we don't need this activity.
finish();
return;
}
if (savedInstanceState == null) {
// During initial setup, plug in the details fragment.
DetailsFragment details = new DetailsFragment();
details.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
}
}
What is the point of the second if statement -
if (savedInstanceState == null) {
I can't find any situation where this if statement wouldn't be true. I've tested this code by adding an else statement and setting a breakpoint in it. I could not get to that breakpoint no matter I tried. So why even bother with an if statement? Why not leave it out all together?
There are situations in which your Activity is stopped by the Android operating system. In those cases, you get a chance to save the state of your Activity by a call to [onSaveInstanceState](http://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState(android.os.Bundle). If after this, your Activity is started again, it'll be passed the Bundle you created so that you can restore the state properly.
You have to look at the complete example code. With this part it makes sense.
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
If you start your Activity the first time the Bundle savedInstanceState will be null and the body of the if statement will be executed. If onSaveInstanceState is called, because you navigated away from the Activity, the Bundle isn't null anymore and the if body will be not executed.
If your app was paused/killed, etc and you saved state by onSaveInstanceState then
savedInstanceState will contain the state of your app that you saved. Otherwise it will be null.
Apparently this was added to the example for future expansion on this code. While it has absolutely no functionality as it stands right now, if this activity were to launch another activity and get killed while the new activity had focus, this code would rebuild the activity when the user hits the back button, rather than rebuilding from scratch.
I have an odd issue which I've never seen anywhere on SO so I've resorted to posting here, hoping I make it clear enough.
I have a simple SherlockFragmentActivity as shown further down which contains three fragments which all call getActivity().setTitle() in their onCreateOptionsMenu() allowing my app to change titles depending on which fragment is visible.
This works as desired, but for some reason (perhaps unrelated) when I exit my application by means of the HOME button occasionally the title isn't visible upon reopening the application. It seems that should I close my app and reopen it, it's fine but after leaving it for a while the title won't be there when I reopen it.
I have absolutely no idea what could be causing this so any help is appreciated. The layout of my application (relevant to this issue) is a basic splash screen (as an activity) with a loading bar which then opens the following FragmentActivity:
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import static java.lang.Math.*;
public class FragmentControl extends SherlockFragmentActivity {
private static final int NUM_PAGES = 3;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_control);
ActionBar action = getSupportActionBar();
action.setDisplayShowTitleEnabled(true);
action.setDisplayShowHomeEnabled(false);
mPager = (ViewPager)findViewById(R.id.pager);
mPagerAdapter = new FragmentControlAdapter(getSupportFragmentManager(), NUM_PAGES);
mPager.setAdapter(mPagerAdapter);
// If this activity wasn't called after a reload
if((Integer)getIntent().getExtras().get("current") == null){
// Always start on the middle page, or as close as possible
mPager.setCurrentItem((int) ceil(NUM_PAGES/2));
// Otherwise start on the page we left for a smoother experience
} else {
mPager.setCurrentItem((Integer)getIntent().getExtras().get("current"));
}
}
}
Only when the application re-opens to the FragmentActivity do I see this issue, when reopening on anything else and navigating to this activity it's fine (as you'd expect).
Any and all help is appreciated, hope I've made it clear.
Oh, and if it matters I'm currently targeting API 17 with minimum support for API 8. The test phone I'm seeing this issue on is a HTC One S - not sure on other devices yet but I'm going to start looking.
occasionally (...) after leaving it for a while
This sounds like your application process is killed in the meantime.
Make sure to save instance state (like what title is displayed) using onSaveInstanceState and restore it in Activity.onCreate or Fragment.onViewCreated.
I found this on android developers when i was trying to program a button:android developers blog: UI framework changes in Android 1.6
With Android 1.6, none of this is necessary. All you have to do is declare a public method in your Activity to handle the click (the method must have one View argument):
class MyActivity extends Activity { public void myClickHandler(View target){
// Do stuff
}
}
And then reference this method from your XML layout:
<Button android:onClick="myClickHandler" />
can smeone please explain this code to me? I am a beginner in programming, and I don't know what to put in the //do stuff space? I need to reference another activity so i can open another screen. And do i still need to have an activity and put a block of program in the class? this is the code i am using in the class at the moment. Please tell me if i need to update it to use this method:
package com.duncan.hello.world;
import com.duncan.hello.world.R;
import android.app.Activity;
import android.os.Bundle;
public class OtherActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.other);
}
}
You put what you want to happen when the button is clicked in the // do stuff part.
You only need to update your code if you're using a button click handler in this fashion.
You might want to start with something a bit simpler, and perhaps not target Android 1.6.
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"