I am trying to build a split pane based on the example code provided at the end of the Android documentation about Fragments, basically a LinearLayout containing two fragments (all the details can be found in the above link):
TitlesFragment to display a list of titles
DetailsFragment to display the details of the currently selected title
My problem is that the following unexpected behavior happens:
I start the application in Landscape orientation: the titles list is displayed on the left and the details of the currently selected title (the first one at the beginning) are displayed on the right.
I select item X from the titles list: the title becomes highlighted since the ListView is set to CHOICE_MODE_SINGLE and its items implement the Checkable interface.
I switch to Portrait orientation (by rotating the device or emulator): as expected, only the titles list is shown and no item is selected in this configuration, because the ListView gets recreated and, by default, it is set to CHOICE_MODE_NONE.
I select item Y from the titles list: as expected, an activity showing only the DetailsFragment corresponding to the selection made is shown.
I switch back to Landscape orientation (here's where the unexpected behavior happens): the DetailsFragmenton the right correctly shows the details of item Y (which was selected in portrait orientation) but the TitlesFragment still shows item X as selected (i.e. the one which was selected BEFORE the first screen rotation).
Of course, I would like that the last selection made in Portrait orientation was correctly shown also within the TitlesFragment, otherwise I end up with an inconsistent highlighting which shows item X as selected while displaying the details of item Y.
I post the relevant code where the ListView mode is changed (within 'onActivityCreated') and the selected item is set (within the 'showDetails' method):
public static class TitlesFragment extends ListFragment {
boolean mDualPane;
int mCurCheckPosition = 0;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Populate list with our static array of titles.
setListAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
// Check to see if we have a frame in which to embed the details
// fragment directly in the containing UI.
View detailsFrame = getActivity().findViewById(R.id.details);
mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
if (mDualPane) {
// In dual-pane mode, the list view highlights the selected item.
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Make sure our UI is in the correct state.
showDetails(mCurCheckPosition);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
showDetails(position);
}
/**
* Helper function to show the details of a selected item, either by
* displaying a fragment in-place in the current UI, or starting a
* whole new activity in which it is displayed.
*/
void showDetails(int index) {
mCurCheckPosition = index;
if (mDualPane) {
// We can display everything in-place with fragments, so update
// the list to highlight the selected item and show the data.
getListView().setItemChecked(index, true);
// Check what fragment is currently shown, replace if needed.
DetailsFragment details = (DetailsFragment)
getFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {
// Make new fragment to show this selection.
details = DetailsFragment.newInstance(index);
// Execute a transaction, replacing any existing fragment
// with this one inside the frame.
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.details, details);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Intent intent = new Intent();
intent.setClass(getActivity(), DetailsActivity.class);
intent.putExtra("index", index);
startActivity(intent);
}
}
Does anyone have an idea about how to solve such issue?
Thanks in advance.
Two years later, here I am working with the Fragment Layout sample from Android's v4 support library and I'm stuck with this exact same issue!
Here's the only fix I came up with that doesn't require using CHOICE_MODE_SINGLE all the time:
just add a call to getListView().setItemChecked(mCurCheckPosition, true); in the onResume() method of TitlesFragment. And... that's it!
Maybe you should set the value of outState BEFORE you call super.onSaveInstanceState?
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("curChoice", mCurCheckPosition);
super.onSaveInstanceState(outState);
}
I've encountered the same problem on that code and as workaround I call setItemChecked on the onStart method:
#Override
public void onStart() {
super.onStart();
if (mDualPane) {
getListView().setItemChecked(mCurCheckPosition, true);
}
}
Or as LearningNerd said do it in onResume()
If you place android:configChanges="orientation" in your activity tag in the manifest file, your app will ignore the rotations but still render the relevant layouts.
Related
Background and Intro
My app consists of a single activity and 3 fragments. The app is viewed in two-panes only for 600dp smallest width in landscape only. In Portrait orientation it is viewed in a single pane nor matter which size.
Heres an overview of what it looks like.
SINGLE PANE MODE
TWO PANE MODE
Quick description of what this app does. In single pane mode fragmentAList is added to a container (R.id.list_container). When an item is pressed, its container is replaced with fragmentBDetail which contains a description and a button. Pressing this button goes to FragmentC. FragmentBDetail and FragmentC are added to back stack so the user can navigate back to FragmentAList.
In two pane mode, there are two containers (R.id.list_container) and (R.id.detail_container).
FragmentAList still goes inside R.id.list_container but clicking an item shows FragmentBDetail inside R.id.detail_container on the same screen.
The Problem I am having
I want to be able to go from two-pane mode to single pane when the orientation changes while maintaining the detail fragment. i.e. If the user was in two-pane mode and they clicked an item in the list to see the detail (say the Windows description) and when they switch to portrait orientation (single pane) I want the detail fragment (Windows description) to be shown in the single pane.
Likewise, if it was in single pane, and the user clicked a list item to see its detail. I want that detail fragment to be shown in second pane when the orientation changes to landscape.
At the current moment, if I am in two-pane mode and I switch orientation to single pane, I will see FragmentAList only. If I then click a list item I see FragmentAlist replaced with FragmentBDetail. If I then change orientation to two-pane mode, I will see two FragmentBDetails.
What I have tried
I am absolutely clueless on this problem and not sure what to do. All I have got at the moment is the tag of the fragment that is inside the detail_cotainer. With this tag I am able to find the fragment.
if(mIsTwoPane)
{
Fragment frag = getFragmentManager().findFragmentById(R.id.detail_container);
if(frag != null)
{
Log.d("TEST", "found something = " + frag.getTag());
}
else
{
Log.d("TEST", "nothing");
}
}
My Code
Below is what I deem the major parts of the code. Please let me know if you want to see others.
Main Activity
Here I am checking if I am in two pane mode. Also I add FragmentAList into the R.id.list_container (which will always be there)
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(findViewById(R.id.detail_container) != null)
{
mTwoPane = true;
}
if(savedInstanceState == null)
{
FragmentAList fragmentAList = new FragmentAList();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.list_container, fragmentAList, FragmentAList.class.getName());
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fragmentTransaction.commit();
}
}
FragmentAList
Here I get the value of mTwoPane from MainActivity. if its true, I set a variable to the ID of detail_container. If its false, I set to the ID of the R.id.list_container (which will always be there).
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view = inflater.inflate(R.layout.fragment_a, container, false);
mIsTwoPane = ((MainActivity) getActivity()).getIsTwoPane();
if(mIsTwoPane)
{
containerID = R.id.detail_container;
}
else
{
containerID = container.getId();
}
lvPlatforms = (ListView)view.findViewById(R.id.lvPlatforms);
return view;
}
This is used here within the onItemClickListener for the List View. the ContainerID variable contains the ID of the container based on the logic seen above in onCreateView. Also if its not in two pane mode I don't add to back stack only if its not two-pane (so the user can navigate back to the list view)
#Override
public void onItemClick(AdapterView<?> parent, View v, int pos, long id)
{
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(containerID, FragmentBDetail.newInstance(pos), FragmentBDetail.class.getName());
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(!mIsTwoPane) //NOT in two pane mode
{
fragmentTransaction.addToBackStack(null);
}
fragmentTransaction.commit();
}
Thank you for reading.
When the action bar has tabs, I've noticed that onTabSelected is called when the activity loads on screen. It also is being called whenever an orientation change occurs. My code queries the database depending on the tab being selected, and displays the query results to the loaded layout.
My problem is when saving tab state, and the current selected tab is 1 or higher, on restore state, since onTabSelected is called by default on the 0 tab, it will be called again when restored state tab is 1 or higher. This makes database query on tab 0 useless.
How to configure android that onTabSelected isn't called on tab creation or at least detect that this call is default and not user triggered?
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
int tabPos = tab.getPosition();
switch(tabPos) {
case 0:
// query database and display result
break;
case 1:
// a different query and display result
break;
case 2: ...
}
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
if(savedInstanceState.containsKey(STATE_SELECTED_TAB)) {
getActionBar().setSelectedNavigationItem(savedInstanceState.getInt(STATE_SELECTED_TAB));
}
super.onRestoreInstanceState(savedInstanceState);
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(STATE_SELECTED_TAB, getActionBar().getSelectedNavigationIndex());
super.onSaveInstanceState(outState);
}
Added complication:
When the current selected tab is 0, and the orientation changes, onTabSelected is still called twice! Once when the tabs are initially created, and 2nd time when onRestoreState restores the saved tab selected state, even though it is 0.
What I originally supposed was that onTabSelected was called twice, but I was mistaken. It was my fragment onCreateView being called twice, some errors in fragment transaction that added the same fragment twice on orientation change. onTabSelected is called once, and restore state calls onTabReselected is called too when the restored tab is also 0.
After scouring SO and google, I've found this question to have similar cause of problem.
Creating ActionBar tab also calling its selectTab functions
So after reviewing the reference docs on ActionBar from Google's Android site, addTab method is the one responsible for calling onTabSelected by default.
public abstract void addTab (ActionBar.Tab tab)
Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list. If this is the first tab to be added it will become the selected tab.
Parameters
tab Tab to add
Incidentally, other overloaded methods exist that do not call onTabSelected.
public abstract void addTab (ActionBar.Tab tab, boolean setSelected)
So I used these alternative methods instead and it fixed the problem.
However, once the tabs are displayed, the first tab may appear selected even though it's not. Clicking on it will call onTabSelected and not onTabReselected.
I solved it in this way:
Call setupWithViewPager before tabLayout.addOnTabSelectedListener
tabLayout.setupWithViewPager(viewPager)
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {....}
.....
}
because when you call setupWithViewPager, this will internally call
setOnTabSelectedListener(new
ViewPagerOnTabSelectedListener(viewPager));
so you better call it before adding tabSelect Listener to tabLayout
i think you can do this (and ignore any typo please :-)) :
public class MainActivity extends FragmentActivity {
boolean isConfigChanged;
int savedTabIndex;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState != null){
if(savedInstanceState.containsKey(STATE_SELECTED_TAB)) {
savedTabIndex = savedInstanceState.getInt(STATE_SELECTED_TAB);
//getActionBar().setSelectedNavigationItem(savedTabIndex); actually you do not need this
}
isConfigChanged = true;
}
// here add actionbar tabs
//...}
and in :
below code checks that if configuration changes and the user selected tab is not zero so this is default call and ignore but if isConfigChanged == true and the user selected tab is 0 you must query DB or if isConfigChanged == false you must query DB because it is first time that app is loading. a quick play may fit it to what you want.
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if(isConfigChanged && savedTabIndex != 0){
isConfigChanged = false;
return;
}
isConfigChanged = false;
int tabPos = tab.getPosition();
switch(tabPos) {
case 0:
// query database and display result
break;
case 1:
// a different query and display result
break;
case 2: ...
}
}
remove onRestore
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(STATE_SELECTED_TAB, getActionBar().getSelectedNavigationIndex());
super.onSaveInstanceState(outState);
}
I am new to fragments and have a lot of problems understanding and working with them. I will try to describe my problem step by step:
I start with a list
after selecting an item i start an activity with a dynamic url in a webview
If portrait, only the webview is showed, if landscape the list is presented next to it
In landscape when i select another item, it is showd in the webview (check the code)
Problem: When i turn back now to portrait the first selected item is showd instead of the last one.
How can i update the activity so it will keep the last opened url after orientation change?
if (fragment==null || ! fragment.isInLayout()) {
Intent intent = new Intent(this.getActivity(), DetailViewActivity.class);
intent.putExtra("link", urlforwebview);
startActivity(intent);
} else {
DetailView detailFragment =
(DetailView)
getFragmentManager().findFragmentById(R.id.detailfragment);
detailFragment.changeWebviewURL(urlforwebview);
}
FIX:
Added this to my detailfragment:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("currentUrl", url);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Restore last state for checked position.
url = savedInstanceState.getString("currentUrl", "");
changeWebviewURL(url);
}
}
Have a look at : onSaveInstanceState.
http://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState(android.os.Bundle)
This answer here can help you: android fragment- How to save states of views in a fragment when another fragment is pushed on top of it
This question already has answers here:
Android ListView in Fragment
(2 answers)
Closed 9 years ago.
I am a beginner in android development.
For a project, I would like to know how to implement a listview into a fragment ?
In my project, I have 3 fragments.
I have to retrieve all the contacts which are in my phone.
(I am using this example => http://samir-mangroliya.blogspot.fr/p/android-read-contact-and-display-in.html).
Then, I'll put them into one af the fragment, which contains a listview.
I am searching for many example, but it is only listview into ACTIVITY example.
Can you please help me ?
Best regards,
Tofuw
PS : Sorry for my bad english, I'm french :/
EDIT
Here is my code :
public class PhoneFrag extends ListFragment
{
private List<Contact>listecontact=new ArrayList<Contact>();
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
public void onActivityCreated(Bundle savedInstanceState, Context context)
{
super.onActivityCreated(savedInstanceState);
String[]values=new String[]{};
Cursor phones=context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
while(phones.moveToNext())
{
String name=phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String num=phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Contact contact=new Contact();
contact.setName(name);
contact.setNum(num);
listecontact.add(contact);
}
phones.close();
ArrayAdapter<String>adapter=new ArrayAdapter<String>(getActivity(), android.R.layout.simple_expandable_list_item_2, listecontact);
setListAdapter(adapter);
}
I have an error at the third last line :
the constructor ArrayAdapter(FragmentActivity, int,
List) is undefined
Can you help me please ?
Tofuw
Get your Fragment to extend ListFragment instead of Fragment.If you have used or looked at examples with ListActivity there is no need to associate this fragment with an xml layout file using setContentView(layout) anymore,Android creates a readymade Fragment with a ListView within it for your conveniance.Instead conveniance methods like setListAdapter(adapter) are available to you.However,in case you would like to access the ListView instance use the getListView().The Fragment seems to use onActivityCreated to do this:
I am guessing that you know fragments well enough to understand that this in ListActivity mean getActivity() in your ListFragment.
Here is the code that I found in the Android Fragment Documentation:
public static class TitlesFragment extends ListFragment {
boolean mDualPane;
int mCurCheckPosition = 0;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Populate list with our static array of titles.
setListAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
// Check to see if we have a frame in which to embed the details
// fragment directly in the containing UI.
View detailsFrame = getActivity().findViewById(R.id.details);
mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
if (mDualPane) {
// In dual-pane mode, the list view highlights the selected item.
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Make sure our UI is in the correct state.
showDetails(mCurCheckPosition);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
showDetails(position);
}
/**
* Helper function to show the details of a selected item, either by
* displaying a fragment in-place in the current UI, or starting a
* whole new activity in which it is displayed.
*/
void showDetails(int index) {
mCurCheckPosition = index;
if (mDualPane) {
// We can display everything in-place with fragments, so update
// the list to highlight the selected item and show the data.
getListView().setItemChecked(index, true);
// Check what fragment is currently shown, replace if needed.
DetailsFragment details = (DetailsFragment)
getFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {
// Make new fragment to show this selection.
details = DetailsFragment.newInstance(index);
// Execute a transaction, replacing any existing fragment
// with this one inside the frame.
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (index == 0) {
ft.replace(R.id.details, details);
} else {
ft.replace(R.id.a_item, details);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Intent intent = new Intent();
intent.setClass(getActivity(), DetailsActivity.class);
intent.putExtra("index", index);
startActivity(intent);
}
}
}
You can also use methods like setEmptyText and setListShown to display a message in case your Cursor has no rows and toggle the visibility of your listView respectively.Also,Android's Loader Framework is only supported with a Fragment and not an Activity in the support library.
I implemented my layout based on this tutorial: http://android-developers.blogspot.hu/2011/02/android-30-fragments-api.html
The differences are:
I have different fragments to show, based on the choice in the left
list
The "details fragments" (those that come to the right) have different options menus
My problem is that if I have already selected something from the left and then rotate the phone to portrait, the last optionsmenu is still there and is visible.
I think the problem comes from the last active "details" fragment is recreated after the orientation change. to test it I created these two methods:
#Override
public void onStart() {
super.onStart();
setHasOptionsMenu(true);
}
#Override
public void onStop() {
super.onStop();
setHasOptionsMenu(false);
}
And I'm showing the right fragment like this:
case R.id.prefs_medicines:
if (mDualPane) {
// Check what fragment is shown, replace if needed.
View prefsFrame = getActivity().findViewById(R.id.preferences);
if (prefsFrame != null) {
// Make new fragment to show this selection.
MedicineListF prefF = new MedicineListF();
// Execute a transaction, replacing any existing
// fragment with this one inside the frame.
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.preferences, prefF);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Intent intent = new Intent();
intent.setClass(getActivity(), MedicinePrefsActivity.class);
startActivity(intent);
}
break;
in one of my "details" fragment. when I debugged it, the onstart was called after the rotation.
The problem in pictures:
1: in landscape it's OK
Landscape mode http://img834.imageshack.us/img834/8918/error1d.png
2: in portrait: optionsmenu not needed
Portrait mode http://img860.imageshack.us/img860/8636/error2r.png
How can I get rid of the optionsmenu in portrait mode?
I had the same problem, and resolved it by setting setHasOptionsMenu(true) in the fragment only when savedInstanceState is null. If onCreate gets a bundle then the fragment is being restored in an orientation change to portrait, so don't display the menu.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
setHasOptionsMenu(true);
}
}