I have 3 view which are set to display on below the other. But the problem is that this view(the middle one) isn't showing up at all on screen.
I am not exactly sure as to how to send this list to the parent view.
This is the tutorial I'm using : http://tausiq.wordpress.com/2012/12/12/android-custom-adapter-listview-with-listfragment-and-loadermanager-inside-fragmentactivity/
Here is the code :
public class MyActivity extends Fragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view = inflater.inflate(R.layout.my_activity_layout, container, false);
DataListFragment list = new DataListFragment();
return view;
}
public static class DataListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<Model>> {
CustomListViewAdapter mAdapter;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setEmptyText("No Data Here");
mAdapter = new CustomListViewAdapter(getActivity());
setListAdapter(mAdapter);
setListShown(false);
// Prepare the loader.
getLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<List<Model>> onCreateLoader(int arg0, Bundle arg1) {
System.out.println("DataListFragment.onCreateLoader");
return new DataListLoader(getActivity());
}
#Override
public void onLoadFinished(Loader<List<Model>> arg0, List<Model> data) {
mAdapter.setData(data);
System.out.println("DataListFragment.onLoadFinished");
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
#Override
public void onLoaderReset(Loader<List<Model>> arg0) {
mAdapter.setData(null);
}
}
public static class DataListLoader extends AsyncTaskLoader<List<Model>> {
List<Model> mModels;
public DataListLoader(Context context) {
super(context);
}
#Override
public List<Model> loadInBackground() {
System.out.println("DataListLoader.loadInBackground");
List<Model> entries = new ArrayList<Model>(5);
entries.add(new Model("1", "2","3"));
entries.add(new Model("1", "2","3"));
entries.add(new Model("1", "2","3"));
return entries;
}
/**
* Called when there is new data to deliver to the client.
*/
#Override public void deliverResult(List<Model> listOfData) {
if (isReset()) {
// An async query came in while the loader is stopped. We
// don't need the result.
if (listOfData != null) {
onReleaseResources(listOfData);
}
}
List<Model> oldApps = listOfData;
mModels = listOfData;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(listOfData);
}
// At this point we can release the resources associated with
// 'oldApps' if needed; now that the new result is delivered we
// know that it is no longer in use.
if (oldApps != null) {
onReleaseResources(oldApps);
}
}
/**
* Handles a request to start the Loader.
*/
#Override protected void onStartLoading() {
if (mModels != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mModels);
}
if (takeContentChanged() || mModels == null) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
#Override protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to cancel a load.
*/
#Override public void onCanceled(List<Model> apps) {
super.onCanceled(apps);
// At this point we can release the resources associated with 'apps'
// if needed.
onReleaseResources(apps);
}
/**
* Handles a request to completely reset the Loader.
*/
#Override protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
// At this point we can release the resources associated with 'apps'
// if needed.
if (mModels != null) {
onReleaseResources(mModels);
mModels = null;
}
}
/**
* Helper function to take care of releasing resources associated
* with an actively loaded data set.
*/
protected void onReleaseResources(List<Model> apps) {}
}
}
Here is the custom adapter:
public class CustomListViewAdapter extends ArrayAdapter<Model>{
private final LayoutInflater mInflater;
public CustomListViewAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(List<Model> data) {
clear();
if (data != null) {
for (Model appEntry : data) {
add(appEntry);
}
}
}
/**
* Populate new items in the list.
*/
#Override public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.single_item, parent, false);
} else {
view = convertView;
}
Model item = getItem(position);
((TextView)view.findViewById(R.id.tV_1)).setText(item.getA());
((TextView)view.findViewById(R.id.tV_2)).setText(item.getB());
((TextView)view.findViewById(R.id.tV_3)).setText(item.getC());
return view;
}
}
And this is my main activity:
public class MainActivity extends Activity{
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.song_info_fragment, new TopActivity());
fragmentTransaction.add(R.id.playlist_activity_fragment, new MyActivity());
fragmentTransaction.add(R.id.controls_fragment, new BottomActivity());
fragmentTransaction.commit();
}
}
When I run the code, I only see the top and bottom activities. How can I fix this?
Ok, I was finally able to get this working.
I copied the DataListFragment class into a new class file under the same name and removed the static keywork.
Further, I copied the other section called dataListLoader into another file called DataListLoader.java.
And moved the fragment.add() transaction into the mainActivity file. And that did it!
Related
I'm trying to implement a loader which should load some data from a restful api using retrofit. However the content isn't loaded... unless I place a call to workspaceAdapter.notifyDataSetChanged() in my onOptionsItemSelected method. I say place a call because I don't actually have to make the call. Which I find very odd.
The second odd thing about this is that if I change some data on the server and tries to update by click the refresh menu item which should call the same workspaceAdapter.notifyDataSetChanged() nothing happens.
Below is my Activity.
public class WorkspacesActivity extends Activity implements LoaderCallbacks<List<ListviewEntry>> {
private static final String TAG = WorkspacesActivity.class.getSimpleName();
private static final int LOADER_ID = 1;
private RestClient client;
private ProgressBar progressBar;
private WorkspaceAdapter workspaceAdapter;
private ListView listView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_workspaces);
workspaceAdapter = new WorkspaceAdapter(this);
listView = (ListView)findViewById(R.id.listView);
listView.setAdapter(workspaceAdapter);
progressBar = (ProgressBar)findViewById(R.id.workspacesProgressBar);
progressBar.setVisibility(View.VISIBLE);
Bundle extras = getIntent().getExtras();
if(extras == null) {
return;
}
String accessToken = extras.getString(MainActivity.INTENT_ACCESS_TOKEN);
if(accessToken != null) {
client = new RestClient(accessToken);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
switch (item.getItemId())
{
case R.id.action_refresh:
workspaceAdapter.notifyDataSetChanged();
// getLoaderManager().getLoader(LOADER_ID).forceLoad();
return true;
case R.id.action_create_organization:
return true;
case R.id.action_settings:
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public Loader<List<ListviewEntry>> onCreateLoader(int i, Bundle bundle) {
Log.d(TAG, "onCreateLoader");
progressBar.setVisibility(View.VISIBLE);
return new WorkspaceLoader(this, client);
}
#Override
public void onLoadFinished(Loader<List<ListviewEntry>> listLoader, List<ListviewEntry> listviewEntries) {
Log.d(TAG, "onLoadFinished");
progressBar.setVisibility(View.GONE);
workspaceAdapter.setData(listviewEntries);
}
#Override
public void onLoaderReset(Loader<List<ListviewEntry>> listLoader) {
Log.d(TAG, "onLoaderReset");
progressBar.setVisibility(View.VISIBLE);
workspaceAdapter.setData(null);
}
}
My loader.
public class WorkspaceLoader extends AsyncTaskLoader<List<ListviewEntry>> {
private static final String TAG = WorkspaceLoader.class.getSimpleName();
private RestClient client;
public WorkspaceLoader(Context context, RestClient client) {
super(context);
this.client = client;
}
#Override
public void onStartLoading() {
Log.d(TAG, "onStartLoading");
forceLoad();
super.onStartLoading();
}
/**
* Since Organization has (1:m) Workspaces. We need to flatten this structure. A
* List<ListviewEntry> is used as internal data source. So this method request all the
* oganizations associated with a user, extracts organization or workspace id, organization or
* workspace name and stores those with a type indicating weather it's one or the other. This
* list structure can then be passed on to the adapter.
*
* #return listviewEntries
*/
#Override
public List<ListviewEntry> loadInBackground() {
Log.d(TAG, "loadInBackground");
List<Organization> organizations = client.requestOrganizations();
List<ListviewEntry> listviewEntries = new ArrayList<ListviewEntry>();
// Flatten Organizations and Workspaces
for (Organization organization : organizations) {
listviewEntries.add(new ListviewEntry(organization.getOrg_id(), organization.getName(),
ListviewEntry.Type.ORGANIZATION));
for (Workspace workspace : organization.getSpaces()) {
listviewEntries.add(new ListviewEntry(workspace.getSpace_id(), workspace.getName(),
ListviewEntry.Type.WORKSPACE));
}
}
return listviewEntries;
}
#Override
public void deliverResult(List<ListviewEntry> data) {
Log.d(TAG, "deliverResult");
if(isReset())
{
if(data != null) {
releaseResources(data);
return;
}
}
super.deliverResult(data);
}
private void releaseResources(List<ListviewEntry> data) {
Log.d(TAG, "releaseResources");
// For a simple List, there is nothing to do. For something like a Cursor,
// we would close it in this method. All resources associated with the
// Loader should be released here.
}
}
My adapter.
public class WorkspaceAdapter extends BaseAdapter {
private static final String TAG = WorkspaceAdapter.class.getSimpleName();
private List<ListviewEntry> data;
private LayoutInflater layoutInflater;
public WorkspaceAdapter(Context context) {
data = new ArrayList<ListviewEntry>();
layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(List<ListviewEntry> data) {
this.data = data;
}
#Override
public int getCount() {
return data.size();
}
#Override
public ListviewEntry getItem(int position) {
return data.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
switch (getItem(position).getType()) {
case WORKSPACE:
convertView = layoutInflater.inflate(R.layout.listview_workspace, null);
holder.textView = (TextView) convertView.findViewById(R.id.listViewRow);
break;
case ORGANIZATION:
convertView = layoutInflater.inflate(R.layout.listview_organization, null);
holder.textView = (TextView) convertView.findViewById(R.id.listViewHeader);
break;
}
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.textView.setText(data.get(position).getName());
return convertView;
}
public static class ViewHolder {
public TextView textView;
}
}
Anyone got a clue about where I'm going wrong?
I've updated my setData method on my adapter si calls notifyDataSetChanged. Eg.
public void setData(List<ListviewEntry> data)
{
this.data = data;
notifyDataSetChanged();
}
That seems to do the trick. But is that how it should be done?
I have inserted a record using dialog. However when I do a listadapter.notifyDataSetChanged(), nothing happened. Any tips on how to do this? I loaded records from my database then using adapter to load record on the list view.
The Dialog is part of an Activity that extend FragmentActivity Im not sure if this will help.
new AlertDialog.Builder(ItemListActivityMain.this)
.setTitle("Update Status")
.setMessage("Please Enter Plate Number")
.setView(input)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Editable value = input.getText();
Toast.makeText(getApplication(), "Insert Main Car details" + value,
Toast.LENGTH_LONG).show();
DatabaseManager.init(getApplication());
RoadMainTableFile roadMainTableFile = new RoadMainTableFile();
roadMainTableFile.setPlate_Number(value.toString());
DatabaseManager.getInstance().addOrUpdateRoadMainTableFile(roadMainTableFile);
final List<RoadMainTableFile> allRoadMainTableFilesList = DatabaseManager.getInstance().getAllRoadMainTableFilesList();
List<String> plateNumber = new ArrayList<String>();
for (RoadMainTableFile wl : allRoadMainTableFilesList) {
plateNumber.add(wl.getPlate_Number());
}
listadapter = new PlateNumberAdapterMain(ItemListActivityMain.this,
R.layout.snippet_list_row_main, plateNumber);
**Nothing happened and no error encountered.**
listadapter.notifyDataSetChanged();
}
}).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Do nothing.
}
}).show();
My List Adapter:
package com.xxxx.roadexpensereport.Main;
/**
* Created by Exponencially Rich on 6/05/2014.
*/
import android.content.Context;
import android.content.pm.PackageManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filterable;
import android.widget.TextView;
import com.toksis.roadexpensereport.R;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class PlateNumberAdapterMain extends ArrayAdapter<String> implements Filterable {
private List<String> plateNumber = null;
private Context context;
private PackageManager packageManager;
private ArrayList<String> arraylist;
private LayoutInflater inflater;
public PlateNumberAdapterMain(Context context, int textViewResourceId,
List<String> plateNumber) {
super(context,textViewResourceId, plateNumber);
this.context = context;
this.plateNumber = plateNumber;
this.arraylist = new ArrayList<String>();
if(plateNumber.size()> 0) {
this.arraylist.addAll(plateNumber);
}
packageManager = context.getPackageManager();
}
#Override
public int getCount() {
return ((null != plateNumber) ? plateNumber.size() : 0);
}
#Override
public String getItem(int position) {
return ((null != plateNumber) ? plateNumber.get(position) : null);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
// This is needed so that your list view will not load forever when there is no record.
if(rowView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rowView = inflater.inflate(R.layout.snippet_list_row_main, parent, false);
}
String data = plateNumber.get(position);
if (null != data) {
TextView plateNumber = (TextView) rowView.findViewById(R.id.plateNumber);
plateNumber.setText(data);
}
return rowView;
}
// Filter Class
public void filter(String charText) {
charText = charText.toLowerCase(Locale.getDefault());
plateNumber.clear();
if (charText.length() == 0) {
plateNumber.addAll(arraylist);
}
else
{
//*if (wp. .toLowerCase(Locale.getDefault()).contains(charText))*//*
for (String wp : arraylist) {
if (wp.toLowerCase(Locale.getDefault()).contains(charText)) {
plateNumber.add(wp);
}
}
}
notifyDataSetChanged();
}
My ListFragment
public class ItemListFragmentFirstPage extends ListFragment {
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
/**
* The fragment's current callback object, which is notified of list item
* clicks.
*/
private Callbacks mCallbacks = sDummyCallbacks;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
/**
* A callback interface that all activities containing this fragment must
* implement. This mechanism allows activities to be notified of item
* selections.
*/
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onItemSelected(String id, String action);
}
/**
* A dummy implementation of the {#link Callbacks} interface that does
* nothing. Used only when this fragment is not attached to an activity.
*/
private static Callbacks sDummyCallbacks = new Callbacks() {
#Override
public void onItemSelected(String id, String action) {
}
};
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public ItemListFragmentFirstPage() {
}
private List<String> plateNumber;
private int position = 0;
public PlateNumberAdapterMain listadaptor = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Load records to listview.
new LoadRecords().execute();
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
registerForContextMenu(getListView());
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Activities containing this fragment must implement its callbacks.
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}
#Override
public void onDetach() {
super.onDetach();
// Reset the active callbacks interface to the dummy implementation.
mCallbacks = sDummyCallbacks;
}
#Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(Integer.toString(position), " ");
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
registerForContextMenu(getListView());
setHasOptionsMenu(true);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(activateOnItemClick
? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
public boolean onContextItemSelected(MenuItem item) {
if (BuildConfig.DEBUG) {
Log.d("ReminderListFragment", "onContextItemSelected called");
}
switch(item.getItemId()) {
case R.id.menu_delete:
Toast.makeText(getActivity(), "Delete Main Record",
Toast.LENGTH_LONG).show();
return true;
case R.id.menu_record_travel:
Toast.makeText(getActivity(), "Travel Reports",
Toast.LENGTH_LONG).show();
/*Pass Parameter to the second page. Use the Plate number*/
/* Bundle arguments = new Bundle();
arguments.putString(ItemDetailFragmentSecondPage.ARG_ITEM_ID, id);*/
mCallbacks.onItemSelected("0", "Travel");
return true;
case R.id.menu_edit:
Toast.makeText(getActivity(), "Edit MAin Record",
Toast.LENGTH_LONG).show();
return true;
case R.id.menu_generate_report:
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id, "report");
Toast.makeText(getActivity(), "Generate Report",
Toast.LENGTH_LONG).show();
return true;
}
return super.onContextItemSelected(item);
}
/*Long press on the item list*/
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo
menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater mi = getActivity().getMenuInflater();
mi.inflate(R.menu.list_menu_item_longpress, menu);
}
private class LoadRecords extends AsyncTask<Void, Void, Void> {
private ProgressDialog progress = null;
#Override
protected Void doInBackground(Void... params) {
DatabaseManager.init(getActivity());
//Load records.
final List<RoadMainTableFile> allRoadMainTableFilesList = DatabaseManager.getInstance().getAllRoadMainTableFilesList();
plateNumber = new ArrayList<String>();
for (RoadMainTableFile wl : allRoadMainTableFilesList) {
plateNumber.add(wl.getPlate_Number());
}
listadaptor = new PlateNumberAdapterMain(getActivity(),
R.layout.snippet_list_row_main, plateNumber);
/*
*/
/*Sort The list. This has a slow speed*//*
Collections.sort(applist, new ApplicationInfo.DisplayNameComparator(packageManager));
*/
return null;
}
#Override
protected void onCancelled() {
super.onCancelled();
}
#Override
protected void onPostExecute(Void result) {
setListAdapter(listadaptor);
progress.dismiss();
super.onPostExecute(result);
}
#Override
protected void onPreExecute() {
progress = ProgressDialog.show(getActivity(), null,
"Loading List...");
super.onPreExecute();
}
#Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
}
#Override
public void onResume() {
super.onResume();
//plateNumber.clear();
new LoadRecords().execute();
/*plateNumber = Dat.getItems(); //reload the items from database*/
// listadaptor.notifyDataSetChanged();
}
}
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?
I have a ListView in a fragment which I can add to from a BroadcastReceiver. However, when the app is removed from the "recents" panel (swipe the thumbnail of the app away - NOT choosing Force Stop in Settings) the BroadcastReceiver still runs (as it is supposed to do do when an app is removed from recents) but I get a Force Close dialog when it tries to update the ListView.
What I have gathered about what happens when removing an app from recents is that it does not kill the app, it just stops all the activiites. This means that the BroadcastReceivers and Services keep running. This is where my problem lies - I try to update the ListView in an Activity which has been stopped.
EDIT: I think that removing from recents causes onStop() to be called.
Do I need to create a service that update the ListView and keeps the activity running? Will it make any difference?
What I am trying to do is similar to say an SMS app. In an SMS app, a Broadcast is received and the ListView with the messages is updated to show the new message.
EDIT: Added some code
This is the Fragment which contains the ListView:
public class HistoryFragment extends FragmentBase implements OnItemAddedHandler {
ListView lv;
HistoryAdapter simpleAdpt;
int mPosition;
int index;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View histView = inflater.inflate(R.layout.history_fragment, container,
false);
setHasOptionsMenu(true);
ListView lv = (ListView) histView.findViewById(R.id.h_listView);
simpleAdpt = new HistoryAdapter();
lv.setAdapter(simpleAdpt);
return histView;
}
private class HistoryAdapter extends BaseAdapter {
private List<Map<String, Object>> mPlanetsList;
public HistoryAdapter() {
mPlanetsList = DataModel.getInstance().getPlanetList();
}
#Override
public int getCount() {
return mPlanetsList.size();
}
#Override
public Object getItem(int position) {
return mPlanetsList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (null == convertView) {
convertView = LayoutInflater.from(getActivity()).inflate(
R.layout.history_item, null);
Log.i("convertView", "was null");
}
TextView tv_title = (TextView) convertView
.findViewById(R.id.hi_tv_title); // This is part of the layout of each item
HashMap<String, String> itemDataHashMap = (HashMap<String, String>) getItem(position);
tv_title.setText(itemDataHashMap.get("planet"));
return convertView;
}
}
#Override
public void onItemAdded(Object data) {
simpleAdpt.notifyDataSetChanged();
}
#Override
public void onItemRemove(int postion) {
simpleAdpt.notifyDataSetChanged();
}
}
This is the BroadcastReceiver that I am trying to use to add items to the ListView. It is fired using an AlarmManager. This means that there is time for the user to remove the app from the recents panel before the item is added to the ListView:
public class ReminderBroadcastReceiver extends BroadcastReceiver {
// This is declared in the manifest
#Override
public void onReceive(Context context, Intent intent) {
String title = "title";
DataModel.getInstance()
.addItem(title); // Add to History
}
}
In DataModel there is:
public static DataModel getInstance() {
if (null == instance) {
Log.i("getInstance", "null");
instance = new DataModel();
}
return instance;
}
private DataModel() {
initList();
}
private void initList() {
mHistoryList = History.getList();
for (int i = 0; i < mHistoryList.size(); i++) {
mPlanetsList.add(mHistoryList.get(i).createPlanet());
}
}
public void addItem(String title) {
History history = new History();
history.getDataHashMap().put("planet", title);
history.addToHistoryDB(); // This just adds to a Database
mHistoryList.add(0, history); // Help keep the orders the same
mPlanetsList.add(0, history.createPlanet());
if (null != mOnItemAddHandler) {
mOnItemAddHandler.onItemAdded(title);
}
}
If any more code is needed, please say
I'm having problems using AsyncTaskLoader. This is my first attempt populating a ListView from a SQLite database using a loader.
Everything seems ok, when I rotate the screen the data is cached and no query is done again. But when I press the home button and launch my app again, the data is loaded again.
Note: Usuario means User, so I'm populating the ListView with a list of users.
public class Main extends SherlockFragmentActivity
implements LoaderManager.LoaderCallbacks<ArrayList<Usuario>> {
UsuarioAdapter adapter;
ListView listView;
Database db;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
listView = (ListView) findViewById(R.id.lista);
db = new Database(this);
adapter = new UsuarioAdapter(this, new ArrayList<Usuario>());
listView.setAdapter(adapter);
getSupportLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<ArrayList<Usuario>> onCreateLoader(int id, Bundle args) {
return new UsuariosLoader(this, db);
}
#Override
public void onLoadFinished(Loader<ArrayList<Usuario>> loader,
ArrayList<Usuario> usuarios) {
//adapter.notifyDataSetChanged();
listView.setAdapter(new UsuarioAdapter(this, usuarios));
// ((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
}
#Override
public void onLoaderReset(Loader<ArrayList<Usuario>> loader) {
listView.setAdapter(null);
}
}
// THE LOADER
class UsuariosLoader extends AsyncTaskLoader<ArrayList<Usuario>> {
private ArrayList<Usuario> usuarios;
private Database db;
public UsuariosLoader(Context context, Database db) {
super(context);
this.db = db;
}
#Override
protected void onStartLoading() {
if (usuarios != null) {
deliverResult(usuarios); // Use the cache
}
forceLoad();
}
#Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
}
#Override
public ArrayList<Usuario> loadInBackground() {
db.open(); // Query the database
ArrayList<Usuario> usuarios = db.getUsuarios();
db.close();
return usuarios;
}
#Override
public void deliverResult(ArrayList<Usuario> data) {
usuarios = data; // Caching
super.deliverResult(data);
}
#Override
protected void onReset() {
super.onReset();
// Stop the loader if it is currently running
onStopLoading();
// Get rid of our cache if it exists
usuarios = null;
}
#Override
public void onCanceled(ArrayList<Usuario> data) {
// Attempt to cancel the current async load
super.onCanceled(data);
usuarios = null;
}
}
And I think this snippet is not well done. I'm creating a new Adapter instead of updating the data.
#Override
public void onLoadFinished(Loader<ArrayList<Usuario>> loader,
ArrayList<Usuario> usuarios) {
//adapter.notifyDataSetChanged();
listView.setAdapter(new UsuarioAdapter(this, usuarios));
//((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
}
Why adapter.notifyDataSetChanged() does not work?
So, basically, my app does not crash but all my data is reloaded again every time I restart the app.
Edit: This is my Adapter code:
class UsuarioAdapter extends BaseAdapter {
private ArrayList<Usuario> usuarios;
private LayoutInflater inflater;
public UsuarioAdapter(Context context, ArrayList<Usuario> usuarios) {
this.usuarios = usuarios;
this.inflater = LayoutInflater.from(context);
}
#Override
public int getCount() { return usuarios.size(); }
#Override
public Object getItem(int pos) { return usuarios.get(pos); }
#Override
public long getItemId(int pos) { return pos; }
#Override
public View getView(int pos, View convertView, ViewGroup arg) {
LinearLayout itemView;
if (convertView == null) {
itemView = (LinearLayout) inflater.inflate(R.layout.list_item, null);
} else {
itemView = (LinearLayout) convertView;
}
ImageView avatar = (ImageView) itemView.findViewById(R.id.avatar);
TextView nombre = (TextView) itemView.findViewById(R.id.nombre);
TextView edad = (TextView)itemView.findViewById(R.id.edad);
// Set the image ... TODO
nombre.setText(usuarios.get(pos).getNombre());
edad.setText(String.valueOf(usuarios.get(pos).getEdad()));
return itemView;
}
}
The call to notifyDataSetChanged() won't change the data your adapter is using. You need to update the data the adapter has, then call that method.
NotifyDataSetChanged() will only tell the adapter it needs to create it's views, but it does not change the data. You need to handle that yourself.
In your adapter add:
public void setUsuario(List<Usuario> usuarios) {
this.usuarios = usuarios;
}
Then in onLoadFinished() call the new method, then notifyDataSetChanged().
listView.getAdapter().setUsuario(usuarios);
listView.getAdapter().notifiyDataSetChanged();
I've found the solution. The onStartLoading was the guilty:
#Override
protected void onStartLoading() {
if (usuarios != null) {
deliverResult(usuarios); // Use cache
} else {
forceLoad();
}
}
In my original post forceLoad was always called. It must be in the else branch.