Android actionbar refresh when rotating and multi-pane - android

I have an activity the same as the following image:
FragmentA is a listview and has a SearchWidget as menu-item (which is not displayed on old devices, only API11 and above).
FragmentB is a detail view and has several menu-items.
When ActivityA runs on a tablet, the menu-items of FragmentA + FragmentB are visible in the actionbar. This is correct and works perfect.
Now on a Nexus 7 I want a mix of those:
In portrait only use the handset layout
When I rotate the device, the tablet layout is loaded
The only thing which I can't seem to get working is the actionbar. When I rotate the device from landscape mode (tablet view) back to portrait (handset view), still the actionbar shows the menu-items of FragmentA + FragmentB.
I've tried calling the invalidateOptionsMenu() from onResume() in both ActivityA as FragmentA, but without luck.
Does anyone has an idea?

I think this is due activity re-creation process.
When screen is rotated your activity is destroyed (by default).
But before it is destroyed it saves state including states of all currently active fragments.
Later, when activity is creating after orientation change it restores saved state (with both fragments). As details fragment restores it appends menu items.
You can check this by adding log statements or using debugger in onCreateView of DetailsFragment.
If it's your case then you have next solutions:
Suppress saving state (must be avoided if DetailsFragment should keep track of last displayed item or something similar) by removing this fragment before activity save its state.
Suppress any initialization of DetailsFragment if it will not be displayed. Activity should give answer about visibility.
Your variant? I think two approaches above isn't good enough...
Sorry if my answer didn't help you at all

This works for me.
In Activity A try find Fragment B and set setHasOptionsMenu(mTwoPane)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_document_list);
Fragment fragment = getFragmentManager().findFragmentByTag(TAG_FRAGMENT_DETAIL);
if (getResources().getBoolean(R.bool.has_detail)){
mTwoPane = true;
....
}
if (fragment != null) {
fragment.setHasOptionsMenu(mTwoPane);
invalidateOptionsMenu();
}
}

Related

Detail fragment re-starts even when rotated from landscape to vertical. (Master-Detail Flow)

