I am using two spinners. The items in second spinner gets populated dynamically based on the selection of the item of first spinner. I have implemented OnItemSelectedListener and overridden onItemSelected to achieve this. I am also using SharedPreferences to persist certain data to take care of screen rotation. The trouble I am facing is that, everything works perfectly fine and as expected with android version 4.0 but the same code fails to work with above versions of android(4.1, 4.2, 4.3) and selected item of second spinner resets on screen rotation. What is driving me nuts is, with android version 4.0 things work perfectly fine. Any suggestions?
I have no idea what it could possibly have to do with versions > 4.0, however it might be a better idea to handle a configuration change by writing the data you want to keep into the bundle in onSaveInstanceState(Bundle out) and read it in onCreate(Bundle savedInstance).
Even though it won't "fix" the strange behavior you are seeing, but it should work...
In my opinion is not a good idea to use SharedPreferences to persist that data. at the same time is not good idea to put a Spinner in the onSaveInstanceState() Bundle. Besides the fact that it is impossible, doing so would cause your new activity to hold onto a reference to that Spinner, which holds onto a reference to your old activity, which will cause a substantial memory leak.
Your Spinner's contents are set via some sort of Adapter. You should be making sure your new Activity can re-create that Adapter. Then, put either getItemSelectedId() or getItemSelectedPosition() in the Bundle, so you can restore the selection in the new Activity.
I found a solution, although is not perfect, more of a hook, if the screen is rotated , I am assuming the item selected item of first spinner and its index will be same(as i am dealing with natural numbers) so i am setting the data in the second spinner accordingly, otherwise creating a new spinner and setting the required data in that, in the onItemSelected listener of the first spinner. Not very clean, but works for me (for 4.0 and above versions). if(firstSpinnerItemSelected != position || firstSpinnerItemSelected == 0){
firstSpinner.setSelection(secondSpinnerItemSelected, true);
ArrayAdapter secondAdapter = new ArrayAdapter(
context, R.layout.spinner_item, itemList);
secondSpinner.setAdapter(secondAdapter);
}
else{
secondSpinner.setSelection(secondSpinnerItemSelected, false);
}
Try to implement methods
onSaveInstaceState() // to save current selected items;
and
onRestoreInstanceState() // to select saved items.
Or you can add to your manifests activity tag
android:configChanges="orientation"
then your activity wont be recreated, and everything should be the same
Related
I have an activity A with a listview and upon selecting some items in the list, and navigating to another activity B with the selected items in the list of Activity A. And made some modifications. After return to A. My screen is not updating with the latest modifications.
Please guide me how to do this. Thank you in advance.
If you have unified data storage for both activities (so that you know that this problem is purely UI issue, because the data itself is updated upon returning from activity B), then you should call this method on your ListView adapter:
yourListView.getAdapter().notifyDataSetChanged()
In order to display what you want, you need to have some TextViews in your other activity and then just add a text to them to display what you need.
When you get the data, just do like this in the onCreate method:
TextView tv = (TextView)inflate.findViewById(R.id.myTextView);
tv.setText(theDataStringYouReceived);
First off, I've already looked at this question. In my scenario, I have a fragment that extends support.v4.app.ListFragment. The fragment calls a custom adapter. I need to handle orientation changes on a device that will switch to a 2 pane layout. I believe I'm doing most things right as the view itself changes correctly and I am able to retrieve the data from savedInstanceState. However, my list is always empty. I tried the recommended answer in the linked question (calling setListShown(true)), but I get an exception, "Can't be used with a custom content view". The relevant code is below:
#Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
LayoutInflater inflater = LayoutInflater.from(getActivity());
ViewGroup group = ((ViewGroup) getView());
group.removeAllViews();
inflater.inflate(R.layout.activity_message_list, group);
if(!messages.isEmpty()){
mAdapter = new MessageListAdapter(getActivity().getApplicationContext(), messages);
}
setListAdapter(mAdapter);
}
The adapter's getView method is never invoked after the configuration change. What else do I need to do to re-hydrate the view? Let me know if you need to see any other code.
Since you are creating a new view then you have to redo all View's initialization that you do in the original piece of code (in onViewCreated or somewhere else). So, in order to initialize the ListView - you should do something like this:
View rootView = inflater.inflate(R.layout.activity_message_list, group);
if(!messages.isEmpty()){
mAdapter = new MessageListAdapter(getActivity().getApplicationContext(), messages);
}
ListView listView = (ListView) root.findViewById(R.id.<your_list_view_id>;
listView.setAdapter(mAdapter);
Just keep in mind that you also have to do all other initialization (creating references to Views and creating onClickListeners or whatever else you're doing)
There is a way to avoid app reload after device orientation changed, you just have to tell system that you'll handle screen orientation changed events on your own.
Have you tried to add parameter android:configChanges="orientation" inside activity tag in Manifest.xml?
With a ton of direction from Chaosit, I was able to achieve what I wanted. My app has a nav drawer activity and I swap the fragment (which is a master-detail type fragment) based on the selection. I added android:configChanges="orientation" so the fragment wouldn't revert back to the default selection I made in the onCreate method of the activity. So, I removed that line and made the adjustments to store the selection in the savedInstanceState bundle. Everything is working as I would have expected now.
I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).
Hello friends,
As i am deleting the items of arrylist from listview than the item of arrylist become deleted but the problem occurs when i change the device mode from (Landscape to portrait mode ) OR ( portrait to Landscape mode ) than the deleted item becomes visible on the listview.Please help me to short from these problem.Thanks in advance
Thanks a lot all my dear friends for yours valuable comment....
I just change in the manifest file and it work fine for me..Again thanks friends..Below is the line which i have added in the manifest file along activity
android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode
OR USE SIMPLY THESE
android:configChanges="orientation|screenSize|screenLayout"
Every time the underlying data for an adapter changes, you need to call the notifyDataSetChanged() method on your adapter. It causes your ListView to be "refreshed".
Update:
bro i used it but my main problem is that when i used the both modes
of the device than the deleted items also shows in the listview
It seems that you are reloading the same old data when the orientation changes. The changes do not persist. You need a mechanism whereby your changes to the underlying data will persist.
For an ArrayAdapter, notifyDataSetChanged works if you use the add(), insert(), remove(), and clear() on the Adapter.
arrayList.remove(position);
myAdapter.notifyDataSetChanged()
when u update listview u use this methed on adapter object
Adapter.notifyDataSetChanged()
#Ravindra Kushwaha use follwing steps,
Store the new arraylist in local db or shared preferences.
If you delete an element immediately update the local db.
Always use arraylist from local db.
When Activity goes to landscape, portrait mode activity is destroyed & it created again. I think in your activity's onCreate() method it reinitialized by your previous values. please check it.
If I am right then, you need to store the state of current arrays before activity is destroyed & restore it in onCreate() method.
This is sample code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
// get values from Bundle & set it to adapter
}
#Override
protected void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
// save your state to Bundle
}
On orientation change your arraylist is getting reinitialized or again getting back all the data from its source.
So when you are removing the data from arraylist, Do remove it from the main source of the arraylist.
As a result of this the arraylist will not get the removed data on orientation.
Hope this helps. Happy coding :)
P.S. Please mark this answer as correct if it helps.
I have an app that needs tabs with one list in the content of each tab. Those lists need to refresh several times without changing the tab. The lists represent trees of possible choices, so the user clicks element 4, for example, and I have to refresh the list to show the elements under 4.
The easiest (and probably ugliest) way I found to accomplish this is to attach a listener to the list "currently" shown, and launch "this" activity again (with some extras relating the element the user clicked), effectually recreating the whole view, tabs and everything. This allows me to keep each state of the lists independent, and makes the back support easy enough. --I said it was ugly!
For that to work, I create the tabs inside onCreate, like this:
TabHost.TabSpec spec1 = tabHost.newTabSpec("TOC").setIndicator("TOC", res.getDrawable(R.drawable.ico_table_of_contents)).setContent(this);
tabHost.addTab(spec1);
TabHost.TabSpec spec2 = tabHost.newTabSpec("BLAH").setIndicator("BLAH", res.getDrawable(R.drawable.blah)).setContent(this);
tabHost.addTab(spec2);
The .setContent(this) works because this activity extends TabActivity and implements TabHost.TabContentFactory. The createTabContent function returns this:
return from(this).inflate(R.layout.new_list, getTabHost().getTabContentView(), true);
no matter which tab I'm in.
After this, I update the List with an adapter, like this:
private void updateList(ArrayList<Element> items) {
ElementAdapter adapter = new ElementAdapter(this, R.layout.element, items);
ListView list = (ListView) findViewById(R.id.theListView);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {...}
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
The problem is that the first tab works great, but the second doesn't. It needs just the same view as the first one (the list showing a list of elements), but it shows nothing-zippo-nada, just the tabs. The ArrayList I pass in has elements in both cases.
Walking the code, I noticed that in my createTabs, the createTabContent is called ONCE, on tabHost.addTab(spec1) (for the first tab), but NOT when adding the second.
What gives? Why do I get an empty screen instead of the list, if both cases have elements to show?
I hope this makes sense. It's a bizarre problem to explain and I'm not sure if I'm making much sense.
Any help will be appreciated.
Thanks
llappall
I have never tried using the same TabContentFactory more than once. I would have expected what you used here to work, particularly given the tag parameter to createTabContent().
That being said, try using multiple TabContentFactory objects and see if that helps (e.g., use instances of anonymous inner classes).