ArrayList not populated correctly from AsyncTask - android

I've an app that populates an ArrayList from a webservice. The webservice is called from an AsyncTask. The ArrayList is populated when i test it in the onPostExecute. The ArrayList is defined as an instance variable. Why when AsyncTask finishes is the arrayList populated but when i test the instance variable itself after, it's null. It seems like Async is not setting the values properly.
Once Async has finished i am passing the ArrayList to an arrayAdapter, but my listView is empty.
Tests: in onPostExecute
Log.e(TAG, "array from WS = " + array.size()); // returns 4
tests: in onCreate
Log.e(TAG, "checking to see if array is null " + array.size()); // returns 0
package com.carefreegroup;
public class GetRotaDetails extends NfcBaseActivity implements OnItemClickListener
{
ArrayList<ArrayList<String>> array;
MySimpleArrayAdapter arrayAdapter;
Intent intent;
private static final String TAG = GetRotaDetails.class.getSimpleName();
ListView listView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
array = new ArrayList<ArrayList<String>>();
nfcscannerapplication = (NfcScannerApplication) getApplication();
intent = this.getIntent();
setContentView(R.layout.getrotadetailslayout);
listView = (ListView) findViewById(R.id.getrotadetailslistview);
//set titlebar to carer's name
Cursor cursorCarerName = nfcscannerapplication.loginValidate.queryAllFromCarer();
cursorCarerName.moveToLast();
String carerTitleName = cursorCarerName.getString(cursorCarerName
.getColumnIndex(LoginValidate.C_CARER_NAME));
setTitle(carerTitleName + " is currently logged in");
callID = intent.getStringExtra("callIDExtra");
Log.e(TAG, "callID = " + callID);
String[] params = { callID };
AsyncGetRotaDetails agrd = new AsyncGetRotaDetails();
agrd.execute(params);
Log.e(TAG, "checking to see if array is null " + array.size());
if (arrayAdapter == null){
MySimpleArrayAdapter arrayAdapter = new MySimpleArrayAdapter(this, array);
listView.setAdapter(arrayAdapter);
}
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(this);
}// end of onCreate
#Override
protected void onResume() {
super.onResume();
}
private class AsyncGetRotaDetails extends AsyncTask<String, Void, String> {
ProgressDialog progressDialog;
String rotaDetails = null;
#Override
protected void onPreExecute() {
progressDialog = ProgressDialog
.show(GetRotaDetails.this, "Connecting to Server",
" retrieving rota details...", true);
};
#Override
protected String doInBackground(String... params) {
try {
Log.e(TAG, "inside doInBackground");
rotaDetails = nfcscannerapplication.loginWebservice.getRotaDetail(params[0]);
} catch (Exception e) {
e.printStackTrace();
}
return rotaDetails;
}
#Override
protected void onPostExecute(String xmlResult) {
super.onPostExecute(xmlResult);
if (progressDialog != null)
progressDialog.dismiss();
if (rotaDetails != null) {
RetrieveExtraDetails red = new RetrieveExtraDetails();
array = red.getExtraDetails(xmlResult);
Log.e(TAG, "array from WS = " + array.size());
} else {
AlertDialog alertDialog = new AlertDialog.Builder(
GetRotaDetails.this).create();
alertDialog.setTitle("Signal/Server Test");
alertDialog.setMessage("No Phone Signal or Server Problem");
alertDialog.setButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
onStart();
}
});
alertDialog.show();
}
}// end of postExecute
}//end of Async
private class MySimpleArrayAdapter extends ArrayAdapter<String> {
private final Context context;
private final ArrayList<?> list;
public MySimpleArrayAdapter(Context context, ArrayList<?> list) {
super(context, R.layout.getrotadetailsrow);
Log.e(TAG, "inside adapter constructor");
this.context = context;
this.list = list;
}// end of constructor
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.getrotadetailsrow, parent,
false);
TextView recTypeID = (TextView) rowView
.findViewById(R.id.rectypeid);
TextView recType = (TextView) rowView
.findViewById(R.id.rectype);
TextView name = (TextView) rowView
.findViewById(R.id.name);
TextView relationship = (TextView) rowView
.findViewById(R.id.relationship);
TextView address = (TextView) rowView
.findViewById(R.id.address);
TextView postCode = (TextView) rowView
.findViewById(R.id.postcode);
TextView telNo = (TextView) rowView
.findViewById(R.id.telno);
TextView keySafe = (TextView) rowView
.findViewById(R.id.keysafe);
TextView notes = (TextView) rowView
.findViewById(R.id.notes);
TextView meds = (TextView) rowView
.findViewById(R.id.meds);
String record = list.get(position).toString();
Log.e(TAG, "record = " + record);
recTypeID.setText(record);
return super.getView(position, convertView, parent);
}
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
}
}

