Listview does not refresh when underlying Loader data changes - android

First, I'll preface my question with the fact that I'm not using a CursorLoader.
I'm pulling in data from a SQLlite database to populate a listview in a ListFragment. The initial load works well, but once the data is manipulated (i.e. an addition is made to the list), the listview NEVER refreshes to show the new data. I am implementing the Loader callbacks like so:
public class BillListingFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<Bill>> {
private billListAdapter mAdapter;
private static final int LOADER_ID = 1;
private SQLiteDatabase mDatabase;
private BillsDataSource mDataSource;
private BillsStoreDatabaseHelper mDbHelper;
/**
* The fragment argument representing the fragment type (archive or outstanding)
*/
private static final String ARG_FRAGMENT_TYPE = "fragment_type";
/**
* Returns a new instance of this fragment based on type
*/
public static BillListingFragment newInstance(String type) {
// TODO: Make the fragment type an enum
BillListingFragment fragment = new BillListingFragment();
Bundle args = new Bundle();
args.putString(ARG_FRAGMENT_TYPE, type);
fragment.setArguments(args);
return fragment;
}
public BillListingFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.bill_view_layout, container, false);
return rootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mDbHelper = new BillsStoreDatabaseHelper(getActivity());
mDatabase = mDbHelper.getWritableDatabase();
mDataSource = new BillsDataSource(mDatabase);
mAdapter = new billListAdapter(getActivity(), R.layout.bill_row_layout);
setListAdapter(mAdapter);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public Loader<List<Bill>> onCreateLoader(int id, Bundle args) {
BillDataLoader loader = new BillDataLoader(getActivity(), mDataSource);
return loader;
}
#Override
public void onLoadFinished(Loader<List<Bill>> loader, List<Bill> data) {
for(Bill bill: data){
mAdapter.add(bill);
}
setListAdapter(mAdapter);
}
#Override
public void onLoaderReset(Loader<List<Bill>> loader) {
mAdapter.clear();
}
#Override
public void onDestroy() {
super.onDestroy();
mDbHelper.close();
mDatabase.close();
mDataSource = null;
mDbHelper = null;
mDatabase = null;
}
public void reload(){
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
private class billListAdapter extends ArrayAdapter<Bill> {
Context context;
public billListAdapter(Context context, int resourceID){
super(context, resourceID);
this.context = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getActivity().getLayoutInflater().inflate(R.layout.bill_row_layout, parent, false);
}
TextView payToField = (TextView) convertView.findViewById(R.id.nameField);
TextView dueDateField = (TextView) convertView.findViewById(R.id.overdueField);
payToField.setText(getItem(position).getPayTo());
// calculate days until due
Bill bill = getItem(position);
// TODO: Add how many days until bill in overdue field + add color
JodaTimeAndroid.init(getActivity());
DateTime dueDateDt = new DateTime(bill.getDateDue());
DateTime currentDt = new DateTime();
int daysDifference = Days.daysBetween(currentDt.toLocalDate(), dueDateDt.toLocalDate()).getDays();
// depending on what that differential looks like set text / color
if (daysDifference > 1) {
dueDateField.setText(Integer.toString(daysDifference) + " Days");
} else {
if (daysDifference == 0) {
dueDateField.setText("DUE TODAY");
} else {
if (daysDifference < 0) {
}
}
}
return convertView;
}
}
}
I have debugged my code so I know that the onLoadFinished callback is being made after the data has been manipulated. I also know that adapter contains the updated data at this point. I have tried resetting the adapter via setListAdapter(mAdatper) and every notifyDataChanged-like method I can find, but to no avail. What is going on here and how can I get the listview to update?

Related

How to run SQLite query asynchronously