In an Activity, lets call it MasterActivity, I want to load the detail fragment with media playback only in landscape mode. The media automatically starts when ready.
Master-Detail Flow setup: I have two xmls, activity_master and activity_master.xml(land). The container view with id "detail_container" is only in the landscape xml. The purpose of the landscape xml with a detail container is to show master-detail both on the same screen, on width >900.
In onCreate(), this is how I'm determining the screen orientation though checking the existence of the "detail_container", like so:
if (findViewById(R.id.detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-w900dp).
// If this view is present, then the
mTwoPane = true;
}
and also in OnCreate() of the activity, I have the following code to automatically load the fragment when activity is in two pane mode (landscape on large-screen layouts)
if (mTwoPane) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_container,
someFragment.newInstance(MEDIA_URI))
.commit();
}
Problem:
When I start the activity in vertical, the fragment isn't loaded (expected).
When I start the activity in landscape, the fragment is loaded (expected).
On screen rotation, fragment is destroyed (expected, playback stops and resources released only in onDestory());
The problem is when I start activity in landscape but rotate it to vertical, the fragment restarts, and media playback start again (unexpected).
My Goal: I want the fragment to automatically load when the device is in landscape on large screen devices, on device rotation the fragment shouldn't load again.
EDIT: In vertical mode, the fragment shouldn't load automatically, user would click in master activity, opens the detail activity, and the detail activity would host the detail fragment.
How should I go about this? Thank You in Advance
Extra Info: Test physical device is a 7' tablet running Android 5.0 API 21, the issue is also present in emulator.
When a config change occurs, Android will make sure all fragments that are attached to an activity are recreated and reattached back to the new activity's FragmentManager ; this is separate of whether or not the fragments took setRetainInstance(...) into consideration.
So it looks like what you want to do is go against the automatic reattaching of fragments. I was curious about this too so I took a little deep dive into FragmentActivity.onCreate(...) and FragmentManager but I couldn't find anything exposed that allows developers to disallow this automatic process. You can, however, work around the process with a FragmentTransaction by performing the following in your code:
FragmentManager manager = getSupportFragmentManager();
if (mTwoPane) {
// set up your two pane
manager.beginTransaction()
.replace(R.id.detail_container,
someFragment.newInstance(MEDIA_URI),
someFragment.TAG)
.commit();
} else {
// this is not two pane, so remove the fragment if it is attached
Fragment detail = manager.findFragmentByTag(someFragment.TAG);
if (detail != null) {
manager.beginTransaction()
.remove(detail)
.commit();
}
}
I don't know how your layout XML looks like but I suggest to define the Fragment in your "landscape" layout file only by creating a new directory called /res/layout-land if it not exists.
Layout may look similar to this:
<SomeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<fragment android:name="com.example.YourPlaybackFragment"
android:id="#id/playback_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
...
</SomeLayout>
Here's a useful link on the official Android doc.
Then the Fragment is only inflated in landscape mode and Android takes care of that. Same procedure can be done for bigger screens. That frees your MasterActivity from layout logic.
Your playback logic goes into the Fragment. If the Activity, for whatever reason, needs access to the Fragment then you pull it in with
YourPlaybackFragment fragment = (YourPlaybackFragment) getFragmentManager().findFragmentById(R.id.playback_fragment);
Hope that gives you the right direction.
Edit:
In that case your best bet is to add the Fragment programmatically in the onCreate() method similar to this.
if (findViewById(R.id.detail_container) != null) {
// detect the landscape
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
if(display.getRotation() == Surface.ROTATION_90) {
// add the fragment
getSupportFragmentManager().beginTransaction().add/replace(...)
}
}
Disclaimer: not tested.
Since you have two different activities, the fragment no longer exists in the same context so that might be a little difficult to produce the behavior you're looking for. Try to do the following:
Portrait : xml layout with only one container for the MasterFragment. Swap this container with the DetailFragment when selected.
Landscape : xml layout with two containers, one for MasterFragment and one for DetailFragment. If DetailFragment already exists (user was viewing it in portrait before the orientation change), the fragment manager knows about it. Find the existing fragment and attach it to the appropriate container.
Assuming you're letting your activity be recreated on orientation changes, if you're using setRetainInstance(true), the fragment manager can hold onto your existing fragment and should re-attach the same one automatically on orientation change. If you want it to reattach, you should check to make sure the fragment doesn't exist yet before calling replace(...). If you want to attach it somewhere else, you can grab the existing one and put it in a different container.
if (mTwoPane) {
if (getSupportFragmentManager().findFragmentByTag(someFragment.TAG) != null) {
// fragment already exists so either do nothing (auto reattach to R.id.detail_container)
// or manually attach it to a different container
return;
}
fragment = someFragment.newInstance(MEDIA_URI);
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_container,
someFragment.newInstance(MEDIA_URI),
someFragment.TAG)
.commit();
}

How to avoid multiple instances of fragments in Activity after app is killed and resumed?

