I have seen this question answered a few times, however none of the fixes have worked for me, so i'm reaching out.
I have built an app that features the Diolor Swipeable Cards Library (here) and now am trying to implement Course Card Filters.
Essentially when a user clicks a course filter we want to change the data that is being fed to the adapter.
Currently I am trying to update the data and calling notifyDataSetChanged() on the adapter, expecting the cards to refresh to show the new data set, however am finding that it is not refreshing at all.
Any help with this would be hugely appreciated.
All code below is from my Main Activity.
I declare the data set that i will be feeding to the adapter at the top of the activity:
ArrayList<CourseCardModel> courseCardModelList;
then in my onCreate() method I instantiate the adapter, attach it to the view, and call a generateCourseCards() method which populates the courseCardModelList with objects pulled from a firebase database.
// Set up and assign card adapter
ca = new CustomCardAdapter(CardsActivity.this, android.R.layout.simple_list_item_1, generateCourseCards());
flingContainer.init(CardsActivity.this, ca);
generateCourseCards() method
private ArrayList<CourseCardModel> generateCourseCards() {
Toast.makeText(getApplicationContext(), "Retrieving Courses", Toast.LENGTH_LONG).show();
courseCardModelList = new ArrayList<CourseCardModel>();
dbref = FirebaseDatabase.getInstance().getReference().child("courses");
// Retrieve the course data from Firebase db and cast as Course object
dbref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
Log.e("Count " ,"" + snapshot.getChildrenCount());
for (DataSnapshot postSnapshot: snapshot.getChildren()) {
c = postSnapshot.getValue(Course.class);
System.out.println(c.getCourseName());
CourseCardModel model = new CourseCardModel();
model.setCourse(c);
courseCardModelList.add(model);
}
Collections.shuffle(courseCardModelList);
ca.notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e("The read failed: ", databaseError.getMessage());
}
});
return courseCardModelList;
}
Attempt to update the dataset (a simple shuffle for the time being) and refresh the cards
// Shuffle the collection and refresh the cards
Collections.shuffle(courseCardModelList);
ca.notifyDataSetChanged();
EDIT: added adapter code
public class CustomCardAdapter extends ArrayAdapter {
private TextView courseName, uniName, entryStandards, courseDuration, studyMode, qualification,
studentSatisfaction, gradProspects, t1, t2, t3, t4, t5, t6;
ArrayList<CourseCardModel> items;
View v;
LayoutInflater vi;
public CustomCardAdapter(Activity context, int resource, ArrayList<CourseCardModel> courses) {
super(context, resource, courses);
vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
v = convertView;
if (v == null) {
v = vi.inflate(R.layout.course_card_inner_template, parent , false);
}
CourseCardModel c = (CourseCardModel) getItem(position);
if (c != null) {
courseName = (TextView) v.findViewById(R.id.courseCardCourseName);
uniName = (TextView) v.findViewById(R.id.courseCardUniName);
entryStandards = (TextView) v.findViewById(R.id.courseCardEntryStandards);
courseDuration = (TextView) v.findViewById(R.id.courseCardCourseDuration);
studyMode = (TextView) v.findViewById(R.id.courseCardStudyMode);
qualification = (TextView) v.findViewById(R.id.courseCardQualification);
studentSatisfaction = (TextView) v.findViewById(R.id.courseCardStudentSatisfaction);
gradProspects = (TextView) v.findViewById(R.id.courseCardGraduateProspects);
t1 = (TextView) v.findViewById(R.id.cardTV1);
t2 = (TextView) v.findViewById(R.id.cardTV2);
t3 = (TextView) v.findViewById(R.id.cardTV3);
t4 = (TextView) v.findViewById(R.id.cardTV4);
t5 = (TextView) v.findViewById(R.id.cardTV5);
t6 = (TextView) v.findViewById(R.id.cardTV6);
v.setBackgroundResource(R.drawable.newcard);
courseName.setText(c.getCourse().getCourseName());
uniName.setText(c.getCourse().getUniversity());
entryStandards.setText(c.getCourse().getEntryStandards());
courseDuration.setText(c.getCourse().getCourseDuration());
studyMode.setText(c.getCourse().getStudyMode());
qualification.setText(c.getCourse().getQualification());
studentSatisfaction.setText(c.getCourse().getStudentSatisfaction().toString() + " / 5");
gradProspects.setText(c.getCourse().getGradProspects() + " / 100");
}
if(position ==0)
{
//float alpha = (float) 0.8;
//v.setAlpha(alpha);
courseName.setVisibility(View.VISIBLE);
}
else if (position == 1){
// Prepare the View for the animation
v.setVisibility(View.VISIBLE);
float alpha = (float) 0.8;
float alpha2 = (float) 0.3;
courseName.setAlpha(alpha2);
uniName.setAlpha(alpha2);
entryStandards.setAlpha(alpha2);
courseDuration.setAlpha(alpha2);
studyMode.setAlpha(alpha2);
qualification.setAlpha(alpha2);
studentSatisfaction.setAlpha(alpha2);
gradProspects.setAlpha(alpha2);
t1.setAlpha(alpha2);
t2.setAlpha(alpha2);
t3.setAlpha(alpha2);
t4.setAlpha(alpha2);
t5.setAlpha(alpha2);
t6.setAlpha(alpha2);
v.setAlpha(alpha);
}
else {
v.setVisibility(View.INVISIBLE);
}
return v ;
}
public void updateData(ArrayList<CourseCardModel> courseCardModels) {
this.items = courseCardModels;
notifyDataSetChanged();
}
}
Problem is in this method.
public void updateData(ArrayList<CourseCardModel> courseCardModels) {
this.items = courseCardModels;
notifyDataSetChanged();
}
here you are giving another array reference to your adapter.
Just rewrite as below.
public void updateData(ArrayList<CourseCardModel> courseCardModels) {
this.items.clear();
this.items.addAll(courseCardModels);
notifyDataSetChanged();
}
Without adapter class provided my first guess would be that you messed the references up. Maybe you are shuffling the data that is not referenced from the adapter. Once you share your adapter's code, I'll update my answer.
== EDIT ==
Avoid referencing some external collection of data from adapter, and updating that referenced data. Updating adapter/list data should be done using adapter's interface and methods such as add(), addAll() or remove() It might happen that (parent) adapter makes clone/copy of your data and in that case updating external/referenced collection is not doing any good.
You're extending an ArrayAdapter which holds his own array of models (the array passed to the constructor). If you would like to update the items, do something like this:
ca.clear();
for (CourseCardModel object : courseCardModelList) {
ca.insert(object, ca.getCount());
}
ca.notifyDataSetChanged();
Or you can override the getItem method and return an item from your items array.
And another option would be extending BaseAdapter instead of the ArrayAdapter.
Related
I try to open each video when I click on them but what I get instead is only the second video (sometimes first video). For example, when I click on "16 best video ideas for small business" I want it to open that particular video. But what I get instead is "this tiny camera can show the world from a bug's point of view. I think the problem occurs because of for loop inside query in UserHomeVideoAdapter.
UserHomeVideoAdapter.java:
public class UserHomeVideoAdapter extends FirestoreRecyclerAdapter<FollowList, UserHomeVideoAdapter.UserVideoHolder> {
Context context;
final FirebaseFirestore db = FirebaseFirestore.getInstance();
String thumbUrl, videoTitle, videoUrl, videoDesc, videoId, publisherId;
Video video;
public UserHomeVideoAdapter(#NonNull #NotNull FirestoreRecyclerOptions<FollowList> options, Context context) {
super(options);
this.context = context;
}
#Override
protected void onBindViewHolder(#NonNull #NotNull UserVideoHolder holder, int position, #NonNull #NotNull FollowList model) {
Query query = db.collection("Videos").whereEqualTo("publisherId", model.getUserId());
query.get().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
if (task.getResult() != null) {
for (QueryDocumentSnapshot documentSnapshot : task.getResult()) {
video = documentSnapshot.toObject(Video.class);
Log.d("Data", documentSnapshot.getId() + " => " + documentSnapshot.getData());
thumbUrl = video.getThumbUrl();
videoTitle = video.getVideoTitle();
videoUrl = video.getVideoUrl();
videoDesc = video.getVideoDesc();
videoId = video.getVideoId();
publisherId = video.getPublisherId();
}
if (task.getResult().size() != 0) {
Glide.with(context).load(model.getUserImageUrl()).into(holder.userProfileImage);
Glide.with(context).load(thumbUrl).into(holder.videoImageView);
holder.videoTitle.setText(videoTitle);
holder.mainContainerVideo.setVisibility(View.VISIBLE);
} else if (task.getResult().size() == 0) {
holder.mainContainerVideo.getLayoutParams().height = 0;
holder.mainContainerVideo.getLayoutParams().width = 0;
}
}
} else {
Toast.makeText(context, String.valueOf(task.getException()), Toast.LENGTH_SHORT).show();
}
}).addOnFailureListener(e -> Toast.makeText(context, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show());
holder.videoContainer.setOnClickListener(v -> {
Intent intent = new Intent(context, VideoActivity.class);
intent.putExtra("videoPublisherUserName", model.getUserName());
intent.putExtra("thumbUrl", thumbUrl);
intent.putExtra("videoPublisherEmail", model.getUserEmail());
intent.putExtra("videoUrl", videoUrl);
intent.putExtra("videoId", videoId);
intent.putExtra("videoPublisherFullName", model.getUserFullName());
intent.putExtra("videoPublisherId", publisherId);
context.startActivity(intent);
});
}
#NonNull
#NotNull
#Override
public UserVideoHolder onCreateViewHolder(#NonNull #NotNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(context).inflate(R.layout.video_cell, parent, false);
return new UserVideoHolder(v);
}
public static class UserVideoHolder extends RecyclerView.ViewHolder {
RelativeLayout videoContainer, mainContainerVideo;
CircleImageView userProfileImage;
TextView videoTitle;
ImageView videoImageView;
public UserVideoHolder(#NonNull #NotNull View itemView) {
super(itemView);
mainContainerVideo = itemView.findViewById(R.id.mainContainerVideo);
videoContainer = itemView.findViewById(R.id.videoContainer);
userProfileImage = itemView.findViewById(R.id.userProfileImage);
videoTitle = itemView.findViewById(R.id.videoTitle);
videoImageView = itemView.findViewById(R.id.videoImageView);
}
}
}
I logged videoId inside that is assigned inside for loop. Sometimes it returns ids in this order "1"; "2" and sometimes it returns like this "2"; "1". When it returns in this order "1"; "2" click opens second video even if I click first video and when it returns like this "2"; "1" click opens first video even if I click second video.
If you need additional code to solve the problem please ask and I will provide it as soon as possible. Any help is appreciated. Thanks
The short answer is that onBindViewHolder() is trying to do too much. From the documentation:
Called by RecyclerView to display the data at the specified position. This method should update the contents of the ViewHolder#itemView to reflect the item at the given position.
In other words, onBindViewHolder() is only responsible for one single item in the RecyclerView. However, you are trying to fetch all of the data for every element in the list. Instead, you should fetch the data external to your adapter and pass it in as a parameter. Then onBindViewHolder() should update the UI elements of a view inside the RecyclerView to display whatever you want for one single item.
Google has a great example CustomerAdapter. First, the constructor takes the list of data that will be displayed:
public CustomAdapter(String[] dataSet) {
mDataSet = dataSet;
}
Then onbindViewHolder() is only responsible for setting what is displayed in the UI of a single item in the RecyclerView:
#Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
Log.d(TAG, "Element " + position + " set.");
// Get element from your dataset at this position and replace the contents of the view
// with that element
viewHolder.getTextView().setText(mDataSet[position]);
}
It does NOT try to get data or loop over a list or anything else. All of that is someone else's responsibility.
I want to display a list of match objects (match = two users having liked each other) in a recycler view with the help of an adapter.
This is my activity which is meant to display those matches:
public class MatchesActivity extends AppCompatActivity {
// variables:
private RecyclerView mMatchesRecyclerView;
private RecyclerView.Adapter mMatchItemAdapter;
private RecyclerView.LayoutManager mMatchesLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_matches);
mMatchesRecyclerView = findViewById(R.id.matches_recyclerView);
mMatchesRecyclerView.setNestedScrollingEnabled(false);
mMatchesRecyclerView.setHasFixedSize(true);
// set layout manager & pass it to the recycler view:
mMatchesLayoutManager = new LinearLayoutManager(MatchesActivity.this);
mMatchesRecyclerView.setLayoutManager(mMatchesLayoutManager);
// set match adapter & pass it to the recycler view:
mMatchItemAdapter = new MatchItemAdapter(getMatchesList(), MatchesActivity.this);
mMatchesRecyclerView.setAdapter(mMatchItemAdapter);
// add test items to the recycler view:
Match testMatch = new Match("abcdefgh");
matchesList.add(testMatch);
mMatchItemAdapter.notifyDataSetChanged();
Log.d("MatchesActivity", "TEST LIST: " + matchesList.toString());
}
private ArrayList<Match> matchesList = new ArrayList<Match>();
private List<Match> getMatchesList() {
Log.d("MatchesActivity", "getMatchesList function: " + matchesList.toString());
return matchesList;
}
}
And this is my adapter which is supposed to inflate the relevant layout & populate it with relevant object data:
public class MatchItemAdapter extends RecyclerView.Adapter<MatchViewholder> {
private List<Match> mMatchesList;
private Context mViewContext;
public MatchItemAdapter(List<Match> matchesList, Context context) {
this.mMatchesList = matchesList;
this.mViewContext = context;
Log.d("MatchItemAdapter", "Constructor: " + mMatchesList.toString());
}
// inflate the layout:
#Override
public MatchViewholder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_matches, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutView.setLayoutParams(lp);
MatchViewholder matchViewholder = new MatchViewholder(layoutView);
Log.d("MatchItemAdapter", "onCreateViewHolder: " + mMatchesList.toString());
return matchViewholder;
}
// populate each row within the layout:
#Override
public void onBindViewHolder(MatchViewholder holder, int position) {
Log.d("MatchItemAdapter", "onBindViewHolder: " + mMatchesList.toString());
holder.mMatchID.setText(mMatchesList.get(position).getMatchID());
}
#Override
public int getItemCount() {
return 0;
}
}
The Match class currently only takes matchID parameter which is string. An object is created with a default image and this matchID string.
At the moment, I have no real match objects from database ready, so I wanted to check that the recycler view along with adapter are working as expected before i move on to that later.
However, when I go to Matches Activity, it is empty, showing nothing at all. As you can see from the MatchesActivity onCreate method, I created a test Match object with matchID = "abcdefgh" and then added that to the matchesList. So I am expecting the "abcdefgh" text to be passed to the adapter and to be shown in the MatchesActivity.
My log statements indicate that the Match object has been created and added to the list successfully, however, getMatchesList() function returns an empty list which is then used in the Adapter constructor too, (I think this is) causing Activity not show anything.
I am relatively new to Android and Java development, especially recycler view and adapters, but from what I gathered it seems to be as if the
mMatchItemAdapter.notifyDataSetChanged();
is not working properly as everything seems to be fine up until that point. Any help would be appreciated!
You're returning 0. What you should do instead is return the length of the mMatchesList list.
#Override
public int getItemCount() {
return mMatchesList.size();
}
Im using the Horizontal MPAndroid chart to display income/Expense and the chart works for the most. I can change the information displayed although I can only change it if I do it in OnViewCreated. Nothing at all happens if I try doing it from the activity in which the fragment is displayed and I have absolutely no idea why. Although I am not 100% sure if I am setting the data the right way.
public class BudgetFragment extends Fragment{
private HorizontalBarChart mainChart;
private BarData data;
private BarDataSet dataset1;
private BarDataSet dataset2;
private int expenseSum = 0;
private int incomeSum = 0;
public MainActivityBudgetFragment(){
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.budget_fragment, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mainChart = (HorizontalBarChart) view.findViewById(R.id.mainBudgetChart);
ArrayList<BarEntry> entries1 = new ArrayList<>();
ArrayList<BarEntry> entries2 = new ArrayList<>();
entries1.add(new BarEntry(10000, 5));
entries2.add(new BarEntry(10000, 5));
dataset1 = new BarDataSet(entries1, "income");
dataset2 = new BarDataSet(entries2, "expense");
//X-axis labels
ArrayList<String> xVals = new ArrayList<String>();
xVals.add("income"); xVals.add("expense");
ArrayList<BarDataSet> dataSets = new ArrayList<BarDataSet>();
dataSets.add(dataset1);
dataSets.add(dataset2);
//Add to chart
data = new BarData(xVals, dataSets);
mainChart.setData(data);
//Description and animation
mainChart.setDescription(""); // set the description
mainChart.setScaleYEnabled(false);
mainChart.setTouchEnabled(false);
mainChart.animateY(2000);
setDataExpense(200);//(This works fine)
setDataIncome(200); //(This works fine)
}
public void updateDataExpense(){
Log.e("updateTag", "Updated expense");
dataset2.removeEntry(1);
data.addEntry(new BarEntry(expenseSum, 1), 1);
dataset2.setColor(getResources().getColor(R.color.orange));
mainChart.notifyDataSetChanged(); // let the chart know it's data changed
mainChart.invalidate(); // refresh
}
public void updateDataIncome(){
Log.e("updateTag", "Updated Income");
dataset1.removeEntry(0);
data.addEntry(new BarEntry(newIncome, 0), 0);
dataset1.setColor(getResources().getColor(R.color.green));
mainChart.notifyDataSetChanged(); // let the chart know it's data changed
mainChart.invalidate(); // refresh
}
//(These do not work when called outside OnViewCreated)
private void setDataExpense(int sum){
expenseSum = (expenseSum + sum);
Log.d("ResumeTag", "expense set at " + expenseSum);
updateDataExpense();
}
private void setDataIncome(int sum){
incomeSum = (incomeSum + sum);
Log.d("ResumeTag", "income set at " + incomeSum);
updateDataIncome();
}
}
Let me know if I forgot anything important. I do not have much experience in asking questions on Stackoverflow.
Thank you for your help!
//Chris
Please try this :
public void updateDataIncome() {
Log.e("updateTag", "Updated Income");
dataset1.removeEntry(0);
data.addEntry(new BarEntry(newIncome, 0), 0);
dataset1.setColor(getResources().getColor(R.color.green));
data.notifyDataChanged(); // NOTIFIES THE DATA OBJECT
mainChart.notifyDataSetChanged(); // let the chart know it's data changed
mainChart.invalidate(); // refresh
}
Your question does say what action is performed for which you expect the data to be updated. On whatever action you want the data to be refreshed, you should have one of the listener and then call your functions that populates the data that you want to be refreshed.
For example in this code on selection of a value in Spinner, onItemSelected(..) gets invoked & within it we are calling the populate function that refreshes the data. This is partial code but you can find a complete example of using Adapter & OnItemSelectedListener. Hope this helps you.
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, monthList);
// Setting the array adapter containing country list to the spinner widget
spinnerMonth.setAdapter(adapter);
AdapterView.OnItemSelectedListener monthSelectedListener = new AdapterView.OnItemSelectedListener()
{
#Override
public void onItemSelected(AdapterView<?> spinner, View container,
int position, long id) {
Log.d("Logger:MainAct"," onItemSelected:Entry::::");
tvMonth.setText("Chart for "+strMonth[position]);
populateChartByMonth(strMonth[position]);
//Toast.makeText(getApplicationContext(), "Success : " + strMonth[position], Toast.LENGTH_SHORT).show();
Log.d("Logger:MainAct"," onItemSelected:Exit::::");
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
Log.d("Logger:MainAct"," onNothingSelected:Entry/Exit");
}
};
entries1.add(new BarEntry(10000, 5));
This line x value is 1 and Y value is 10000
entries1.add(new BarEntry(5,10000));
I have been reading different posts on here about baseadapters and trying to learn so that I can fix my issue but I haven't been able to resolve it. On my BaseAdapter I have a String called post that is used in a column in the listview. If the post is longer than 13 characters then it is shortened automatically when the user Clicks on the shortened post then it displays it's full length,however the issue is that once you scroll down the listview and come back up to that same post it's still shortened even though the user clicked before to show the full post. I think this is an issue of the Listview or Baseadapter recycling or cache mechanism is there anyway I can fix this? This image will clear things up .. This post is more than 13 characters so it shows the shortened version
if a user wants to read it in full then they will click on the Read More which will then show all of the content which looks like this
and when the user scrolls down or up that same long post will return to this without the user clicking it again, which I want to avoid
I know that the Listview recycles but how can I update it? This is my code below
public class LocalFeed_CustomView extends BaseAdapter {
JSONObject names;
Context ctx;
Activity m;
// More is the default value
String result="More";
#Override
public int getCount() {
try {
JSONArray jaLocalstreams = names.getJSONArray("localstreams");
return jaLocalstreams.length();
} catch (Exception e) {
Toast.makeText(ctx, "Error: Please try again", Toast.LENGTH_LONG).show();
return names.length();
}
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row=convertView;
MyViewHolder holder=null;
try {
if (row == null) {
LayoutInflater li = (LayoutInflater) m.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = li.inflate(R.layout.customadapter, null);
holder = new MyViewHolder(row);
row.setTag(holder);
} else {
holder = (MyViewHolder) row.getTag();
}
final MyViewHolder finalHolder1=holder;
// Json data has been read
JSONArray jaLocalstreams = names.getJSONArray("localstreams");
final JSONObject jsonObject = jaLocalstreams.getJSONObject(position);
// if post length is more than 14 then shorten it
if (jsonObject.getString("post").length() > 14) {
holder.post.setText(jsonObject.getString("post").substring(0, 13) + "...Read More");
holder.post.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
try {
// if Result is More then show full post
if (result.equals("More")) {
finalHolder1.post.setText(jsonObject.getString("post") + "... Read Less");
result = "Less";
}
else
{
//Result is Less so shorten it again
finalHolder1.post.setText(jsonObject.getString("post").substring(0, 13) + "... Read More");
result = "More";
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
} else{
// This Post is already less than 14 characters so no Onclick here
holder.post.setText(jsonObject.getString("post"));
}
return row;
} catch (Exception e) {
e.printStackTrace();
}
return row;
}
class MyViewHolder{
TextView post;
MyViewHolder(View v)
{
post = (TextView)v.findViewById(R.id.post);
}
}
}
The adapter represents the model of the list at any given moment in time.
What this means to you is that if a user clicks a TextView to expand it with the idea that the view is going to stay expanded, then that expanded TextView is state information that will have to be captured in the adapter.
Adapters should always be thought of in two phases:
Event (like onClick()) will update state in the adapter and call notifyDataSetChanged().
getView() uses the current state to create the view.
So let's say in the adapter constructor we create an array of flags
boolean expanded[] = new boolean[size];
where size is the length of your list.
Then you can do this:
// use the current state to create the view
String text;
if (expanded[position]) {
text = jsonObject.getString("post") + "... Read Less";
} else {
text = jsonObject.getString("post").substring(0, 13) + "...Read More";
}
holder.post.setText(text);
holder.post.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// update the current state and request list refresh
expanded[position] = ! expanded[position]; // toggle
notifyDataSetChanged();
}
});
This code doesn't do exactly what yours does, I just wanted to give you the basic idea.
What I want to accomplish:
I want one TextView per row of a ListView to be dynamically set (its display text) to the value from a SQLite database.
What I tried:
Inside getView(), I assigned the said TextView via findViewById() to a global variable. Then I assigned the value position (from the parameter of getView()) to my global variable mListRowPosition. After this, I execute my AsyncTask sub-class via new AttemptGetCustomerInfo().execute().
My AsyncTask sub-class gets the SQLiteDatabase from my DatabaseHelper. With the mListRowPosition it receives the customer_id from the method getCustomerID() of the Service object inside the ArrayList<Service> dataset.
Together with the customer_id, it builds a SQL query to get the shortname of the customer. After querying, it gets the String shortname from the Cursor. I then setText(shortname) the global TextView from before (inside getView()).
The problems with that:
This 'kind of works' at some point, but it seems to be so slow that only the last (almost every time) has a value set as its text. Sometimes it also gets it wrong.
After debug-logging I saw that getView() gets called a lot faster than the AsyncTask is even finished (this makes sense, but it destroys my solution for my problem).
Also interesting: My debug log tells me that getView() gets called more often then there are data entries inside the ArrayList. If there are 5 entries, it will call getView() about 15 to 20 times. Why?
The code behind that:
public class ServiceAdapter extends ArrayAdapter<Service> {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
Service service = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(
R.layout.listview_row, parent, false);
}
// Lookup view for data population
TextView quantity = (TextView) convertView
.findViewById(R.id.QUANTITY_CELL);
TextView description = (TextView) convertView
.findViewById(R.id.DESCRIPTION_CELL);
Button delete = (Button) convertView.findViewById(R.id.BUTTON_DELETE);
customerView = (TextView) convertView.findViewById(R.id.CUSTOMER_VIEW);
mListRowPosition = position;
Log.d("ServiceAdapter", "getView changed mListRowPositon to be "
+ String.valueOf(mListRowPosition));
new AttemptGetCustomerInfo().execute();
// Populate the data into the template view using the data object
quantity.setText(String.valueOf(service.getQuantity()));
description.setText(service.getDescription());
// Set up the listener for the delete button.
final int pos = position;
delete.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
showDialog("deleteButton", pos);
}
});
customerView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showDialog("customerView", pos);
}
});
// Return the completed view to render on screen
return convertView;
}
class AttemptGetCustomerInfo extends AsyncTask<String, String, String> {
String shortname = null;
#Override
protected String doInBackground(String... params) {
DatabaseHelper db_helper = new DatabaseHelper(context);
SQLiteDatabase db = db_helper.getReadableDatabase();
Log.d("AttemptGetCustomerInfo",
"ListRowPos: " + String.valueOf(mListRowPosition));
Log.d("AttemptGetCustomerInfo", "description of ListRowPos: "
+ services.get(mListRowPosition).getDescription());
int customer_id = services.get(mListRowPosition).getCustomerID();
Log.d("AttemptGetCustomerInfo",
"customer id: " + String.valueOf(customer_id));
Cursor c = db.rawQuery(
"SELECT " + CustomerEntry.COLUMN_NAME_SHORTNAME + " FROM "
+ CustomerEntry.TABLE_NAME + " WHERE "
+ CustomerEntry.COLUMN_NAME_CUSTOMER_ID + "="
+ customer_id, null);
Log.d("AttemptGetCustomerInfo",
"available cursor size" + String.valueOf(c.getCount()));
if (c.getCount() == 0) {
Log.d("AttemptGetCustomerInfo", "There are no Cursor entries!");
return null;
}
c.moveToFirst();
shortname = c
.getString(c
.getColumnIndexOrThrow(CustomerEntry.COLUMN_NAME_SHORTNAME));
db.close();
return null;
}
#Override
protected void onPostExecute(String s) {
if (shortname != null) {
customerView.setText(shortname);
}
}
}
Additional info:
I didn't paste all of the code and a lot of code refering happens inside the above code to which no code is there also. I hope the function of my methods not shown are understandable via the method name.
So, lets begin.
If you want to display information from your database in your Listview I strongly recommend you using CursorAdapter instead of ArrayAdapter it will work far faster than it is probably now.
About the getView() calling, that happens because of the way Android paint listviews, the first time Android will call getView() several times in order to display properly things, if you change for example your ListView height from match_parent to wrap_content or viceversa you will notice that your getView() method will get called a different number of times.
Now about your code, you are not properly programming your getView() method. Inside the first part if (convertView == null) you should define your views using the ViewHolder pattern which will improve your performance.
Another problem I find here is that you are launching your AsyncTask everytime you your getView() method is called, that will produce a problem with your ListView scrolling as it will prevent of going smoothly (In a tablet for example you will run like 40 or 50 asynctask one after another, and that is a heavy workload).
If you want to keep your current code which I would strongly discourage you to do it, you will need a way of controlling if the current row have been executed your AsyncTask code in order to not repeat that work.
Hope it helps