Actually, AsyncTask runs asynchronously it is not a blocking call (like execute() method returns, after the execution of onPostExecute() ). So you need to notify adapter in onPostExecute() when your data is downloaded. Its a multi-thread related problem,when execute() line executed a thread is created for AsyncTask and onCreate()'s execution move to next line, so simultaneously, doInBackground() and onCreate() will be executing in AsyncTask thread and UI thread respectively.

Make your ListView a field in the class and
Use
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(this);
in onPostExecute(String result) method
but what i recommend is:
Use a loading spinner until the data is fetched and adapter is set, otherwise the behavior is the Activity shows up as empty and after 3-5 secs suddenly the whole list appears, which is a bad UX.
Usually we use Async Tasks without any loading spinner when the data being fetched is a small part of the UI, not a ListView which is a major component of the UI.
and don't check the length of the Array in onCreate(). It will always be 0. Because the code flow will be :
1. Create Array
2. Check its length in onCreate //0 as nothing is added yet
3. Add data to the Array in background
4. Check its length in onPostExecute(String result) //actual length

Related

Combine Activity and fragment into a class android

I want to ask,
I have 2 classes:
1. an activity class (to get data from json)
2. a fragment class (not to do something)
and I want to get data from json of activity via fragment class.
Can be combine Activity and Fragment in a class ? and how to do ?
I combined activity and fragment in a Fragment class, I have used a GridView to get data and display
JSON, execute the AsyncTask in the this Fragment
This is my code after updated 25/10:
public class FeedBackFragment extends Fragment {
ProgressDialog pDialog;
JSONParser jsonParser = new JSONParser();
MyAdapter adapter;
JSONArray manufacturers = null;
// manufacturers JSON url
private static final String URL_MANUFACTURERS ="MYURL";
public FeedBackFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.feedback_gridview_manufacturer, container, false);
GridView gridView = (GridView) view.findViewById(R.id.gridview);
gridView.setAdapter(new MyAdapter(getActivity(), manufacturersList));
gridView.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View view, int arg2, long arg3) {
// on selecting a single manufacturer
// CategoryCarActivity will be launched to show category car inside the manufacturer
Intent i = new Intent(getActivity(), CategoryCarActivity.class);
// send manufacturer id to activity to get list of cars under that manufacturer
String manufacturer_id = ((TextView) view.findViewById(R.id.manufacturer_id)).getText().toString();
i.putExtra("manufacturer_id", manufacturer_id);
startActivity(i);
}
});
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
// manufacturersList = new ArrayList<>();
new LoadAllManufacturers().execute();
}
class LoadAllManufacturers extends AsyncTask<String, String, String> {
/**
* Before starting background thread Show Progress Dialog
* */
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = new ProgressDialog(getActivity());
pDialog.setIndeterminate(false);
pDialog.setCancelable(false);
pDialog.show();
}
/**
* After completing background task Dismiss the progress dialog
* **/
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
adapter.notifyDataSetChanged();
// dismiss the dialog after getting all manufacturers
if (pDialog.isShowing())
pDialog.dismiss();
}
}
private class MyAdapter extends BaseAdapter
{
// List<POJOManufacturer> listData = null;
LayoutInflater inflater;
Context context;
public MyAdapter(Context context, ArrayList<HashMap<String, String>> arrayList)
{
// this.context = context;
this.manufacturersList = arrayList;
inflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
if (manufacturersList != null)
return manufacturersList.size();
return 0;
}
#Override
public Object getItem(int i)
{
if (manufacturersList != null)
return manufacturersList.get(i);
return null;
}
#Override
public long getItemId(int i)
{
if (manufacturersList != null)
return manufacturersList.get(i).hashCode();
return 0;
}
#Override
public View getView(int i, View convertView, ViewGroup viewGroup)
{
ViewHolder holder;
if (convertView == null)
{
convertView = inflater.inflate(R.layout.gridview_item, null);
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.text);
holder.iconName = (ImageView) convertView.findViewById(R.id.picture);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
holder.name.setText(this.manufacturersList.get(i).getClass().getName());
// holder.iconName.setImageResource(this.manufacturersList.get(i).image);
return convertView;
}
public class ViewHolder
{
TextView name;
ImageView iconName;
}
}
}
I have updated and added: manufacturerList = new ArrayList<>. everything seem is better, and it happen some issues in getView() method,
I have try and it's only display with 7 empty items in gridview, and not display content and image
So How fill data from Adapter into Gridview?
constructor ManufacturerFragment in class ManufacturerFragment cannot be applied to given types;
gridView.setAdapter() takes an adapter, not a Fragment
And new ManufacturerFragment() doesn't accept an Context.
I am not really sure why you think you need to create a new ManufacturerFragment within the Fragment class you already are in. Did you mean to do gridView.setAdapter(new MyAdapter(getActivity()))?
Also, your manufacturersList needs to be loaded into that adapter, so you'll need to figure that out.
And you need to use getActivity() instead of getActivity().getApplicationContext() in most places.
Then, you should only call new LoadAllManufacturers().execute(); in either onCreateView or onActivityCreated, not both. Otherwise, you're running two AsyncTasks.
Then, onPostExecute already runs on the UI thread, no need to use getActivity().runOnUiThread(new Runnable() {...
Once you do figure out how to put that ArrayList into the Adapter class, you'll want to call adapter.notifyDataSetChanged() within onPostExecute to tell the adapter to refresh the data, thereby updating the GridView to display the data.

Parse.com Error: "This query has an outstanding network connection. You have to wait until it's done."

I am struggling with a very weird Parse (Parse.com) problem:
When I use the usual list.setAdapter(Adapter);, I get the Error: "This query has an outstanding network connection. You have to wait until it's done."
Here is my code:
private ParseQueryAdapter<ParseObject> Adapter;
private CommentListAdapter CommentListAdapter;
Adapter = new ParseQueryAdapter<ParseObject>(this, "Activity");
Adapter.setTextKey("title");
// Initialize the subclass of ParseQueryAdapter
CommentListAdapter = new CommentListAdapter(this);
// Initialize ListView and set initial view to Adapter
CommentList.setAdapter(Adapter);
Adapter.loadObjects();
if (CommentList.getAdapter() == Adapter) {
CommentList.setAdapter(CommentListAdapter);
CommentListAdapter.loadObjects();
}else{
CommentList.setAdapter(Adapter);
Adapter.loadObjects();
}
I get the error at "CommentList.setAdapter(Adapter);"
Here is my Adapter:
public class CommentListAdapter extends ParseQueryAdapter<ParseObject> {
private List<String> itemList;
private Context context;
public ParseImageView thumbnailFile;
public TextView UsernameView;
public TextView Comments;
public CommentListAdapter(Context context) {
// Use the QueryFactory to construct a PQA that will only show
// Todos marked as high-pri
super(context, new ParseQueryAdapter.QueryFactory<ParseObject>() {
public ParseQuery create() {
ParseQuery query = new ParseQuery("Activity");
query.whereEqualTo("photoId", CommentActivity.getPhoto());
query.whereEqualTo("type", "comment");
query.orderByAscending("created_at");
Log.e("Log", "Query: " + query.countInBackground().toString());
return query;
}
});
}
// Customize the layout by overriding getItemView
#Override
public View getItemView(ParseObject object, View v, ViewGroup parent) {
if (v == null) {
v = View.inflate(getContext(), R.layout.comment_list_items, null);
}
super.getItemView(object, v, parent);
Log.e("Log", "Comment: " + object.toString());
thumbnailFile = (ParseImageView) v.findViewById(R.id.user_thumbnail);
UsernameView = (TextView) v.findViewById(R.id.user_name);
Comments = (TextView) v.findViewById(R.id.comment);
String Content = object.toString();
Comments.setText(Content);
return v;
}
}
Please help me, this kind of error should occur in such a case, I don't know what to do..
If you need more of my code, just tell me.

Converting FragmentActivity to a ListFragment with listview -- null pointer exception

I am having issues converting my previously working FragmentActivity and Customlistadapter to a ListFragment to be used with my navigation drawer. Currently, the navigation drawer is working correctly.
Below is the modified code for GetContacts
public class GetContacts extends android.support.v4.app.ListFragment {
// Log tag
private static final String TAG = MainActivity.class.getSimpleName();
// json URL
private static final String url = "http://url.json.php";
private ProgressDialog pDialog;
private List<Contacts> contactList = new ArrayList<Contacts>();
private ListView listView;
private ContactListAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootview = inflater.inflate(R.layout.fragment_contacts, container, false);
return rootview;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
//See the second issue about wotking with ListFragment's list.
adapter = new ContactListAdapter(this, contactList);
setListAdapter(adapter);
// pDialog = new ProgressDialog(this);
// Showing progress dialog before making http request
// pDialog.setMessage("Loading...");
//pDialog.show();
// Creating volley request obj
JsonArrayRequest contactReq = new JsonArrayRequest(url,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
Log.d(TAG, response.toString());
hidePDialog();
// Parsing json
for (int i = 0; i < response.length(); i++) {
try {
JSONObject obj = response.getJSONObject(i);
Contacts contacts = new Contacts();
contacts.setDivision(obj.getString("Division"));
contacts.setName(obj.getString("name"));
contacts.setNumber(obj.getString("number"));
// adding contacts to contacts array
contactList.add(contacts);
} catch (JSONException e) {
e.printStackTrace();
}
}
// notifying list adapter about data changes
// so that it renders the list view with updated data
adapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
hidePDialog();
}
});
// Adding request to request queue
AppController.getInstance().addToRequestQueue(contactReq);
//end volley code paste
}
public void onDestroy() {
super.onDestroy();
hidePDialog();
}
private void hidePDialog() {
if (pDialog != null) {
pDialog.dismiss();
pDialog = null;
}
}
}
Additionally, I have modified the code for ContactListAdapter. Currently I am getting the following compile error. "Error:(49, 56) error: cannot find symbol method getContext()". I checked this SO question and tried to understand what is causing this issue with no luck.
public class ContactListAdapter extends BaseAdapter {
private Activity activity;
private Fragment fragment;
private LayoutInflater inflater;
private List<Contacts> contactItems;
public ContactListAdapter(Fragment fragment, List<Contacts> contactItems) {
this.fragment = fragment;
this.contactItems = contactItems;
}
#Override
public int getCount() {
return contactItems.size();
}
#Override
public Object getItem(int location) {
return contactItems.get(location);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null){
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_rowcontacts, null);
}
TextView division = (TextView) convertView.findViewById(R.id.division);
TextView name = (TextView) convertView.findViewById(R.id.name);
TextView number = (TextView) convertView.findViewById(R.id.number);
// getting contact data for the row
Contacts m = contactItems.get(position);
// Division
division.setText(m.getDivision());
// Name
name.setText("Name: " + m.getName());
// number
number.setText("Number: " + m.getNumber());
return convertView;
}
}
The main problem is that a FragmentActivity isn't a Fragment, is an Activity (see the reference here). You can't instantiate it as a Fragment like you do in selectItem().
If you want contacts to be loaded in your main activity (the one with the navigation drawer) then GetContacts should inherit from Fragment instead of FragmentActivity.
UPDATE
There are three issues with your code.
Null pointer exception (setting the adapter in the right place)
First you should move everything related to setting up the adapter to a method that is called after the Fragment is attached to its Activity (more info about the Fragment's lifecycle here). In onCreateView the Activity hasn't attached the Fragment yet so the Context of the adapter won't be available (see the third issue). Override onAttach() and place it there:
public class GetContacts extends ListFragment {
// Log tag
private static final String TAG = MainActivity.class.getSimpleName();
// json URL
private static final String url = "http://url.json.php";
private ProgressDialog pDialog;
private List<Contacts> contactList = new ArrayList<Contacts>();
private ListView listView;
public ContactListAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_contacts, container, false);
return rootView;
}
#Override
public void onAttach(Activity activity) {
//See the third issue about the Context passed to the adapter.
adapter = new ContactListAdapter(activity, contactList);
//See the second issue about working with ListFragment's list.
setListAdapter(adapter);
// pDialog = new ProgressDialog(this);
// Showing progress dialog before making http request
// pDialog.setMessage("Loading...");
//pDialog.show();
// Creating volley request obj
JsonArrayRequest contactReq = new JsonArrayRequest(url,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
Log.d(TAG, response.toString());
hidePDialog();
// Parsing json
for (int i = 0; i < response.length(); i++) {
try {
JSONObject obj = response.getJSONObject(i);
Contacts contacts = new Contacts();
contacts.setDivision(obj.getString("Division"));
contacts.setName(obj.getString("name"));
contacts.setNumber(obj.getString("number"));
// adding contacts to contacts array
contactList.add(contacts);
} catch (JSONException e) {
e.printStackTrace();
}
}
// notifying list adapter about data changes
// so that it renders the list view with updated data
adapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
hidePDialog();
}
});
// Adding request to request queue
AppController.getInstance().addToRequestQueue(mcontactReq);
//end volley code paste
}
public void onDestroy() {
super.onDestroy();
hidePDialog();
}
private void hidePDialog() {
if (pDialog != null) {
pDialog.dismiss();
pDialog = null;
}
}
}
Null pointer exception (working with ListFragment's list
ListFragments provide some methods like setListAdapter() to work with the inner ListView without having to get it first with findViewById(), but they work with their "default" ListView. For identifying this ListView they look in the layout for a ListView with the id #id/android:list. You're using a custom id for your ListView so setListAdapter() cannot find the default ListView (more info about ListFragment here). You have three posible solutions:
First:
If your layout doesn't contain more than a ListView you don't have to use a custom layout. Don't override onCreateView, removing everything about inflating a custom view.
Second:
Change the id of your ListView to #id/android:list in the xml layout file.
Third:
Maintain your custom layout and id, but always use the ListView getting it first with findViewById(), not with the ListFragment's methods for dealing with lists (like when GetContacts was a FragmentActivity). So this:
listView = (ListView) rootView.findViewById(android.R.id.list);
adapter = new ContactListAdapter(this, contactList);
setListAdapter(adapter);
would become this:
listView = (ListView) getView().findViewById(android.R.id.list);
adapter = new ContactListAdapter(this, contactList);
listView.setListAdapter(adapter);
The first two allow you to work with the default list. I don't recomend you to go for the third, since ListFragments are designed for helping you in dealing with lists faster and you wouldn't be using their helper methods, and therefore using a ListFragment like any other Fragment.
P.S. The code for GetContacts I posted before use the second solution.
LayoutInflater in Adapter
With BaseAdapter you have to keep a reference of the Context in wich the adapter will work. Since Fragment isn't a Context, fragments have to use the Activity they're attached to as the Context. Look a this line in the GetContact's code I just updated (in GetContact's onAttached()):
adapter = new ContactListAdapter(activity, contactList);
Then in the adapter you use that as the Context to get the LayoutInflater:
public class ContactListAdapter extends BaseAdapter {
//There is no need for this two
//private Activity activity;
//private Fragment fragment;
//We use this instead
private Context context
private LayoutInflater inflater;
private List<Contacts> contactItems;
//Now the constructor receives a Context
public ContactListAdapter(Context context, List<Contacts> contactItems) {
this.context = context;
this.contactItems = contactItems;
}
//Every other method remains the same
...
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//Now we use context to get the LayoutInflater
if (convertView == null){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_rowcontacts, null);
}
//The rest of the code is the same
...
}
}
EDIT
Added the changed code for the "set up the adapter in the right place".
Changed the third issue for using a BaseAdapter (I got confused).