I have an app with a Home screen that has 2 fragments (for now) and a navigation drawer. Currently I load the fragment A (Explore) on startup and load fragment B when clicked. From then on, I show and hide fragments. It's faster than recreating fragments on every click and my fragment A takes some time to load.
I've noticed that when I go to fragment B and go to another activity (let's call it activity 2) from there and leave the app and wait for it to be killed (or do something crazy like change the device language), and then come back to the same activity, it's still there. When I press back to go back to fragment B, sometimes (50% of times) the fragment B is drawn over fragment A. On clicking fragment A in the drawer, fragment A appears fine, but on clicking fragment B, there's another instance of fragment A and on top of that fragment B.
I've spent more than 2 days on this problem and got nowhere.
Here's my code for selecting the fragment:
private void selectItem(int position, boolean addExploreFragment) {
Log.d(tag, "selectItem: " + position);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
//add explore fragment - this is called on app startup, but also when the app is killed and resumed which results in 2 explore fragments
if (addExploreFragment){
fragmentTransaction.replace(R.id.content_frame, mExploreFragment, EXPLORE_FRAGMENT_TAG);
Log.d(tag, "Replaced frame and added "+ mFragmentTags[position]);
} else {
//add fragment for the first time
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[position]) == null && position != 0) {
fragmentTransaction.add(R.id.content_frame, mFragments[position], mFragmentTags[position]);
Log.d(tag, "Added Fragment: "+ mFragmentTags[position]);
}
//shows and hides fragments
for (int i = 0; i < mFragments.length; i++) {
if (i == position) {
fragmentTransaction.show(mFragments[i]);
Log.d(tag, "Showing Fragment: "+ mFragmentTags[i]);
} else {
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[i]) != null) {
fragmentTransaction.hide(mFragments[i]);
Log.d(tag, "Hid Fragment: "+ mFragmentTags[i]);
}
}
}
}
fragmentTransaction.commit();
//not null check for calling selectItem(0) before loading the drawer
if (mDrawerList != null){
mDrawerList.setItemChecked(position, true);
}
}
I know for sure, the explore fragment is getting created twice and the two instances behave independently of each other (just sharing).
I'm lost what to do next. This is an issue which can be reproduced very easily on low end devices but on a device like Nexus 4 (my test device), the issue can be reproduced by changing the device language.
Has anyone got any ideas about this? Basically if the addExploreFragment block doesn't get called when there is already an exploreFragment, this issue could be solved, I think, but I've been unable to do so. Also, I tried removing all the fragments and then adding the exploreFragment but same thing happens (50% of times).
Thanks! and sorry for the long post, I felt I should share all the details.
Update: When I change the device language and come back to the app on Activity 2 and go back to Home activity, it has the fragment B open which is good, but fragment A get recreated because it's a heavy fragment and the system probably removed it from memory. Again, that's ok that it gets recreated IF it got removed by the system but why does it get recreated when it's not removed. I believe it's something with my code, on every 2nd attempt (without closing the app) this happens, 2 instances of the heavy fragment A. Out of ideas.
But shouldn't fragmentTransaction.replace remove all the previously added fragments and then add exploreFragment. It's not working like that. Neither fragment A nor Fragment B are getting removed.
I found out something new and rather odd to me. When you use fragmentTransaction.add, the listeners you have, like DrawerItemClickListener, on the previous fragment, are still active. And this is even if you use fragmentTransaction.commit.
So...I suspect when the add method is used, you actually clicked on another hidden button or hidden UI that has an event listener on the previous fragment. I don't like this of course and the effect may be very confusing. Yes, this happened to me and I didn't understand why for a while.
For now, I think the easiest code fix would be to use the replace method instead of add. The replace() makes listeners inactive. If it works, then you can make a better/elegant fix.
Let me know what happens....
I started to notice your post
when I go to fragment B and go to another activity
When you interact or start another Activity, you start a new set of Fragments. Look at this Google webpage # Fragments Lifecycle.
For clarification of my claim, there is a quote saying
A fragment must always be embedded in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle.
You might as well read few paragraphs of it, at least.
I am not sure what your solution should be. Perhaps make the fragments distinctive, different and clear between the two Activities you have.

Single Activity with Multiple Fragments and Screen Orientation

I'm currently dealing with an issue with Android & It's Re-Creation Cycle on screen rotation:
I have one single Activity and lots of Fragments (Support-V4) within.
For example, the Login it's on a Single Activity with a Fragment, when the logs-in then the App changes it's navigation behavior and uses multiple fragments, I did this, because passing data between Fragment A to Fragment B it's way much easier than passing data Between an Activity A to an Activity B.
So My issue it's presented when I rotate the device, on my first approach, the initial fragment was loaded, but what would happen, if the user it's on Page 15 and it rotates it's device, it would return to Fragment 1 and give a very bad user-experience. I set all my fragments to retain their instance and added this on the MainActivity on Create:
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
initBackStackManager();
initControllers();
mayDownloadData();
setTitle();
if(savedInstanceState == null){
addAreaFragment();
}
}
Now, the first fragment is not loaded after screen orientation change, but If I try to make a fragment transaction, it says Can not perform FragmentTransaction.commit() after onSaveInstanceState(), is there a way to handle this? Or Do I really really need to use multiple Activities with a Fragment embedded within?
Thank you very much!
EDITED
I forgot to add that this happens only on a specific Fragment... For example I have the following fragment flow:
AreaFragment -> WaiterSelectionFragment -> WaiterOptionsFragment.
If I'm in the AreaFragment and I rotate the device I can still add/replace fragments and nothing happens, no error it's being thrown. If I'm on the WaiterSelectionFragment no error happens too. BUT, If I'm on the WaiterOptionsFragment the error it's being thrown. The WaiterSelectionFragment has the following structure:
LinearLayout
FragmentTabHost
Inside the FragmentTabHost there are some fragments, and that's where the error it's happening. You might wonder Why FragmentTabHost? easy, the Customer wants that App to show the TabBar, If I use Native Android Tabs the Tabs get rearranged to the ActionBar when on
Landscape position.
EDIT 2
I've used the method provided by #AJ Macdonald, but no luck so far.
I have my Current Fragment being saved at onSaveInstanceState(Bundle) method and restore my fragment on onRestoreInstanceState(Bundle) method on the Android Activity, I recover my back button and the current Fragment but when I get to the third Fragment the error still occurs. I'm using a ViewPager that holds 4 Fragments, Will this be causing the Issue? Only on this section of the App Happens. I've 4 (main workflow) fragments, on the First, Second and Third Fragment no error it's being presented, only on the ViewPager part.
Give each of your fragments a unique tag.
In your activity's onSaveInstanceState, store the current fragment. (This will probably be easiest to do if you keep a variable that automatically updates every time the fragment changes.)
In your activity's onCreate or onRestoreInstanceState, pull the tag out of the saved bundle and start a new fragment of that type.
public static final int FRAGMENT_A = 0;
public static final int FRAGMENT_B = 1;
private int currentFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//other stuff
if(savedInstanceState == null){
addAreaFragment();
currentFragment = FRAGMENT_A;
}else{
currentFragment = savedInstanceState.getInt("currentFragment");
switch(currentFragment){
case FRAGMENT_A:
addAreaFragment();
break;
case FRAGMENT_B:
addFragmentB();
}
}
}
// when you switch fragment A for fragment B:
currentFragment = FRAGMENT_B;
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt("currentFragment", currentFragment);
super.onSaveInstanceState(savedInstanceState);
}
A suggestion to try is to use FragmentTransaction.commitAllowingStateLoss() in place of FragmentTransaction.commit(). That should stop the Exception from being thrown, but the downside is if you rotate the device again the most recent state of the UI may not return. That is a suggestion given that I am not sure of the effect of using FragmentTabHost, if it has any effect at all.

