I have a weird situation with a custom ArrayAdapter.
When I try to update the adpater with new data, instead of the data being updated, the new data are inserted to the beginning of the listview and the old data are remaining and visible once you scroll the listview.
UPDATE
It seems that the problem is caused by the ArrayList from the fragment bundle.
If I don't set the listview in the onCreateView from the fragment bundle, my update code works fine, but now I'm puzzled why this:
ArrayList<Collection> cityStoresList = fragmentBundle.getParcelableArrayList("stores");
mStoresList.addAll(cityStoresList);
is causing the items to always remain on the list?
END OF UPDATE
Here are parts of the code: (Collection is a custom object model class)
ArrayList<Collection> mStoresList = new ArrayList<Collection>();
/** List Adapter */
private StoresListAdapter mListAdapter;
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
boolean attach = false;
if (container == null) {
attach = true;
}
Bundle fragmentBundle = getArguments();
ArrayList<Collection> cityStoresList = fragmentBundle.getParcelableArrayList("stores");
mStoresList.addAll(cityStoresList);
//inflater code not added here, but is present
mListAdapter = new StoresListAdapter(getActivity(), mStoresList);
mListView.setAdapter(mListAdapter);
return layout;
}
My custom adapter is as follows:
public class StoresListAdapter extends ArrayAdapter<Collection> {
public StoresListAdapter(Context c, ArrayList<Collection> array) {
super(c, 0, array);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// View from recycle
View row = convertView;
// Handle inflation
if (row == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.row_store, null);
}
// Get the Store
Collection store = getItem(position);
//rest of code follows
return row;
}
}
Now when I want to update my adapter I use the following:
public void updateAdapter(ArrayList<Collection> storesList, final int listIndex) {
mStoresList.clear();
mStoresList.addAll(storesList);
mListAdapter.notifyDataSetChanged();
}
And this creates the issue I mentioned. The new items appear fine, but the previous ones are still visible and added after the new ones.
It's like adding the new items in the ArrayList as the first items, instead of just replacing the old ones.
Any ideas, suggestions?
Ok, finally found the problem.
Because the whole thing is within a fragment, the oncreateView is actually called when I'm attaching the array, so what happens is that my updateAdapter method is called, the items are added and displayed, before the view is actually visible.
Then the oncreateView method is fired and the original bundle items are being added to the Arraylist....
Related
First of all please correct me if my ideology is incorrect, it is my first time working with fragments and not that good at ListView adapters.
I am currently using DrawerLayout which consist of fragments as you'd expect, I have a FAB which upon clicking will add a new entry to the SQLite db entry and will refresh the ListView (data pulled from the db).
My thinking was that I could get the ListView adapter (which is in Profiles fragment class) in the ActivityMain (as the FAB is not part of the fragment and will have different actions depending on current fragment) and then call .notifyDataSetChanged()
The Profiles fragment class
public class Profiles extends Fragment{
public ProfileListAdapter adapter;
(...)
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DBHandler db = new DBHandler(getActivity().getBaseContext());
adapter = new ProfileListAdapter(getActivity().getBaseContext(), db.getAllProfiles());
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
ListView profileListView = (ListView)view.findViewById(R.id.profileListView);
profileListView.setAdapter(adapter);
}
ProfilesListAdapter (from here)
public class ProfileListAdapter extends ArrayAdapter<Profile> {
private static class ViewHolder {
TextView name;
}
public ProfileListAdapter(Context context, ArrayList<Profile> profileList) {
super(context, R.layout.profile_row, profileList);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
Profile profile = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
ViewHolder viewHolder; // view lookup cache stored in tag
if (convertView == null) {
// If there's no view to re-use, inflate a brand new view for row
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.profile_row, parent, false);
viewHolder.name = (TextView) convertView.findViewById(R.id.profileListRowValue);
// Cache the viewHolder object inside the fresh view
convertView.setTag(viewHolder);
} else {
// View is being recycled, retrieve the viewHolder object from tag
viewHolder = (ViewHolder) convertView.getTag();
}
// Populate the data into the template view using the data object
viewHolder.name.setText(profile.name);
// Return the completed view to render on screen
return convertView;
}
}
Inside the DBHelper class
// Getting All Profiles
public ArrayList<Profile> getAllProfiles() {
ArrayList<Profile> profileListItems = new ArrayList<Profile>();
// Select All Query
String selectQuery = "SELECT * FROM " + TABLE_PROFILE;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, new String[] {});
// looping through all rows and adding to list
if (cursor.moveToLast()) {
do {
Profile profile = new Profile();
profile.setId(Integer.parseInt(cursor.getString(0)));
profile.setName(cursor.getString(1));
// Adding contact to list
profileListItems.add(profile);
} while (cursor.moveToPrevious());
}
// return contact list
cursor.close();
return profileListItems;
}
And this is what I am trying to do upon FAB click controlled in MainActivity, magic number is just an int which starts at 0 and increments by one for me to test the db / ListView easier.
db.addProfile(new Profile("Test Profile " + magicNumber));
magicNumber++;
FragmentManager fm = getSupportFragmentManager();
Profiles fragment = (Profiles) fm.findFragmentById(R.id.fragmentProfiles);
//I gave the fragment xml main FrameLayout an ID
fragment.updateListView();
Inside the Fragment class
public void updateListView(){
adapter.notifyDataSetChanged();
}
I also tried returning the adapter from the Profiles fragment class which I feel is the wrong way to tackle this problem.
public ProfileListAdapter getAdapter(){
return adapter;
}
I always end up with
java.lang.NullPointerException: Attempt to invoke virtual method 'void layout.Profiles.updateListView()' on a null object reference
or
java.lang.NullPointerException: Attempt to invoke virtual method 'com.xxxxx.xxxxx.appName.ProfileListAdapter layout.Profiles.getAdapter()' on a null object reference
To my noobish eye it seems that the adapter cannot be access from outside the fragment Profiles class, not sure if that's what it is - even if so not sure how to work around this. Let me know if any other code / logcat is required. Thank you!
For anyone coming across this question, the solution I found was here: notifyDataSetChange not working from custom adapter (top answer worked well).
Although I did implement CodeCody's idea of putting the FABs in individual fragments I can safely revert to as it was earlier, not sure which option is better.
I've got a ListView in my application that's rendered in a ListFragment's onActivityCreated()using setListAdapter(). Passed in to this setListAdapter()are my implementation of ArrayAdapter. At some times this adapter can be empty, and that's fine, but at those moments I would like to show a message in the list telling that there are no items, instead of just an empty view. However I don't really know how to achieve this, as for I have researched most people to show lists in a ListActivityand by doing that you can setEmptyView() but this doesn't seem doable when using a ListFragment. Another way of doing this was to change view in the ListFragment if the ArrayAdapter has no item's in it, then change view to another showing the message, but this seems to me a bit hacky.
Whats really the proper way of doing what I want to achieve?
Tried to setEmptyView() to my ListView but that didn't work either, see code on how views are inflated in my ListFragment:
public class MyFragment extends ListFragment {
#SuppressLint("InflateParams")
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ListView listView = (ListView) inflater.inflate(R.layout.my_list, null);
listView.setEmptyView(inflater.inflate(R.layout.list_items_missing, null));
return listView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
MyItemArrayAdapter adapter = new MyItemArrayAdapter(getActivity());
// Populate adapter with items...
setListAdapter(adapter);
}
}
Shouldn't this result in the empty view beeing shown if no items exists in my adapter?
1) Try putting backgroundImage for ListView inside xml. If no item, show backgroundImage, if there is item, put background color.
2) You can also do what as #kgandroid suggested and here, setEmptyView() which you set custom view.
Example:
Another:
You can customize your adapter to show the layout R.layout.list_items_missing when (item == null) instead of inflating the normal custom list item layout that you implement.
Something like this:
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder; //asuming you have this in your custom adapter
if (convertView == null) {
holder = new ViewHolder();
if(MyItemArrayList.get(position)!=null)
{
convertView = this.inflater.inflate(R.layout.layout_list_item,
parent, false);
else
{
convertView = this.inflater.inflate(R.layout.list_items_missing,
parent, false);
}
//the rest of the adapter logic
}
I have a listview that is populated via an adapter. I need one of the items (which are just bits of text) in the listview to have a different background compared to the others (to mark it as the currently selected one).
I have all of the background logic, I just need a way to say listview.setBackgroundById(int position)
or something like that.
How do I do this?
This needs to be as simple as possible, 'cause all of the other things done in the Activity are currently working perfectly. :)
As asked, this is my Adapter that I'm using:
public class SampleAdapter extends ArrayAdapter<SampleItem> {
private String title;
public SampleAdapter(Context context) {
super(context, 0);
}
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_station, null);
}
TextView title = (TextView)convertView.findViewById(R.id.station_name);
font.setFont(title, 2, getActivity());
title.setText(getItem(position).title);
RelativeLayout info = (RelativeLayout)convertView.findViewById(R.id.info_relative_button);
info.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
MainActivity.setCurrentTab(41);
MainActivity.setBackDisabled(true);
Log.e("current tab:",String.valueOf(MainActivity.getCurrentTab()));
Fragment fragment = new StationInfo();
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
UserManager.getInstance().setStationId(getItem(position).id);
}
});
return convertView;
}
}
The SampleItem has 2 String fields, title and id, it's very simple.
You need to use a custom list adapter and have it return views with your desired background. Create a class extending ListAdapter or any of the existing SimpleAdapter etc and override getView to inflate a suitable view for your element, and add any logic you need to set the background of that view.
There is no way to tell the listview itself to decorate some of its elements by id or position.
Update: I just noticed you added the list adapter code.
Since you are already implementing getView, to change the background of your element simply call convertView.setBackgroundColor, or have two different views inflated depending on the situation.
(BTW it's really bad practice to call static methods on your activity like in your onClickListener.)
In ListView adapter:
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
if(view==null)
....
//for example every even list item to be grey, every odd to be white
if(((position+1)%2)==0)
view.setBackgroundColor(mContext.getResources().getColor(R.color.grey));
else view.setBackgroundColor(mContext.getResources().getColor(android.R.color.white));
Hope you get an idea...
I'm currently working on an Android app with two tabbed-fragments. One fragment consists of a pre-populated list in a listview. The other contains a dynamic list to be displayed in a listview.
I want to populate the listview on the second fragment from the list items from the first fragment.
The Problem
1) The ListView on fragment 2 doesn't populate unless the underlying adapter is initialized by a dummy list.
2) Changes on ListView are not instant. If I remove a list item from the list, the item still appears in the ListView on fragment 2. But if I switch between tabs, I see the desired List.
Here's the code:
Fragment 2
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstance) {
// listEntries = new ArrayList<InterimOrderEntry>(0);
listEntries = DummyListData.getOrder();
view = (ListView)inflater.inflate(R.layout.fragment2, container, false);
ListAdapter2 listAdapter2 = new ListAdapter2(orderEntries, getLayoutInflater(savedInstance), container);
view.setAdapter(listAdapter2);
return view;
}
ListAdapter1
private void addToList2(String itemName, int quantity, int itemImageId, Context context){
ListEntry listEntry = new ListEntry(itemName, quantity, itemImageId);
listEntries.clear();
listEntries.addAll(LisAdapter2.getOrderEntries());
listEntries.add(orderEntry);
new ListAdapter2().refreshListEntries(listEntries);
}
ListAdapter2
private static List<ListEntry> listEntries;
public void refreshListEntries(List<ListEntry> newListEntries){
listEntries.clear();
listEntries.addAll(newListEntries);
this.notifyDataSetChanged();
}
Would appreciate any help!
Figured it out, the answer was in the android docs, here's the link:
http://developer.android.com/training/basics/fragments/communicating.html
I'm trying to give the users of my app the option to change how they want their results displayed.
I've created a different layout item for each view and extended from BaseAdapter like so:
public View getView(int index, View recycledCompatibleView, ViewGroup parent)
{
// Just in case the view can be reused
View toReturn = recycledCompatibleView;
if(toReturn == null)
{
LayoutInflater inflator = LayoutInflater.from(parent.getContext());
toReturn = inflator.inflate(layoutId, null);
}
...
}
public void setResultsListStyle(int layoutId)
{
this.layoutId = layoutId;
}
Calling notifyDataSetChanged() is (observable through debug) refreshing the view because the new view is being inflated and returned from getView() method.
However the view on screen is not changing...
Is there something I'm missing ?
The problem here is that ListView may cache already inflated Views that are of old "format". You need to somehow reset this View "cache". For this you can use next trick:
mListView.setAdapter(mListView.getAdapter());
Instead of calling notifyDataSetChanged().