I'm trying to get all the contacts from my SQLite database.
Everything is working fine, I just want to make it asynchronous and not run in the main thread, to not influence the UI.
public List<contacts> getAllcontacts() {
List<contacts> contactsl = new LinkedList<contacts>();
String query = "SELECT * FROM contacts WHERE show is not 'NOTSIGNEDUP'"
+" ORDER BY name COLLATE NOCASE;";
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(query, null);
contacts contact = null;
if (cursor.moveToFirst()) {
do {
contact = new contacts();
contact.setName(cursor.getString(1));
contact.setNumero(cursor.getString(3));
contact.setProfil(cursor.getString(2));
contact.setShow(cursor.getString(5));
contact.setBlocked(cursor.getString(4));
contact.setObjectid(cursor.getString(6));
contactsl.add(contact);
} while (cursor.moveToNext());
}
return contactsl;
}
I'm calling this function from my activity :
final sql s = sql.getInstance(getContext());
if (ContactsList != null) {
ContactsList.clear();
ContactsList.addAll(list);
ContactsList.addAll(s.getAllcontacts_());
cAdapter.notifyDataSetChanged();
}
Is there any way to make s.getAllcontacts() runs asyn
I made my Fragment like this :
public class ContactsFragment extends Fragment implements LoaderManager.LoaderCallbacks<List<contacts>> {
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private ContactsAdapter cAdapter;
private List<contacts> ContactsList;
public ContactsFragment() {
// Required empty public constructor
}
public void set(List<contacts> list) {
final sql s = sql.getInstance(getContext());
if (ContactsList != null) {
ContactsList.clear();
ContactsList.addAll(list);
ContactsList.addAll(s.getAllcontacts_());
cAdapter.notifyDataSetChanged();
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_blank, container, false);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
View view = getView();
if(view != null) {
mRecyclerView = (RecyclerView) view.findViewById(R.id.contacts_recycler);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(view.getContext());
mRecyclerView.setLayoutManager(mLayoutManager);
final sql s = sql.getInstance(view.getContext());
ContactsList = new ArrayList<contacts>();
cAdapter = new ContactsAdapter(ContactsList, mRecyclerView);
mRecyclerView.setAdapter(cAdapter);
getLoaderManager().initLoader(0, null, this);
}
}
#Override
public android.support.v4.content.Loader<List<contacts>> onCreateLoader(int id, Bundle args) {
return new AppListLoader(this.getContext());
}
#Override
public void onLoadFinished(android.support.v4.content.Loader<List<contacts>> loader, List<contacts> data) {
ContactsList.addAll(data);
cAdapter.notifyDataSetChanged();
}
#Override
public void onLoaderReset(android.support.v4.content.Loader<List<contacts>> loader) {
}
public static class AppListLoader extends AsyncTaskLoader<List<contacts>> {
final sql s = sql.getInstance(getContext());
public AppListLoader(Context context) {
super(context);
}
#Override
public List<contacts> loadInBackground() {
return s.getAllcontacts();
}
}
}
in addition to what #CommonsWare suggests, you could also use give to the AsyncTaskLoader a try. You could define
public static class AppListLoader extends AsyncTaskLoader<List<Contact>> {
and move your querying logic in loadInBackground().
Your Activity/Fragment will make then use of the LoaderManager. It will implement LoaderManager.LoaderCallbacks<List<Contact>> and onCreateLoader will return a new instance of your AsyncTaskLoader. The List<Contact> will be delivered as part of onLoadFinished

ListView in ListFragment keeps accumulating data

I am unable to refresh my ListView in ListFragment with new data. Instead the new data is added to the previous.
The time period is from 6AM to 5PM for each entity. Then new data is appended to the list restarting at 6AM for another entity. The data for the first entity should be cleaned before the second is added to the ListView.
Here is the code:
public class FragmentStatePagerSupport extends FragmentActivity {
static final int NUM_ITEMS = 4; //control number of fragments
MyAdapter mAdapter;
ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_main);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(null);
mPager.setAdapter(mAdapter);
}
//===============================================================================================
public static class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
fragNumber = position;
return ArrayListFragment.newInstance(position);
}
}
//===============================================================================================
public static class ArrayListFragment extends ListFragment {
Integer mNum;
String FORMAT_LINE = "%s%7s%7s%10s%16s";
static ArrayListFragment newInstance(int num) {
ArrayListFragment f = new ArrayListFragment();
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNum = getArguments() != null ? getArguments().getInt("num") : 1;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_pager_list, container, false);
View tv = v.findViewById(R.id.text);
View tvd = v.findViewById(R.id.tv_description);
String title = "";
switch (mNum){
case 0:title = MyGlobals.getInstance().getToday();break;
case 1:title = MyGlobals.getInstance().getTomorrow();break;
case 2:title = MyGlobals.getInstance().getDayAfter();break;
case 3:title = MyGlobals.getInstance().getDayDayAfter();break;
}
((TextView) tv).setText(MyGlobals.getInstance().getName() + " on " + title);
((TextView) tvd).setText(String.format(FORMAT_LINE, "time", "temp", "rain", "wind", "weather"));
return v;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ArrayList<Data> row = Data.getRows(mNum);
ListViewAdapter adapter = new ListViewAdapter(getActivity(), row);
getListView().setAdapter(null);
getListView().setAdapter(adapter);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
v.setBackgroundColor(getResources().getColor(R.color.blue));
}
}
//==============================================================================================
public static class ListViewAdapter extends ArrayAdapter<Data> {
public static class ViewHolder{
TextView time;
TextView temp;
TextView rain;
TextView wind_speed;
TextView weather;
}
public ListViewAdapter(Context context, ArrayList<Data> list) {super(context, R.layout.text_listview, list); }
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Data data = getItem(position);
ViewHolder holder;
if(convertView == null){
holder=new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView=inflater.inflate(R.layout.text_listview,parent,false);
holder.time=(TextView) convertView.findViewById(R.id.time);
holder.temp=(TextView) convertView.findViewById(R.id.temp);
holder.rain=(TextView) convertView.findViewById(R.id.rain);
holder.wind_speed=(TextView) convertView.findViewById(R.id.wind);
holder.weather=(TextView) convertView.findViewById(R.id.weather);
convertView.setTag(holder);
}else{
holder=(ViewHolder) convertView.getTag();
}
holder.time.setText(data.time);
holder.temp.setText(data.temp);
holder.rain.setText(data.rain);
holder.wind_speed.setText(data.wind_speed);
holder.weather.setText(data.weather);
return convertView;
}
}
}
This last piece populates the ListView. The calls adapter.clear() and adapter.notifyDataSetChanged() does not resolve the problem.
There is a similar question from 3 years ago How update ListView in ListFragment from FragmentActivity? still without accepted response despite the 6836 views.
Thanks a Lot.
The GetRows(mNum) piece as requested:
public class Data {
public String time;
public String temp;
public String rain;
public String wind_speed;
public String weather;
public Data(String time, String temp, String rain, String wind_speed, String weather) {
this.time = time;
this.temp = temp;
this.rain = rain;
this.wind_speed = wind_speed;
this.weather = weather;
}
public static ArrayList<Data> getRows(int fragNumber) {
int mNum = fragNumber;
int size;
String myList[] = null;
ArrayList<Data> list = new ArrayList<Data>();
switch (mNum) {
case 0://Today
size = MyGlobals.getInstance().getToday("time").size();
myList = new String[size];
for (int i = 0; i < size; i++) {
String time = (String) MyGlobals.getInstance().getToday("time").get(i);
String temp = (String) MyGlobals.getInstance().getToday("temperature").get(i);
String wind_speed = (String) MyGlobals.getInstance().getToday("wind_speed").get(i);
String pop = (String) MyGlobals.getInstance().getToday("pop").get(i);
//String wind_gust = (String) MyGlobals.getInstance().getToday("wind_gust").get(i); //when null breaks code
String weather = (String) MyGlobals.getInstance().getToday("weather").get(i);
if (time.length() == 4) time = "0" + time;
if (wind_speed.length() == 1) wind_speed = "0" + wind_speed;
if (pop.length() == 1) pop = "0" + pop;
list.add(new Data(time,temp,pop,wind_speed,weather));
}
return list;
case 1://Tomorrow
...snip... same as above with pertinent variables,...
case 2://DayAfter
...snip...
case 3://DayDayAfter
...snip....
case 4:// is an error
Log.d("***error***", "list got to case 5");
}
return list;
}
}
Try this;
Before adding new list of Data to the existing list, use list.clear();
Add new Data to the list and you should have only newly added data in the list..
pass the list to your adapter class.
Notify adapter of the new data i.e. adapter.notifyDataSetChanged()
You are returning list twice in your getRows()
remove the "return list" before the case structure
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
// Clear old list here before adding new one.
if (list! = null)
{
list.clear();
}
// Add New list
list = Data.getRows(mNum);
ListViewAdapter adapter = new
ListViewAdapter(getActivity(), list);
getListView().setAdapter(adapter);
adapter.notifyDataSetChange();
}
It's because everytime you set an Adapter, it will really be added., remove all its data first before setting again so it will be like
mPager.setAdapter(null);
before doing
mPager.setAdapter(mAdapter);

