I have an Android app with a listview that updates the listview when the user presses a button. The app has two actionbar tabs. When I switch between the tabs, rotate the screen, or press back to exit out of the activity, all the data in the listview disappears. I need the data to remain. I know that at least with the orientation change I can save the data in onSaveInstanceState(). So I save a String[] in savedInstanceState, and retrieve the String[] in onCreateView(). This seems to work fine, it reads the data properly. Then I attempt to update the listview. This also seems to read the data and work properly, only the listview does not update and instead remains blank. My code for this is:
if (savedInstanceState != null) {
lvArray = savedInstanceState.getStringArray("lvArray");
setupListView();
} else {
lvArray= new String[LISTVIEW_LIMIT];
for (int i = 0; i < LISTVIEW_LIMIT; i++) {
lvArray[i] = " ";
}
setupListView();
}
private void setupListView() {
if (listview.getAdapter() == null) {
ArrayAdapter<String> listviewAdapter = new ArrayAdapter<String>(getActivity().getApplicationContext(), android.R.layout.simple_list_item_1, lvArray);
listview.setAdapter(listviewAdapter);
}
registerForContextMenu(listview);
}
So as I said, I go through this, it reads the data properly, it sets up an ArrayAdapter of the data, and sets the adapter for the the listview. It all seems to work, only it doesn't actually update it. I know all the data's there but it's still not working?
Also, I have the ability to set the listview to different sizes (by LISTVIEW_LIMIT). This works fine, except it also removes all the data already in the listview. How can I keep the data instead of recreating the array when the limit changes?
And what would be the best way to save this in onPause() or onStop(), so that I could keep the listview up even if back is pressed or the tab is switched?
EDIT: Changed to use notifyDataSetChanged(), still not working:
lvStopwatch = (ListView) view.findViewById(R.id.list_stopwatch);
stopwatchTimes = new String[TOTAL_TIMES];
for (int i = 0; i < TOTAL_TIMES; i++) {
stopwatchTimes[i] = " ";
}
setupListView();
if (savedInstanceState != null) {
stopwatchRunning = savedInstanceState.getBoolean("stopwatchRunning", false);
stopwatchTimes = savedInstanceState.getStringArray("stopwatchTimes");
stopwatchAdapter.notifyDataSetChanged();
} else {
stopwatchRunning = prefs.getBoolean("stopwatchRunning", false);
Log.d("savedPASS", String.valueOf(stopwatchRunning));
}
private void setupListView() {
if (lvStopwatch.getAdapter() == null) {
stopwatchAdapter = new ArrayAdapter<String>(getActivity().getApplicationContext(), android.R.layout.simple_list_item_1, stopwatchTimes);
lvStopwatch.setAdapter(stopwatchAdapter);
}
registerForContextMenu(lvStopwatch);
}
try this
adapter.notifyDataSetChanged();
Keep listview data save in some data structure.
I find HashMaps best for this.
Call Sales adapter again when you want to populate.
adapter=new MyAdapter(dataHashMap);
listView.setAdapter(adapter);
Found the solution. For some reason it doesn't display the values properly when restoring them in the onCreateView() method. I restored them in the onCreate() method of the fragment instead . I didn't really change much other than moving the saveInstanceState check into the onCreate() method, but now it works.
Related
I have two Spinners. In onCreate i setup listeners for them. At some other places i populate them.
Now I am not sure what the best practice is for handling these spinners when screen orientation changes. Should i store all values and selected item in sharedPreferences somehow or in savedInstanceState?
If you can, please advise me in a prefered way and also include some sample code for handling spinners. The goal here is to keep the values and selected item thru the lifecycle.
I will include code at request or if needed.
Thanks
Try this, onSaveInstanceState for Screen Orientation for saving your selected value of spinner,According to my choice shared preference is not good choice for saving selected values of spinner.
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("mySpinner", mySpinner.getSelectedItemPosition());
// do this for each or your Spinner
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// initialize all your visual fields
if (savedInstanceState != null) {
mySpinner.setSelection(savedInstanceState.getInt("mySpinner", 0));
// do this for each of your text views
}
}
Add android:configChanges="orientation|screenSize" in your AndroidManifest file, it will keep spinner value selected on orientation change.
User Spinner Like this:
mSpinner = findViewById(R.id.spinner);
ArrayList<String> stringArrayList = new ArrayList<>();
for (int i = 0; i < 6; i++) {
stringArrayList.add("List Item " + i);
}
ArrayAdapter arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, stringArrayList);
spinner.setAdapter(arrayAdapter);
I have a spinner populated from an API call.
At first loading of fragment, the list is correctly populated, with differents items, one for row.
After a submit (I need a submit in this fragment) the List is re-populated several times.
This is the onCreate method:
#Override
public void onCreate(Bundle savedInstanceState) {
mItemSubscriptionList = new ArrayList<>();
super.onCreate(savedInstanceState);
}
And this is the method that populate the array list
private void loadItemSubscription() {
ApiMapper mapper = new ApiMapper();
mapper.getBalance(new ApiMapper.VolleyCallback<JSONArray>() {
#Override
public void onSuccess(JSONArray balance) {
if (balance != null) {
Log.d(LOGTAG , balance.toString());
mItemSubscriptionList.clear();
for (int i = 0; i < balance.length(); i++) {
Log.d(LOGTAG , "Popolo lo spinner "+i);
try {
JSONObject obj = balance.getJSONObject(i);
String quantity = obj.getString("balance");
quantity = quantity.replace(".00","");
int id_item = obj.getInt("id_item");
String item = obj.getString("item");
item = item + ' '+'('+quantity+')';
Log.d(LOGTAG , quantity);
Log.d(LOGTAG , item);
ModelItemSubscription modelItemSubscription = new ModelItemSubscription();
modelItemSubscription.setMItemId(id_item);
modelItemSubscription.setMItemName(item);
modelItemSubscription.setMItemQuantity(quantity);
mItemSubscriptionList.add(modelItemSubscription);
mBaseApp.setItemSubscription(modelItemSubscription);
mLoadingDialog.dismiss();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
mItemSubscriptionList = mBaseApp.getItemSubscription();
if ( mItemSubscriptionList!=null ) {
renderSpinnerItemSubscription(mItemSubscriptionList);
}
}
}, mToken, mIdMemberCard, mIdCampaign);
}
In real scenario, I have 4 items to populate. The Log "Popolo lo spinner" is printed exactly 4 times, but this is the ugly finaly result.
Instead this is the right behaviour after the first loading (only 1 item for type)
Fyi this is the array that populates:
[{"balance":"0.00","item":"Lampada","id_item":"540"},{"balance":"0.00","item":"Taglio","id_item":"541"},{"balance":"1.00","item":"Piega","id_item":"542"},{"balance":"11.00","item":"Gelati","id_item":"543"}]
Thank you very much
The problem with your code is the list is getting populated every time the API call is made. Means if the call is made 4 times then your list would be populated 4 times with the same data.
mItemSubscriptionList.add(modelItemSubscription); will not override but it will add the items no matter it the items are already present in the list.
Now what I suggest is you should clear your list before you make a call to API. Just add mItemSubscriptionList.clear() after ApiMapper mapper = new ApiMapper();
I can't see where you are populating your Spinner adapter so I will describe how it should be done with no code.
First everytime you fetch your data from you API you should clear your main ArrayList which holds the data and update the Spinner's adapter:
adapter.notifySetDataChanged(); // Depending on the adapter
After that populate your ArrayList with the new fetched data and call
adapter.notifySetDataChanged();
again so your data get refreshed.
Try this and tell if it works. If not, please add more code so we can see how you are populating your adapter after fetching the data.
I'm attempting to get method to handle updates to the ListView when SetWeatherData is called. Nothing ever shows up in my listview below. Any ideas? _rootView points to the right root and ListView comes back not null. m_weatherdata has a couple string elements in it.
Note the initial set of data does not show up either. Just blank.
I'm thinking it should be easier to setup a generic method to update a ListView when the data changes using straight up code.
private ArrayList<String> m_weatherdata;
private void SetWeatherData ( ArrayList<String> _weather)
{
m_weatherdata = _weather;
UpdateWeatherUI();
return;
}
ArrayAdapter<String> m_adapter = null;
private void UpdateWeatherUI()
{
if ( m_adapter == null ) {
m_adapter = new ArrayAdapter<String>(
this.getContext(),
R.layout.list_item_forecast,
R.id.list_item_forecast_textview,
m_weatherdata);
View _rootview = this.getLayoutInflater(null).inflate(R.layout.fragment_main, null, false);
ListView _listview = (ListView) _rootview.findViewById(R.id.listview_forecast);
_listview.setAdapter(m_adapter);
}
else
{
m_adapter.notifyDataSetChanged();
}
}
You are assigning a new ArrayList to your dataset.
m_weatherdata = _weather;
Instead add items to the dataset. Like this
m_weatherdata.addAll(_weather);
private void SetWeatherData ( ArrayList<String> _weather)
{
m_weatherdata.addAll(_weather);//change here
UpdateWeatherUI();
return;
}
When you set an adapter there is an observer attached to the
underlying data. So notifyDatasetChanged() only works if you only
modify the data in it.
If you want to clear all data from your dataset before adding new items to it, use the clear() method of ArrayList
private void SetWeatherData ( ArrayList<String> _weather)
{
m_weatherdata.clear();//change here
m_weatherdata.addAll(_weather);//change here
UpdateWeatherUI();
return;
}
m_weatherdata = _weather; // updates the local variable with new set of data. but adapter doesn't know about the changes made as you have created the instance of the adapter with array list by the following line of code.
m_adapter = new ArrayAdapter<String>(this.getContext(), R.layout.list_item_forecast,R.id.list_item_forecast_textview,m_weatherdata);
In you want to updated the data either call
m_adapter.addAll(newsetofstringtobeadded);
or
Create new adapter
This will update your list.
I have a ListView with a custom ArrayAdapter and custom items. These items contain multiple View-element, including a Spinner. This Spinner's ArrayAdapter is set like so:
// Method to set or update the Tags in the Spinner
public void updateTagsSpinner(MyHolder h, Spinner sp){
if(h != null && h.orderedProductItem != null){
// If the given Spinner null, it means we change the OrderedProductItem's Spinner
// Is the given Spinner not null, it means we change the Manage Tag's PopupWindow's Spinner
if(sp == null)
sp = h.spTags;
// We know it's an ArrayAdapter<String> so we just ignore the
// "Unchecked cast from SpinnerAdapter to ArrayAdapter<String>" warning
#SuppressWarnings("unchecked")
ArrayAdapter<String> spAdapt = (ArrayAdapter<String>) sp.getAdapter();
ArrayList<String> tagStrings = Controller.getInstance().getAllTagsWithOrderedProductItem(h.orderedProductItem));
if(tagStrings != null && tagStrings.size() > 0){
if(spAdapt == null){
spAdapt = new ArrayAdapter<String>(ChecklistActivity.this, android.R.layout.simple_spinner_item, tagStrings);
spAdapt.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// ArrayAdapter's setNotifyOnChange is true by default,
// but I set it nonetheless, just in case
spAdapt.setNotifyOnChange(true);
sp.setAdapter(spAdapt);
}
else{
spAdapt.clear();
spAdapt.addAll(tagStrings);
}
}
sp.setSelection(h.orderedProductItem.getSelectedFilter());
}
}
For some reason, every time I scroll down and then up again, my Spinners are completely empty.. And when I click on them I can't even open any Spinners anymore (including the ones that aren't empty) because of a warning:
W/InputEventReceiver(899): Attempted to finish an input event but the input event receiver has already been disposed.
Ok, I found the problem, and it's a pesky one.. That's the reason why I post question/answer as whole to help others with similar problems.
WARNING: adapter.clear() changes the original list that has been used by the ArrayAdapter!!
In my Controller I have a HashMap (allTagsOfProducts) with a key = int productId and a value = ArrayList<String> productTags. In the getAllTagsWithOrderedProductItem-method I use the following:
public ArrayList<String> getAllTagsWithOrderedProductItem(OrderedProductItem opi){
if(opi != null && opi.getProductId() > 0){
// Check if a new Tag has been added / removed
if(getProductById(opi.getProductId()).getTagsHasChanged()){
ArrayList<String> newProductTags = new ArrayList<String>();
... // Do some stuff to fill the newProductTags-List
// Replace the previously saved Tags of this Products with this updated one
allTagsOfProduct.put(opi.getProductId(), newProductTags);
// Set the boolean on false
getProductById(opi.getProductId()).setTagsHasChanged(false);
// And return this update list
return newProductTags;
}
// If nothing is added / removed, just return the list we already saved
else
return allTagsOfProduct.get(opi.getProductId());
}
// If the given OrderedProductItem is null return an empty list
else
return new ArrayList<String>();
}
When I debugged this I found out that when I went into the else to the return allTagsOfProduct.get(opi.getProductId()); the first time, everything works fine, and when I went here again, the saved ArrayList inside the HashMap was empty ([]).. The only reason I could think of was the .clear() method of the ArrayAdapter. So instead I had to use a new ArrayList without the reference to the ArrayList in my Controller's HashMap.
TL;DR: The line ArrayList<String> tagStrings = Controller.getInstance().getAllTagsWithOrderedProductItem(h.orderedProductItem)); in my question post is replaced with:
// We create a new ArrayList here, since spAdapt.clear() changes the original list
// all the way into the Controller's HashMap.. You gotta love the Java references.. >.>
ArrayList<String> tagStrings = new ArrayList<String>(
Controller.getInstance().getAllTagsWithOrderedProductItem(h.orderedProductItem));
PS: The reason why I also couldn't open the non-empty Spinners anymore and it gave me the Warning is because of custom ArrayAdapter's (my ListView's in this case) getView() recycling principle. Click this link for more information of how ListView's getView() recycling works. It re-uses the Item's Spinner for another Item. So even though this new Item's Spinner is filled correctly with the other Item's ArrayList/ArrayAdapter, the Input-EventHandler isn't reset, so I still get this Warning for the non-empty Spinners in the recycling ListView-Item.
public void UpdateData(ArrayList<HashMap<String,String>> array_list){
GridView glist = (GridView) findViewById(R.id.tipss_grid)
adapter2 =new CurrentAdapter(CurrentChanels.this,array_list);
glist.setAdapter(adapter2);
}
I call this method to populate data in gridview. I m displaying currently running programs. After every 1 min I call this method to refresh the data. The problem is that when user is on the last element of gridview and mean while I refresh it then control move to top of the screen. I do not want the screen to move,it must stay where it is before refresh. Any suggestions?
This is because every one minute you are creating a new Adapter and assigning it to the GridView.
Implement a new method resetData() in CurrentAdapter:
public void resetData(List<HashMap<String,String>> list) {
_list.clear();
_list.addAll(list);
notifyDataSetChanged();
}
Call resetData() whenever you want to refresh the grid:
GridView glist = (GridView) findViewById(R.id.tipss_grid);
if (glist.getAdapter() == null) {
CurrentAdapter adapter2 = new CurrentAdapter(CurrentChanels.this,
array_list);
glist.setAdapter(adapter2);
} else {
CurrentAdapter adapter2 = ((CurrentAdapter)glist.getAdapter());
adapter2.resetData(array_list);
}
use this line after setadapter line
glist.setSelection(adapter2.getCount() - 1) ;
You can call adapter.notifyDataSetChanged() but if you creating new ArrayList you have to set new adapter. You should change data in old ArrayList if you want notifyDataSetChanged() work.