I have an Activity that displays various fragments using the supportFragmentManager. When I attempt to get a view in the fragment or the parent activity for that matter, and attempt to measure it's position on the screen it only seems to be available for measurement sometime after onResume in the fragment lifecycle or after onActivityCreated/onResume/onAttachedToWindow in the Activity. Typically it is available after about 100-200ms. Is there any lifecycle event documented/undocumented or solid method of knowing when this has occurred, like maybe a canvas drawing event. The fragment in question needs to measure a parent activity view, but it isn't always available in onResume right away. I really hate having to do some kind of hack like having a handler wait 200ms.
You can use ViewTreeObserver.addOnGlobalLayoutListener().
#Override
public void onCreate(Bundle savedInstanceState) {
//...
someView.getViewTreeObserver().addOnGlobalLayoutListener(getOnLayoutListener(someView));
//...
}
private ViewTreeObserver.OnGlobalLayoutListener getOnLayoutListener(final View unHookView) {
return new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
unHookView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
else
unHookView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//YOUR CODE HERE
}
};
}
I have written android app now for a long time but now I'm facing a problem that I have never thought about. It is about the android lifecycle of Activitys and Fragments in in relation to configuration changes. For this I have create a small application with this necessary code:
public class MainActivity extends FragmentActivity {
private final String TAG = "TestFragment";
private TestFragment fragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
fragment = (TestFragment) fm.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new TestFragment();
fm.beginTransaction().add(R.id.fragment_container, fragment, TAG).commit();
}
}
}
And here is my code for the TestFragment. Note that I'm calling setRetainInstance(true); in the onCreate method so the fragment is not recrated after a configuration change.
public class TestFragment extends Fragment implements View.OnClickListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater li, ViewGroup parent, Bundle bundle) {
View rootView = li.inflate(R.layout.fragment_test, parent, false);
Button button = (Button) rootView.findViewById(R.id.toggleButton);
button.setOnClickListener(this);
return rootView;
}
#Override
public void onClick(View v) {
Button button = (Button) v;
String enable = getString(R.string.enable);
if(button.getText().toString().equals(enable)) {
button.setText(getString(R.string.disable));
} else {
button.setText(enable);
}
}
}
And here is the layout that my fragment is using:
<LinearLayout
...>
<EditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="#+id/toggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/enable"/>
</LinearLayout>
My problem is that if I rotate the device the text of the Button change back to the default value. Of course the View of the Fragment is new created and inflated but the saved instance for the Views should be restored. I also have an EditText in my layout and there the text and other properties remains after the rotation. So why is the Button not restore from the Bundle by default? I have read on the developer site:
By default, the system uses the Bundle instance state to save information about each View object in your activity layout (such as the text value entered into an EditText object). So, if your activity instance is destroyed and recreated, the state of the layout is restored to its previous state with no code required by you.
I've also read a lot of answers the last days but I do not know how actual they are anymore. Please do not leave a comment or an answer with android:configChanges=... this is very bad practice. I hope someone can bring light into my lack of understanding.
You should save state of your fragment in the onSaveInstanceState(Bundle outState) and restore it in the onViewCreated(View view, Bundle savedState) method. This way you will end up with the UI just as it was before configuration change.
TextView subclasses don't save their text by default. You need to enable freezesText="true" in the layout, or setFreezesText(true) at runtime for it to save its state.
As per documentation, views should maintain their state without using setRetainInstance(true). Try to remove it from your onCreate, this should force the fragment to be recreated on screen rotation, hence all of it's views should be saved before rotation and restored after.
This stack overflow should answer your question:
setRetainInstance not retaining the instance
setRetainInstance does tell the fragment to save all of its data, and for ui elements where the user has manipulated the state (EditText, ScrollView, ListView, etc) the state gets restored. That being said, normal read-only UI components get reinflated from scratch in onCreateView and have to be set again - my guess would be that their properties are not considered "data" that needs to be retained and restored - Google probably does this for performance reasons. So things like a normal Button, ImageView, or TextView need their contents set manually when they are reinflated if it differs from the initial state in the XML. (TextView's android:freezesText basically puts the TextView in a mode that uses an implementation to save it's state and restore it).
PS: According to this stack overflow Save and restore a ButtonText when screen orientation is switched, you may be able to set android:freezesText on the button to have it keep the text you set - I haven't tried it, but it makes sense.
Edit after Op feedback
Although this fails to answer the question, after consultation with the OP, I've decided to leave it here as a reference point of information on the topic for people who land here. Hope you find it helpful.
Try putting your setRetainInstance in onCreateView. See here
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater li, ViewGroup parent, Bundle bundle) {
setRetainInstance(true);
View rootView = li.inflate(R.layout.fragment_test, parent, false);
Button button = (Button) rootView.findViewById(R.id.toggleButton);
button.setOnClickListener(this);
return rootView;
}
Called when the fragment's activity has been created and this
fragment's view hierarchy instantiated. It can be used to do final
initialization once these pieces are in place, such as retrieving
views or restoring state. It is also useful for fragments that use
setRetainInstance(boolean) to retain their instance, as this
callback tells the fragment when it is fully associated with the new
activity instance. This is called after onCreateView(LayoutInflater,
ViewGroup, Bundle) and before onViewStateRestored(Bundle).
developer.android.com/reference/android/app/Fragment.html#onActivityCreated
Control whether a fragment instance is retained across Activity
re-creation (such as from a configuration change). This can only be
used with fragments not in the back stack. If set, the fragment
lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because
the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being
re-created.
onAttach(Activity) and onActivityCreated(Bundle) will
still be called.
developer.android.com/reference/android/app/Fragment.html#setRetainInstance
And taken from here:
onCreate : It is called on initial creation of the fragment. You do
your non graphical initializations here. It finishes even before the
layout is inflated and the fragment is visible.
onCreateView : It is called to inflate the layout of the fragment i.e
graphical initialization usually takes place here. It is always called
sometimes after the onCreate method.
onActivityCreated : If your view is static, then moving any code to
the onActivityCreated method is not necessary. But when you - for
instance, fill some lists from the adapter, then you should do it in
the onActivityCreated method as well as restoring the view state when
setRetainInstance used to do so. Also accessing the view hierarchy of
the parent activity must be done in the onActivityCreated, not sooner.
Let me know if this helps.
I have a activity with multiples initializations ( fragments , sharedpreferences , services , ui components(search bar , buttons etc..)...) and i do not want to restart the activity when the screen orientation change
I found a easy ( and working solution ) by using android:configChanges="keyboardHidden|orientation|screenSize" but my technical manager forbade me this way because according to him it's a bad practice
What are the other ways in order to handle properly the screen orientation changes?
Thank you very much
My main activity
#AfterViews
public void afterViews() {
putSharedPrefs();
initializeFragments();
initializeComponents();
initializeSynchronizeDialog();
initDownloadDialog();
}
You can just add layout for landscape mode.
You just need to add folder layout-land in your "res" directory and define layout for what your activity looks like if it is in landscape mode.
NOTE-Keep xml file name same as the name which is in simple layout folder and which you are using in your activity. It will automatically detect your screen orientation and apply layout accordingly.
EDIT1
Now to save your text from the text view =) Lets assume your textview is named as MyTextView in your layout xml file. Your activity will need the following:
private TextView mTextView;
private static final String KEY_TEXT_VALUE = "textValue";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = (TextView) findViewById(R.id.main);
if (savedInstanceState != null) {
String savedText = savedInstanceState.getString(KEY_TEXT_VALUE);
mTextView.setText(savedText);
}
#Override
protected void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_TEXT_VALUE, mTextView.getText());
}
Basically, whenever Android destroys and recreates your Activity for orientation change, it calls onSaveInstanceState() before destroying and calls onCreate() after recreating. Whatever you save in the bundle in onSaveInstanceState, you can get back from the onCreate() parameter.
So you want to save the value of the text view in the onSaveInstanceState(), and read it and populate your textview in the onCreate(). If the activity is being created for the first time (not due to rotation change), the savedInstanceState will be null in onCreate(). You also probably don't need the android:freezesText="true"
You can also try saving other variables if you need to, since you'll lose all the variables you stored when the activity is destroyed and recreated.
I'm making an app that change the colors depending of the color you select this change the background, but when I make the screen orientation to landscape this automatically change the color to the predefined and if I'm not wrong this happen because it's being destroy after I change the orientation... so I would like to know where and how I can solve that problem.
Android gives you a chance to save state before changing the layout
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mColor = savedInstanceState.getString(COLOR_VALUE);
}
#Override //this method is called before android trashes and recreates your activity
protected void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(COLOR_VALUE, mColor);
}
If your UI is expensive to recreate then look into retained fragments instead
I'm having problems making my Activity respond to a theme change (i.e. Theme_Dark change to Theme_Light).
In the Activity this code below works fine, and the theme is changed when the Activity is created (method getPreferenceTheme() just gets the theme preference value that was set via a PreferenceActivity).
protected void onCreate(Bundle savedInstanceState) {
setTheme(getPreferenceTheme());
super.onCreate(savedInstanceState);
setContentView(R.layout.controls);
}
But how can I dynamically change the theme ? So after I change the theme in a PreferenceActivity and return to the main Activity how can I get it to change?
I know that I can re-start the Activity to do this (calling onCreate() again), but I didn't want to do this and have heard that it is possible to "re-inflate the view hierarchy" in onResume() - how do I do this ?
I tried the following (a stab in the dark) but with no joy.
protected void onResume() {
super.onResume();
LayoutInflater inflater = LayoutInflater.from (this);
View v = inflater.inflate (R.layout.controls, null);
setTheme(getPreferenceTheme());
setContentView(v);
}
Any help much appreciated,
M.
Try the "recreate" Activity method.
http://developer.android.com/reference/android/app/Activity.html#recreate()
Call it after you have set the new theme.