Problems populating a fragment list

I have a SherlockFragmentActivity class that collects values from a server and loads it in to my database. This SherlockFragmentActivity as 3 Fragment called the Book, Video and Audios. Each of them are meant to show values that were downloaded into the db. By challenge now is when I open my UI i dont get to see the values on the fragments not until I start clicking each fragment before the values get populated into the list in the fragment. And I even notice a continuous addition of this values. My fragment class is pasted below.
public class BooksFragment extends SherlockListFragment{
TextView textview = null;
String CategoryID = null;
ArrayList<HashMap<String,String>> listBooks = null;
IDatabaseHelper databaseHelper = null;
Activity activity = null;
Context context = null;
ListAdapter adapter = null;
public BooksFragment(){
super();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.books, container, false);
// do your view initialization heres
textview = (TextView)view.findViewById(R.id.textView1);
return view;
}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
listBooks = new ArrayList<HashMap<String,String>>();
}
#Override
public void onStart() {
super.onStart();
Bundle bundle =this.getArguments();
if(bundle != null){
CategoryID = bundle.getString("CategoryID");
}
this.initializeComponents();
this.populateListView();
}
#Override
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
activity = getActivity();
context = activity.getBaseContext();
databaseHelper= new DatabaseHelper(context);
}
//Now we are going to initialize components of the fragment
private void initializeComponents(){
ListView listview = getListView();
listview.setOnItemClickListener(listener);
}
//list item click listener
private OnItemClickListener listener = new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
}
};
//This method would be used to collect content from the database and populate the listview item
private void populateListView(){
MedicalBookModel[] booksmodel = this.databaseHelper.ReturnBooks(CategoryID);
if(booksmodel != null){
for(MedicalBookModel book : booksmodel){
HashMap<String,String> bookMap = new HashMap<String,String>();
bookMap.put(MedicalBookModel.MedicalBookModel_ID, book.getID());
bookMap.put(MedicalBookModel.MedicalBookModel_Name,book.getName());
Log.i("values",book.getName());
listBooks.add(bookMap);
}
}
adapter = new SimpleAdapter(context, listBooks,R.layout.list_book,new String[]{ "ID","Name"}, new int[]{ R.id.bookId, R.id.bookName});
setListAdapter(adapter);
}
}
For that you have several solutions :
1- Using the Application instance singleton which is global
2- Creating your own global class to manage your data
3- Use a service bound to the activity (or not) and call backs (maybe intent and broadcast receivers)
4- Pass your object as parceable in argument when adding the fragment
Note that sometimes you will need to invalidate views to force datas to refresh
EXEMPLE OF PARCEABLE OBJECT
public class ImageObject implements Parcelable {
/**
* ATTRIBUTES
*/
protected String _idPicture;
protected String _idAlbum;
protected String _name;
protected String _fileName;
protected String _imageUrl;
protected String _hierarchy;
public ImageObject(String _idPicture, String _idAlbum, String _name, String _fileName, String _imageUrl, String _hierarchy) {
super();
this._idPicture = _idPicture;
this._idAlbum = _idAlbum;
this._name = _name;
this._fileName = _fileName;
this._imageUrl = _imageUrl;
this._hierarchy = _hierarchy;
}
public ImageObject(Parcel in) {
String[] data = new String[6];
in.readStringArray(data);
this._idPicture = data[0];
this._idAlbum = data[1];
this._name = data[2];
this._fileName = data[3];
this._imageUrl = data[4];
this._hierarchy = data[5];
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public ImageObject createFromParcel(Parcel in) {
return new ImageObject(in);
}
public ImageObject[] newArray(int size) {
return new ImageObject[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(new String[] { this._idPicture, this._idAlbum, this._name, this._fileName, this._imageUrl, this._hierarchy });
}
}

Accessing a Loader created in one fragment from another fragment

I have an app with a fairly standard fragment layout. An expandable listview fragment on the left and a panel on the right that is used for different things depending on what the user chooses to do with the list on the left (displaying data, adding new data, etc).
I'm using the LoaderManager (first time using loaders) with CommonWare's loaderex library as I have no need or desire to create a Content Provider for my database just so I can use a standard CursorLoader. This setup works great for displaying my list.
The issue I am having is when I use the second fragment to add data to the database. I cannot figure out how to trigger a re-load of the list in the first fragment. For the life of me I cannot figure out how to grab the loader from the first fragment in the second so that it will be aware that the data needs to be pulled again, nor can I seem to figure how to manually trigger a re-load.
As this is my first attempt at using Loaders, if I'm doing something improperly I'd be happy to be (gently) re-directed down a better path.
Fragment 1
public class StudentListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
private TAOpenHelper mDbHelper = null;
private MyExpandableListAdapter mAdapter = null;
private ExpandableListView lv = null;
private Button addStudentButton;
public static long mRowId = 0;
public SQLiteCursorLoader studentLoader=null;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.leftlistfragment_entry, container,
false);
return v;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
addStudentButton = (Button) getActivity().findViewById(R.id.AddButton);
addStudentButton.setText(getResources().getString(
R.string.button_add_student));
addStudentButton.setOnClickListener(addStudentButtonHandler);
lv = (ExpandableListView) getListView();
mDbHelper = TAOpenHelper.getInstance(getActivity());
fillData();
getLoaderManager().getLoader(-1);
if (studentLoader != null && !studentLoader.isReset()) {
getLoaderManager().restartLoader(-1, null, this);
} else {
getLoaderManager().initLoader(-1, null, this);
}
}
private void fillData() {
mAdapter = new MyExpandableListAdapter(getActivity(), this,
R.layout.listlayout_exp_double_group,
R.layout.listlayout_exp_double_child,
new String[] { TeacherAidDB.STUDENT_FIRST,
TeacherAidDB.STUDENT_LAST }, new int[] {
R.id.ListItem1, R.id.ListItem2 }, new String[] {
TeacherAidDB.CLASS_NAME, TeacherAidDB.CLASS_LEVEL },
new int[] { R.id.ListItem1, R.id.ListItem2 });
lv.setAdapter(mAdapter);
}
public class MyExpandableListAdapter extends SimpleCursorTreeAdapter {
protected final SparseIntArray mGroupMap;
private StudentListFragment mFragment;
public MyExpandableListAdapter(Context context,
StudentListFragment clf, int groupLayout, int childLayout,
String[] groupFrom, int[] groupTo, String[] childrenFrom,
int[] childrenTo) {
super(context, null, groupLayout, groupFrom, groupTo, childLayout,
childrenFrom, childrenTo);
mFragment = clf;
mGroupMap = new SparseIntArray();
}
#Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
int groupPos = groupCursor.getPosition();
int groupId = groupCursor.getInt(groupCursor
.getColumnIndex(TeacherAidDB.CLASS_ROWID));
mGroupMap.put(groupId, groupPos);
Loader<Cursor> loader = getActivity().getLoaderManager().getLoader(
groupId);
if (loader != null && !loader.isReset()) {
getActivity().getLoaderManager().restartLoader(groupId, null,
mFragment);
} else {
getActivity().getLoaderManager().initLoader(groupId, null,
mFragment);
}
return null;
}
public SparseIntArray getGroupMap() {
return mGroupMap;
}
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id != -1) { // Child Cursor
studentLoader = new SQLiteCursorLoader(getActivity(), mDbHelper,
TeacherAidDB.STUDENT_LIST_CLASS_QUERY + id, null);
} else { // Group Cursor
studentLoader = new SQLiteCursorLoader(getActivity(), mDbHelper,
TeacherAidDB.STUDENT_LIST_QUERY, null);
}
return studentLoader;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
int id = loader.getId();
if (id != -1) { // Child cursor
if (!data.isClosed()) {
SparseIntArray groupMap = mAdapter.getGroupMap();
int groupPos = groupMap.get(id);
mAdapter.setChildrenCursor(groupPos, data);
}
} else { // Groups cursor
mAdapter.setGroupCursor(data);
}
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
mAdapter.changeCursor(null);
}
View.OnClickListener addStudentButtonHandler = new View.OnClickListener() {
public void onClick(View v) {
AddPerson personadd = AddPerson.newInstance(AddPerson.STUDENT, AddPerson.CREATE, mRowId);
getFragmentManager().beginTransaction()
.replace(R.id.rightpane, personadd).commit();
}
};
}
Fragment 2
public class AddPerson extends Fragment {
public static int STUDENT = 0;
public static int TEACHER = 1;
public static int CREATE = 0;
public static int EDIT = 1;
private int mRowId;
private TAOpenHelper mDbHelper;
private Cursor personedit;
private Button commit;
private Button cancel;
int who;
int what;
long rowId;
static AddPerson newInstance(int type, int action, long rowid) {
AddPerson f = new AddPerson();
Bundle args = new Bundle();
args.putInt("type", type);
args.putInt("action", action);
args.putLong("rowid", rowid);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
who = getArguments().getInt("type");
what = getArguments().getInt("action");
rowId = getArguments().getInt("rowid");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.dialog_person_add, container, false);
mDbHelper = TAOpenHelper.getInstance(getActivity());
if (what == EDIT) {
if (who == STUDENT) {
// Student Edit stuff here
} else {
// Teacher Edit stuff here
}
} else {
if (who == STUDENT) {
// Student Create stuff here
} else {
// Teacher Create stuff here
}
}
// Code to gather data from user goes here
commit = (Button) v.findViewById(R.id.commitbutton);
commit.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
first = firstTxt.getText().toString();
last = lastTxt.getText().toString();
street = streetTxt.getText().toString();
city = cityTxt.getText().toString();
zip = zipTxt.getText().toString();
phone = phoneTxt.getText().toString();
email = emailTxt.getText().toString();
if (what == CREATE) {
processAdd(who);
} else {
processUpdate(who);
}
}
});
cancel = (Button) v.findViewById(R.id.cancelbutton);
cancel.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Fragment check = getFragmentManager().findFragmentById(
R.id.rightpane);
getFragmentManager().beginTransaction().remove(check).commit();
}
});
return v;
}
private void processAdd(int who) {
ContentValues initialValues = new ContentValues();
if (who == STUDENT) {
initialValues.put(TeacherAidDB.STUDENT_FIRST, first);
initialValues.put(TeacherAidDB.STUDENT_LAST, last);
initialValues.put(TeacherAidDB.STUDENT_STREET, street);
initialValues.put(TeacherAidDB.STUDENT_CITY, city);
initialValues.put(TeacherAidDB.STUDENT_STATE, state);
initialValues.put(TeacherAidDB.STUDENT_ZIP, zip);
initialValues.put(TeacherAidDB.STUDENT_PHONE, phone);
initialValues.put(TeacherAidDB.STUDENT_EMAIL, email);
initialValues.put(TeacherAidDB.STUDENT_BDAY, birthday);
// How to get studentLoader from fragment 1?
//studentLoader.insert(TeacherAidDB.STUDENT_TABLE, null, initialValues);
}
}
}
With a regular CursorLoader, this would happen automagically via the ContentObserver framework, which eventually boils down to a bunch of static data members.
With SQLiteCursorLoader, ContentObserver is not available, with the closest simulacrum being to route your CRUD operations through the Loader so it knows to reload the Cursor. And that is really only designed for use within a single activity.
So, as Luksprog suggested, your best option is to delegate CRUD work to the containing activity.
If these fragments might be hosted by disparate activities (e.g., for small/normal vs. large/xlarge screen sizes), define a common interface for handling this work, and have the fragments delegate to the interface.

