my problem is, that I have a ListView and a ArrayAdapter. Now I create the adapter like this:
items = OtherClass.DataSet1;
adapter = new MyArrayAdapter(getActivity(), R.layout.mein_listitem, items);
then I want to change the items e.g:
items = OtherClass.DataSet2;
adapter.notifyDataSetChanged();
But the result is, that I still see the DataSet1 items in my ListView and I dont know why.
I have the following whole code:
public class PlaceholderFragment extends Fragment implements ActionBar.TabListener {
ArrayList<Item> items;
MyArrayAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
items = OtherClass.DataSet1;
adapter = new MyArrayAdapter(getActivity(), R.layout.mein_listitem, items);
{...}
}
#Override
public void onTabSelected(Tab arg0, FragmentTransaction arg1) {
if (arg0.getText().equals(getText(R.string.foo))) {
items = OtherClass.DataSet1;
adapter.notifyDataSetChanged();
} else {
items = OtherClass.DataSet2;
adapter.notifyDataSetChanged();
}
}
}
public class Item {
public Item(String foo1) {
super();
Foo1 = foo1;
}
public String Foo1;
}
You have to add your items on your ArrayAdapter object via the .add() or .addAll() methods. This will actually append the objects you've provided as parameters to the dataset already showing up.
adapter.add(...);
adapter.addAll(...);
And (as you done), don't forget always calling .notifyDataSetChanged() on your adapter after adding items to it.
Related
I have implemented tabs functionality via SmartTabLayout library in my android application. At present I have used same fragment as viewpager for both of my tabs. Since, the only difference is the view is that sorting of listitems. Below is my HomeActivity code:
HomeActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
final SmartTabLayout viewPagerTab = (SmartTabLayout) findViewById(R.id.viewpagertab);
viewPagerTab.setCustomTabView(this);
FragmentPagerItems pages = new FragmentPagerItems(this);
pages.add(FragmentPagerItem.of("Test1", SampleFragment.class));
pages.add(FragmentPagerItem.of("Test2", SampleFragment.class));
FragmentStatePagerItemAdapter adapter = new FragmentStatePagerItemAdapter(
getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(0);
viewPagerTab.setViewPager(viewPager);
}
#Override
public View createTabView(ViewGroup container, int position, PagerAdapter adapter) {
LayoutInflater inflater = LayoutInflater.from(container.getContext());
View tab = inflater.inflate(R.layout.custom_tab_icon_and_notification_mark, container, false);
TextView txtTab=(TextView)tab.findViewById(R.id.txtTitle);
switch (position){
case 0:txtTab.setText("Test1");break;
case 1:txtTab.setText("Test2");break;
default:throw new IllegalStateException("Invalid pos - "+ position);
}
return tab;
}
My SampleFragment.java is as below wherein I do some server request for data and update the listview adapter.
SampleFragment.java
List<Items> lstItems=new ArrayList<>();
static ItemListAdapter mAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view= inflater.inflate(R.layout.fragment_sample, container, false);
ListView lstviewItems = (ListView) view.findViewById(R.id.lstItems);
int position = FragmentPagerItem.getPosition(getArguments());
View emptyView=view.findViewById(R.id.emptyList);
lstviewItems.setEmptyView(emptyView);
mAdapter = new ItemsListAdapter(getActivity(),lstItems);
lstviewItems.setAdapter(mAdapter);
switch (position){
case 0:
//JsonObjectRequest
loadItems();
break;
case 1:
//sort the loaded items
break;
}
return view;
}
private void loadItems(){
try {
JsonArrayRequest request = new JsonArrayRequest(Request.Method.GET,url, null,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
lstItems.clear();
for (int i = 0; i < response.length(); i++) {
//Add item to lstItems
}
mAdapter.notifyDataSetChanged();
}
}
});
testApp.getInstance().addToRequestQueue(request);
} catch (Exception ex) {
ex.printStackTrace();
}
}
But even after mAdapter.notifyDataSetChanged() the listview in the current viewPager tab i.e. Test1 tab is not getting refreshed. Whereas when I navigate to Test2 tab, I can see the changes in the listview, where is data has been loaded properly. Below is the screenshot for 2 different tabs.
I've also searched for this problem and found other solution which did not work for me. One of the solution being, adding a refresh method in ItemsAdapter as below:
public void refresh(List<Items> items)
{
this.items = items;
notifyDataSetChanged();
}
and instead of mAdapter.notifyDataSetChanged() I used mAdapter.refresh(lstItems);. But unfortunately it did not work either. How can I possibly overcome this. Please let me know if I have to add furthermore details on this.
I think your problem is here:
mAdapter = new ItemsListAdapter(getActivity(),lstItems);
You create a new instance of ItemsListAdapter and bind it to the listview with
lstviewItems.setAdapter(mAdapter);
The problem is that this adapter is static. So if you create a new instance you destroy the old adapter and the listview of the other tab has not the adapter anymore that updates your data.
EDIT:
I'd recommend to load the data on a central place. Add the response (your data objects) to a manager class. Then implement on every view which using this data a callback (lets say JsonDataChangedCallback). Register the classes which implementing the callback to the manager with Manager.getInstance().registerCallback(). Then every time your data is changed call updateCallbacks() in your manager and all views will be updated. That's the way implemented that process in my app and it works.
Sample Code:
public class CDMMovieManager {
private CDMMovieManager() {
m_Movies = new ArrayList<>();
m_MovieChangedCallbacks = new ArrayList<>();
}
public static synchronized CDMMovieManager getInstance() {
if(ms_Instance == null)
ms_Instance = new CDMMovieManager();
return ms_Instance;
}
public void addMovie(CDMMovie p_Movie) {
m_Movies.add(p_Movie);
notifyCallbacks();
}
/**
* Registers a movie changed callback
*
* #param p_MovieChangedCallback the callback to register
*/
public void registerMovieChangedCallback(IDMMovieChangedCallback p_MovieChangedCallback) {
m_MovieChangedCallbacks.add(p_MovieChangedCallback);
}
/**
* Removes a movie changed callback
*
* #param p_MovieChangedCallback the callback to remove
*/
public void removeMovieChangedCallback(IDMMovieChangedCallback p_MovieChangedCallback) {
m_MovieChangedCallbacks.remove(p_MovieChangedCallback);
}
private void notifyCallbacks() {
for(IDMMovieChangedCallback l_MovieChangedCallback : m_MovieChangedCallbacks) {
l_MovieChangedCallback.onMoviesChanged();
}
}
}
And the implementing Class:
public class CDMBasicMovieFragment extends Fragment implements IDMMovieChangedCallback {
//...
#Override
public void onMoviesChanged() {
m_Adapter.notifyDataSetChanged();
}
}
I have a fragment with a viewpager with adapter extending FragmentStatePagerAdapter with three slidable fragments inside. the main fragment has a static list of data, and three sub-fragments references and show the same list (don't ask why). The list item can be removed by a button inside the row layout or by clicking clear-all button after the list in each of those three fragments.
My problem is that after removing one or all the items with the buttons, sometimes i get an instant index out of bounds exception (no application code in stack trace, to find where the exception is coming from) or randomly sometimes removal work, but fragments nearby display old data with the extra item, and clicking to remove it ofcourse throws out of bounds exception, because after some debugging I can see that size of list passed to adapter to recreate the nearby fragment is lower by one (removal is successful) so I believe the list is not notified/invalidated correctly. Any help, since stacktrace can't help?
Btw I'm using FragmentStatePagerAdapter.POSITION_NONE to recreate the fragments with new data on each swipe, notifyDataSetChanged to notify the adapter of changed data, and invalidate() the listview in onViewCreated(), but they don't help.
relevant code:
Main Fragment
public class MainFragment extends BaseFragment<CategoryTreeItem> {
public static List<Map.Entry<EventTreeItem, String>> betList = new ArrayList<>();
...
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ViewPager mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
mViewPager.setAdapter(new MyPagerAdapter(getChildFragmentManager()));
SlidingTabLayout mSlidingTabLayout = (SlidingTabLayout) view.findViewById(R.id.sliding_tabs);
mSlidingTabLayout.setViewPager(mViewPager);
}
}
class MyPagerAdapter extends FragmentStatePagerAdapter {
private List<String> items;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
items = getResources().getStringArray(R.array.three_categories);
}
#Override
public Fragment getItem(int i) {
Fragment fragment;
switch (i) {
case 0:
fragment = new FirstFragment();
return fragment;
case 1:
fragment = new SecondFragment();
return fragment;
case 2:
fragment = new ThirdFragment();
return fragment;
default:
return null;
}
}
...
#Override
public int getItemPosition(Object object) {
return FragmentStatePagerAdapter.POSITION_NONE;
}
}
Three fragments (they are needed since the layout and functionality is a bit different)
public class First/Second/ThirdFragment extends BaseListFragment<ArrayList<EventTreeItem>> {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
MyListAdapter adapter = new MyListdapter(getActivity(), R.layout.item_first/second/thirdfragment, MainFragment.betList);
getListView().setItemsCanFocus(true);
View footerView = getLayoutInflater(savedInstanceState).inflate(R.layout.include_first/second/third_footer, getListView(), false);
getListView().addFooterView(footerView);
adapter.notifyDataSetChanged();
setListAdapter(adapter);
getListView().invalidate();
//handles REMOVE ALL button for all fragments footer button
Helper.setUpClearOption(footerView, adapter);
}
}
MyListAdapter
#CompileStatic
public class MyListAdapter extends ArrayAdapter<Map.Entry<EventTreeItem,String>> {
private int mResourceId;
private List<Map.Entry<EventTreeItem,String>> mObjects;
public MyListAdapter(Context context, int resource, List<Map.Entry<EventTreeItem,String>> objects) {
super(context, resource, objects);
mResourceId = resource;
mObjects = objects;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(mResourceId, parent, false);
}
//------setting a lot of text for textViews
// .....
//------
ImageView ivRemove = (ImageView) view.findViewById(R.id.ivRemove);
ivRemove.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Tried removing directly from adapter, not helping
// MyListAdapter.this.remove(mObjects.get(position));
MainFragment.betList.remove(position);
MyListAdapter.this.notifyDataSetChanged();
}
});
return view;
}
}
Remove All helper method (also returning instant OOB or not notifying neighbor fragments)
public static void setUpClearOption(View view, final ArrayAdapter adapter) {
ImageView ivRemoveAll = (ImageView) view.findViewById(R.id.ivRemoveAll);
ivRemoveAll.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
adapter.clear();
adapter.notifyDataSetChanged();
}
});
}
So any help is much appreciated!
I managed to fix My problem by creating three listadapters for each of my fragments (static adapters instantiaiting with null value ) and in onClick I check each of those three adapters if they are not null (since adapters are null until after onViewCreated, and it is called only when the current fragment is a neighbor). All three adapters use the same data.
I am building an android application containing 4 tabs, each containing a fragment attached to cursor adapter. Each fragment contains a list of items.If I scrolled a lot down in any list, I have to scroll a lot back to get to the top.I want the it to behave like this: when I click on the current tab icon, it will automatically scroll back to top. How can I implement this functionality?
edit 1:
this is how the it is implemented:-
public class OutboxFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
OutboxListAdapter mAdapter;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new OutboxListAdapter(getActivity(),R.layout.outbox_list_item, null, 0);
setListAdapter(mAdapter);
getLoaderManager().initLoader(0, null, this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_outbox, container, false);
return view;
}
On tab changed you can refresh your list view:
MyAdapter myAdapter = new MyAdapter(mContext, items);
mListView.setAdapter(myAdapter);
mListView.setSelection(0);
Instead of setting a new adapter (which causes ListView to reset its state), simply update the content of the adapter already set on the ListView.
You can create a refill() method to your adapter:
public void refill(List<EventLog> events) {
mEvents.clear();
mEvents.addAll(events);
notifyDataSetChanged();
}
And call it onTabChanged:
if (mListView.getAdapter() == null) {
MyAdapter myAdapter = new MyAdapter(mContext, items);
mListView.setAdapter(myAdapter);
} else {
((MyAdapter)mListView.getAdapter()).refill(items);
}
based on your problem you need to scroll the fragment list to the top. you can do this by calling following function:
getListView().smoothScrollToPosition(0);
which getlistView returns your fragment ListView;
I developed an app which fills a list. It works fine in the way I did it but I'm not conviced that I solved the problem in a recommended way. I read that you should override onActivityCreated in a Fragment and fill the list there instead of doing this in onCreateView. onCreateView should only be used to inflate static views. Is this true? If yes, how should these two methods look like in the end?
This is my Fragment class:
public class FragmentMain extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
List<MyItem> items = createListItems();
ListView listView = (ListView) view.findViewById(R.id.list);
MyListAdapter adapter = new MyListAdapter(view.getContext(), items);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(view.getContext(),
"Clicked " + position, Toast.LENGTH_LONG)
.show();
}
});
return view;
}
.
.
.
}
My MainActivity just adds the fragment:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentMain fm = new FragmentMain();
getFragmentManager().beginTransaction()
.add(R.id.fragment_main_container, fm).commit();
}
.
.
.
}
That is true to a certain extend only because onCreateView happens on the UI thread and you don't want anything slowing that down otherwise your UI will be slow and choppy. For example, in your fragment class you have a call to a method "createListItems()". I don't know how many items you're making but if it's a lot it could slow down your UI (especially if youre accessing a database and querying objects and so on). So you could do it in onActivityCreated but you could also use an AsyncTask. So your code would become something like this:
public class LoadListObjectsTask extend AsyncTask<Void, List<MyItem>, Void> {
private MyListAdapter myListAdapter;
private Context mContext;
public LoadListObjectsTask(Context context) {
this.mContext = context;
}
#Override
public void doInBackground(Void...params) {
//create your list objects here instead of on UI thread. This will run on a separate thread.
myListAdapter = new MyListAdapter(mContext, items);
return items; //return list of MyItems
}
//This is called when doInBackground is done. THIS WILL RUN ON THE UI THREAD So don't do
//anything slow here
#Override
public void onPostExecute(List<MyItem>...params //don't really need the list here//) {
listView.setAdapter(myListAdapter);
}
}
then in your fragment
public class FragmentMain extends Fragment {
private ListView listView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
List<MyItem> items = new ArrayList<MyItem>();
listView = (ListView) view.findViewById(R.id.list);
//new code
new LoadListObjectsTask(getActivity()).execute();
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(view.getContext(),
"Clicked " + position, Toast.LENGTH_LONG)
.show();
}
});
return view;
}
public void onResume()... {
//also add the task here so your content is reloaded on resume..
new LoadListObjectsTask(getActivity()).execute();
}
.
.
.
}
If you don't want to do this just make your List of MyItems a private field and move
List<MyItem> items = createListItems();
to onActivityCreated().
Hope that helps!
I have a simple MyListFragment fragment which shows one simple array of data on the screen.
public class MyListFragment extends ListFragment {
private ArrayAdapter<String> adapter;
public ArrayAdapter<String> getAdapter() {
return adapter;
}
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String[] values = new String[] {"data", "data", "data"};
adapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, values);
setListAdapter(adapter);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
}
I wonder how can I change this data dynamically from my Activity? Please tell me if there is a simple way how to do this! I've tried but have failed...
UPD: I added a function changeContent to MyListFragment:
public void changeContent(String[] value) {
adapter.addAll(value);
adapter.notifyDataSetChanged();
}
}
As you see, adapter is a global variable and it's initialized inside onActivityCreated, but my program crashes because inside changeContent in equals null! I can't get why!
You could write a function that changes your values variable and then once you have filled it with your new data just call
adapter.notifyDataSetChanged();
to fill your list with the new data.