onCreateView called two times on Fragment when I rotate the screen

I have an Activity with a NavigationDrawer and a Fragment which is created when I click on the NavigationDrawer. When I create the Fragment, I pass to it some arguments.
When I rotate the screen, the activity is recreated, the navigationdrawer is recreated and also the Fragment.
After this operations, the fragment is recreated, but without arguments.
I want understand who creates the Fragment the second time...
Can anyone help me?
I ran into a similar issue when trying to restore the video player state (play or pause) and position (time elapsed) after changing the screen orientation. When I rotate the screen once, I noticed that onCreateView in the fragment activity is called twice. When it is rotated twice, onCreateView is called three time - and so on. The last call somehow forgets the states I saved in onSaveInstanceState (in the fragment activity).
Upon digging around, I found the answer I was looking for. In the activity that adds the fragment, I needed to check if savedInstanceState is null. Only if it is null should you add the fragment.
For example:
if (savedInstanceState == null) {
fragmentManager = getSupportFragmentManager();
FragmentActivity fragment = new FragmentActivity();
fragmentManager.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
More information can be found here:
Android Fragment lifecycle over orientation changes
Use this in your manifest with Fragment activity:
android:configChanges="orientation|keyboardHidden|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)

Android App Losing State When Switching Tabs And Orientation

The problem: I have a tabbed android app and I'm losing the content in TabOne whenever I follow these (admittedly strange) steps:
Change to another tab.
Switch orientation to landscape.
Switch orientation back to portrait.
Change back to TabOne.
Android App Description: I have a pretty bare-bones android app with three tabs that were built using google's TabLayout tutorial, we'll call them TabOne, TabTwo, and TabThree. Only TabOne has any content: a simple EditText view and Button that lets you add text to the body of TabOne. This is rigged up using a custom ArrayAdapter, which may have something to do with the strange behavior.
Note that this does not occur if I change orientation while remaining on TabOne. This is because I have implemented OnSaveInstanceState() and OnRestoreInstanceState() to save my list of data in my TabOneActivity class.
I had the same problem - the solution I found was to create a 'Dummy' tab and activity for the first tab in the TabLayout onCreate, then in onResume of the Tab Layout Activity, hide the 'Dummy' tab and select the 2nd tab programmatically. Not nice, but works as saves state of 2nd tab (i.e. 1st visible tab).
#Override
protected void onResume() {
super.onResume();
if (getTabHost() != null && getTabHost().getTabWidget()!= null) {
getTabHost().getTabWidget().getChildAt(0).setVisibility(View.GONE);
if (getTabHost().getCurrentTab() == 0) {
getTabHost().setCurrentTab(1);
}
}
}
You also need to restore your activity state in onCreate, as well as in OnRestoreInstanceState.
I should point out though that this technique is only for transient data, not for long term data storage. For that you should be saving the data to a database or to SharedPreferences in onPause, and then retrieving the data in onResume.

Categories

Resources