ViewPager is trying to recreate a Fragment from bad data

The main problem I am running into is that when I try to update my PagerAdapter with a brand new set of data, I get a FC. More specifically, I get the FC only if I remove something from the PagerAdapter's dataset.
In my Main activity I have this loader callback:
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor newCursor) {
List<CycleItem> cycleItems = CursorInflator.inflateList(newCursor, CycleItem.class);
mCycleAdapter.setCycleList(cycleItems);
mIndicator.notifyDataSetChanged();
}
Here is my CycleViewPagerAdapter (mCycleAdapter is a reference to this):
public class CycleViewPagerAdapter extends FragmentPagerAdapter implements TitleProvider {
private ArrayList<CycleItem> mCycleItems;
private CyclePagerCallbacks mParent;
public CycleViewPagerAdapter(CyclePagerCallbacks parent, FragmentManager fm) {
super(fm);
mParent = parent;
mCycleItems = new ArrayList<CycleItem>();
}
public CycleViewPagerAdapter(FragmentManager fm, List<CycleItem> cycleItems) {
super(fm);
mCycleItems = (ArrayList<CycleItem>) cycleItems;
}
public void setCycleList(List<CycleItem> cycleItems) {
mCycleItems = (ArrayList<CycleItem>) cycleItems;
notifyDataSetChanged();
}
#Override
public Fragment getItem(int position) {
return CycleDetailFragment.newInstance(mParent, mCycleItems.get(position % mCycleItems.size()).getId());
}
public String getTitle(int position) {
return mCycleItems.get(position % mCycleItems.size()).getName();
}
#Override
public int getCount() {
if (mCycleItems != null) {
return mCycleItems.size();
} else {
return 0;
}
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
This CycleViewPagerAdapter creates and returns CycleDetailFragments when the getItem method is called. Here is that class:
public class CycleDetailFragment extends Fragment implements PaginatedFragmentProtocol {
public interface CyclePagerCallbacks {
public void removeCycle(Uri cycleUri);
}
private static final String TAG = "CycleDetailFragment";
private CyclePagerCallbacks mParent;
private long mCycleId;
private Cycle cycle;
private static final String CYCLE_ID_KEY = "cycle_id";
private TextView mCycleName;
private TextView mNumItem;
private Button mDeleteCycle;
public static CycleDetailFragment newInstance(CyclePagerCallbacks parent, long cycleId) {
CycleDetailFragment cycleDetailFragment = new CycleDetailFragment(parent);
Bundle bundle = new Bundle();
bundle.putLong(CYCLE_ID_KEY, cycleId);
cycleDetailFragment.setArguments(bundle);
return cycleDetailFragment;
}
public CycleDetailFragment(CyclePagerCallbacks parent) {
mParent = parent;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
if (arguments != null) {
mCycleId = arguments.getLong(CYCLE_ID_KEY);
} else {
mCycleId = 0;
}
Log.i(TAG, Long.toString(mCycleId));
final Uri cycleUri = ContentUris.withAppendedId(CycleContentProvider.CONTENT_URI, mCycleId);
Cursor cursor = getActivity().getContentResolver().query(cycleUri, null, null, null, null);
final Cycle cycle = CursorInflator.inflateOne(cursor, Cycle.class);
Uri cycleItemsUri = ContentUris.withAppendedId(CycleItemContentProvider.CYCLE_ID_FIELD_CONTENT_URI, mCycleId);
Cursor cycleItemsCursor = getActivity().getContentResolver().query(cycleItemsUri, null, null, null, null);
mCycleName.setText(cycle.getName() + " " + cycle.getId());
mNumItem.setText(Integer.toString(cycleItemsCursor.getCount()));
mDeleteCycle.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
getActivity().getContentResolver().delete(cycleUri, null, null);
mParent.refresh();
}
});
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.i("Creating View", "Cycle ID: " + mCycleId);
View view = inflater.inflate(R.layout.cycle_detail, container, false);
mCycleName = (TextView) view.findViewById(R.id.cycle_name);
mNumItem = (TextView) view.findViewById(R.id.num_items);
mDeleteCycle = (Button) view.findViewById(R.id.delete_cycle);
return view;
}
#Override
public String getFragmentTitle() {
return cycle.getName();
}
}
So you see that the CycleDetailFragment looks in the database for information to populate the view.
Here is the problem I am having. After I DELETE a Cycle from the database, the onLoadFinished method is called and the cycleItems is populated with the correct items, but once mCycleAdapter.setCycleList(cycleItems) is called after this deletion I get my error. The onAcitivityCreated method in the CycleDetailFragment is called (not by me or my PagerAdapter - something else internal), but it is fed data that should no longer exist! The bundle that it is passed contains the cycleId of the item that I just deleted and that does not exist in the freshly pulled cycleItems list that I just created. So, it tries to pull something from the database that no longer exists and null pointer exceptions are thrown.
How can I refresh the dataset of my PagerAdapter without the ViewPager trying to use old data first?
shouldn't you use FragmentStatePagerAdapter here?

Categories

Resources