Refreshing a ListView's adapter from inside an AsyncTask without duplicates

I have a ListView and obviously an adapter the uses a List<T> object.
I added a button that is supposed to refresh the listview (= re-query the database and recreate the ListView).
All of the above (from querying the db to setting the adapter) happens inside an AsyncTask.
Update Button Code
mRefreshButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
v.setClickable(false);
new GetScheduleTask().execute((Void[]) null); //Basically, execute it again.
}
});
AsyncTask (I deleted some unnecessary code)
private class GetScheduleTask extends AsyncTask<Void, Void, List<Object[]>> {
#Override
protected List<Object[]> doInBackground(Void... params) {
SQLiteDatabase db = SchooLauncherDbHelper.getInstance(getActivity()).getReadableDatabase();
//TODO test repetition
String sql = "LONG A** SQL HERE";
Cursor c = db.rawQuery(sql, new String[] {String.valueOf(cal.getTimeInMillis()), String.valueOf(cal.getTimeInMillis()),
String.valueOf(cal.getTimeInMillis()), String.valueOf(cal.get(Calendar.MONTH))});
while(c.moveToNext()) {
......................
mEventList.add(new Object[] {new Class(...), new Subject(...)});
}
}
db.close();
return mEventList;
}
#Override
protected void onPostExecute(List<Object[]> result) {
mScheduleListView = (ListView) view
.findViewById(R.id.mScheduleListView);
TextView myTv = new TextView(getActivity());
myTv.setText("No Results");
mScheduleListView.setEmptyView(myTv);
mScheduleEventAdapter = new ScheduleEventAdapter(getActivity(),
R.layout.schedule_item_layout, mEventList);
mScheduleListView.setAdapter(mScheduleEventAdapter);
if(!mImageView.isClickable())
mImageView.setClickable(true);
}
}
ScheduleEventAdapter
public class ScheduleEventAdapter extends ArrayAdapter<Object[]>{
private Context context;
private int layoutResourceId;
private List<Object[]> data = null;
private View row = null;
private LayoutInflater inflater;
private Subject mSubject;
private Event mEvent;
private String mEventStartTime, mEventEndTime;
public ScheduleEventAdapter(Context context, int layoutResourceId, List<Object[]> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
row = convertView;
if(row == null) {
inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(this.layoutResourceId, parent, false);
}
mSubject = (Subject) data.get(position)[1];
mEvent = (Event) data.get(position)[0];
((ImageView) row.findViewById(R.id.subjecticon)).setImageResource(mSubject.icon);
((TextView) row.findViewById(R.id.schedule_event_subject_name)).setText(mSubject.name);
((TextView) row.findViewById(R.id.schedule_event_title)).setText(mEvent.mEventTitle);
((TextView) row.findViewById(R.id.schedule_event_location)).setText(((Class)mEvent).mClassLocation);
this.mEventStartTime = ((int) mEvent.mEventStartTime / (1000*60*60)) + ":" + ((int) (((mEvent.mEventStartTime / (1000*60)) % 60)));
this.mEventEndTime = ((int) mEvent.mEventEndTime / (1000*60*60)) + ":" + ((int) (((mEvent.mEventEndTime / (1000*60)) % 60)));
((TextView) row.findViewById(R.id.schedule_event_time)).setText(this.mEventStartTime + " - " + this.mEventEndTime);
return row;
}
}
The Problem
The problem is that the button works, but instead of re-creating the list, duplicates are being shown.
As you can see, new items are being added mEventList, even the ones that already show.
I could use an if statement with the .contains() method of the list to determine whether an object needs to be added to the list or not, but the problem is, even though the contents of the objects may be the same, the pointers are different which I guess is why it also doesn't work (I tried).
I also tried resetting the adapter and playing with the data and .notifyDataSetChanged() but it didn't work.
The only solution I could think of is to compare each field of each object inside the array, but that sounds too much like a performance-hit to me.
If so, what would be the most efficient and correct way to fix this problem?

why is getView in ArrayAdapter class executing twice?

I have an app that is used by carers to get their rota for a particular day. The user sets a date in a datepicker. This date is passed to an activity called NfcScannerActivity. This activity calls a webservice using that date to get the rota. The rota data is an array. NfcScannerActivity passes this array to GetRota, which is an activity that has an array adapter with which shows the rota data.
All this works fine until in the getView method of GetRota i check if there is no data for a particular day. At this point I toast the user stating this fact and then re-call NfcScannerActivity with another date(hopefully a valid one). If there is no data the getView method seems to execute twice as the user is toasted twice. Why is this?
Snippets from NfcScannerActivity:
if(intent.getAction().equalsIgnoreCase("NEXT_ROTA")){
Log.e(TAG, "next rota action");
String date = intent.getStringExtra("nextRota");
getNextRota(date);
}
private void getNextRota(String stringExtra) {
String[] params = new String[]{nfcscannerapplication.getCarerID(), stringExtra};
AsyncGetRota agr = new AsyncGetRota();
agr.execute(params);
}
//////////snippet of async result of webservice call
...........
..........
#Override
protected void onPostExecute(Void result)
{
super.onPostExecute(result);
if(progressDialog != null)
progressDialog.dismiss();
if(isRotaArrayNull == false){
Intent intent = new Intent(NfcscannerActivity.this,
GetRota.class);
Bundle b = new Bundle();
b.putSerializable("rotaArray", rotaArray);
intent.putExtra("rotaArrayBundle", b);
startActivity(intent);
}else{...........
...........
.
snippet from GetRota, the activity that shows the rota
#Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
super.onNewIntent(intent);
}
#Override
protected void onResume(){
super.onResume();
Log.e(TAG, "global date in onresume getrota = " + nfcscannerapplication.getglobalDateTime());
array = (ArrayList<String[]>)getIntent().getBundleExtra("rotaArrayBundle").get("rotaArray");
Log.e(TAG, "array size in onresume = " + array.size());
..............
...............
/////////adapter class in getrota
private class MySimpleArrayAdapter extends ArrayAdapter<String> {
private final Context context;
private final ArrayList<?> list;
public MySimpleArrayAdapter(Context context, ArrayList<?> list) {
super(context, R.layout.rotarowlayout);
Log.e(TAG, "inside adapter constructor");
this.context = context;
this.list = list;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.rotarowlayout, parent,
false);
TextView startTime = (TextView) rowView
.findViewById(R.id.rowstarttime);
TextView duration = (TextView) rowView
.findViewById(R.id.rowduration);
TextView status = (TextView) rowView.findViewById(R.id.rowstatus);
TextView name = (TextView) rowView.findViewById(R.id.rowclientname);
//ImageView imageView = (ImageView)rowView.findViewById(R.id.rowimagestatus);
String record = list.get(position).toString();
rowView.setTag(record);
String[] itemsInRecord = record.split(",");
Log.e(TAG, "itemin record = " + itemsInRecord.length);
String[] recordItem = new String[itemsInRecord.length];
for (int x = 0; x < itemsInRecord.length; x++) {
recordItem[x] = itemsInRecord[x];
}
if(recordItem[9].toString().trim().equalsIgnoreCase("Out of range]")){
Toast.makeText(GetRota.this, "No rota available", Toast.LENGTH_LONG).show();
nfcscannerapplication.setGobalDateTime(new DateTime());
//onBackPressed();
DateTime globalDateTime = nfcscannerapplication.getglobalDateTime();
DateTimeFormatter fmt = DateTimeFormat.forPattern("d-MMM-Y");
String formattedglobalDateTime = fmt.print(globalDateTime);
Intent i = new Intent(GetRota.this, NfcscannerActivity.class);
i.putExtra("nextRota", formattedglobalDateTime);
i.setAction("NEXT_ROTA");
startActivity(i);
}else if(recordItem[0].toString().trim().equalsIgnoreCase("[nodata")){
Toast.makeText(GetRota.this, "You have no calls", Toast.LENGTH_LONG).show();
Log.e(TAG, "you have no calls");
nfcscannerapplication.setGobalDateTime(new DateTime());
//onBackPressed();
DateTime globalDateTime = nfcscannerapplication.getglobalDateTime();
DateTimeFormatter fmt = DateTimeFormat.forPattern("d-MMM-Y");
String formattedglobalDateTime = fmt.print(globalDateTime);
Intent i = new Intent(GetRota.this, NfcscannerActivity.class);
i.putExtra("nextRota", formattedglobalDateTime);
i.setAction("NEXT_ROTA");
startActivity(i);
You do not have control over the call of getView(). An arrayadapter acts as the bridge between the data and list view. When ever a list view loads, refreshes or there is some scrolling etc, the list view calls the get view method of the array adapter. So you have no control over when the getView() is called.
Moreover the getView() is called for each item of the list. So if there are two items who have recordItem[0] as no data, the toast will appear two times.
recorditem is an array that is created from a particular position of the ArrayList: list.
recorditem=list.get(position).split();
So row1 of the listview will hold a recorditem array, row2 will hold another recorditem array. Hence if recorditem[0] for both the rows have no data, then Toast will be shown multiple times.
This is because getView is something we cannot control. It is designed this way to consistently refresh views, as decided by the Android system.
Perhaps you can move the toast somewhere else in your code. You can check if there is no rota in onResume or onActivityResult. Generally, you should only be concerned with a specific item in your ArrayList in the getView method.

Categories

Resources