AsyncTask with ViewHolder pattern doesn't work - android

I'm trying to optimize my code into my adapter with AsyncTask to do a internet request to read a JSON and display its data.
My code before the change with AsyncTask works perfectly so i'm doing something wrong now because it isn't setting the text after the request is expired. I want that while i scroll the listview, AsyncTask run the code of the internet connection and after it's done, it will change the text into textviews from default to right one.
Adapter class
public class ListBookmarksAdapter extends ArrayAdapter<BookmarksHandler>{
private final Context context;
private List<BookmarksHandler> list;
DatabaseHandler dh;
SQLiteDatabase db;
ViewHolder viewHolder;
Gson gson;
public ListBookmarksAdapter(Context context, List<BookmarksHandler> list) {
super(context, R.layout.listbookmarks, list);
this.context = context;
this.list = list;
}
static class ViewHolder{
TextView tvTitle;
TextView tvChapter;
ImageView immagine;
}
#Override
public View getView(final int position, View rowView, ViewGroup parent) {
gson = new Gson();
dh = new DatabaseHandler(context);
db = dh.getWritableDatabase();
if(rowView==null){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rowView = inflater.inflate(R.layout.listbookmarks, parent, false);
viewHolder = new ViewHolder();
viewHolder.tvTitle = (TextView) rowView.findViewById(R.id.tvTitle);
viewHolder.tvChapter = (TextView) rowView.findViewById(R.id.tvChapter);
viewHolder.immagine = (ImageView) rowView.findViewById(R.id.imageView);
rowView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) rowView.getTag();
}
new AsyncList(position).execute("http://www.myurl.com/" +list.get(position).getId_manga() + "/");
return rowView;
}
private class AsyncList extends AsyncTask<String, Void, String> {
int position;
public AsyncList(int position){
this.position = position;
}
#Override
protected String doInBackground(String... params) {
String urlManga = null;
try {
urlManga = MainActivity.connessione(params[0]);
} catch (Exception e) {
e.printStackTrace();
}
assert urlManga != null;
return urlManga.trim();
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
MangaSpec manga = gson.fromJson(result, MangaSpec.class);
viewHolder.tvTitle.setText(manga.getTitle());
viewHolder.tvTitle.setSelected(true);
if(Integer.parseInt(String.valueOf(manga.getStatus()))==2)
viewHolder.immagine.setImageResource(R.drawable.book_close);
List generic = manga.getChapters();
for(int i=0; i<generic.size(); i++){
List chapters = (List) generic.get(i);
if(((String) chapters.get(3)).equals(list.get(position).getId_chapter())){
double numero = (Double) chapters.get(0);
String titoloC = (String) chapters.get(2);
if((numero-(int)numero)!=0)
viewHolder.tvChapter.setText(numero+" - "+titoloC);
else
viewHolder.tvChapter.setText((int)numero+" - "+titoloC);
viewHolder.tvChapter.setSelected(true);
}
}
}
}
}
Here is MainActivity.connessione method:
public static String connessione(String url) throws Exception {
URL website = new URL(url);
URLConnection connection = website.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null)
response.append(inputLine);
in.close();
return response.toString();
}
And here is a screen of what i mean:
"Shingeki no Kyoijin" and "63 - Catene" are the items on the first row (follow the arrows). The others two items in the list have different values. As you see, the text is set as default one (Text) for the first two items and it's set wrong for the last one.

A ViewHolder is specific to an item not the whole adapter, so move the ViewHolder variable declaration inside the getItem method. Now you see the problem: the AsyncTask does not see any ViewHolders.
Instead of int position (which you don't use) let the async task constructor take a ViewHolder parameter. Furthermore declare your custom async task as static like so:
private static class AsyncList extends AsyncTask<String, Void, String> {
By doing this you ensure that you cannot access anything you don't need to / should not have access to from inside the async task.
Suggestion: Move the JSON parsing to background (and change the async task result type to MangaSpec).

Your biggest problem is that you have declared a single ViewHolder to handle all the views in your adapter.
So here's how the problem happens:
ListView calls getView() in your adapter for the first item. Since there are no views to recycle, you inflate a view, create a ViewHolder, and start a download. ListView draws the first item using the values from your inflated layout.
ListView calls getView() two more times to draw the other items while the download is still in progress. Since there's only one viewHolder for the entire adapter, each time you say viewHolder = new ViewHolder() you are replacing the ViewHolder created by the previous item.
The download for the first item completes. But since you overwrote the ViewHolder, the data for the download of the first position is being written into the views for the ViewHolder of the third position. Once the other downloads complete, they are also being written into this same ViewHolder.
I would imagine that if you scroll this list up and down, you will see items changing pretty randomly as you scroll your list.
Now, even if you do fix your code to have an individual ViewHolder for each view, you will still have problems because view recycling will cause your downloaded data to draw in the wrong place when a view is recycled between the download start and the download end.
In addition, you have set up a situation where the same server data will be downloaded over and over as the user scrolls up and down, and that is not efficient.
Here is what I think you should do:
If your list item has a title, a chapter, and an image, then you should create a model class that has a String property for the title, a String property for the chapter, and a String property for the image name or image URL. Let's call this class Bookmark.
Because Android UI uses the Model/View/Controller pattern, what that means is that whatever is in the adapter list should be the actual data that is displayed. It looks like you are passing the array of bookmark ids that are not what is displayed in the list item.
Create an ArrayList<Bookmark>. Then for each BookmarksHandler in your list, create a blank Bookmark object, add it to the list then hand this list to the ArrayAdapter. This means you have a list of blank items to start.
Your getView() method will simply pull whatever data is in Bookmark list position n to create the view for item position n. If you use a ViewHolder in getView(), make sure you create a new ViewHolder that is not shared with other items.
Start your data download. It looks like your REST interface can only get one bookmark at a time. If there is a way you could change it so you could post all the bookmark ids in the list and get one big JSON array back with all your bookmark data, that would be more efficient.
When your data download completes, you would loop through your JSON data and update each blank Bookmark item in the adapter's ArrayList, then call notifyDataSetChanged() on the adapter. This tells the ListView that its backing data has changed and it's time to update the ListView on the screen.
The biggest thing to know here is that the downloaded data should go into the adapter data so your getView() can create a list item from it. Going directly to the view from a download is only for the actual image.
The image download is not a trivial task. For that I refer you to this excellent article on the Android Developer's blog which explains it way better than I can.
I hope this has been helpful; I know it's a lot to digest. For further information, look for more tutorials online on how to update ListViews with downloaded data.

Related

Custom ListView not getting updated

I'm developing a currency exchange app and I'm having some problems with updating my ListView after extracting the rates from an API.
Ignore the flags, I just put whatever files I had to test the solution
On the start of my activity, I am defining:
final ArrayList<ItemData> list = new ArrayList<ItemData>();
final String[] web = {
"EUR", "JPY", "USD", "GBP"
};
final Integer[] imageId = {R.drawable.austria, R.drawable.bangladesh, R.drawable.benin, R.drawable.uk};
private static String _spinnerData;
public static String test;
public static synchronized String getCurrentSpinner(){
if (_spinnerData == null) {
String _spinnerData;
}
return _spinnerData;
}
And onCreate() is defined as:
... not important ...
Spinner spin = (Spinner) findViewById(R.id.spinner_complist);
final SpinnerAdapter adapter1 = new SpinnerAdapter(this,
R.layout.spinner_layout, R.id.txt, list);
spin.setAdapter(adapter1);
spin.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
ItemData item = (ItemData) (parentView.getItemAtPosition(position));
Log.i("item_1", item.getText());
String spinnerData = getCurrentSpinner();
spinnerData = item.getText();
}
I then have a custom Adapter to put the flags+name, where the name is a textview.
Afterwards, I get my conversion rate from an API, through a function getRate() that is working.
On the custom adapter, I have overriden the getView method:
#Override
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.mylist, null, true);
TextView txtTitle = (TextView) rowView.findViewById(R.id.itemName);
ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
imageView.setImageResource(imageId[position]);
String currency;
txtTitle.setText(web[position]);
String spinnerData= getCurrentSpinner();
if (spinnerData!=null) {
currency=spinnerData;
getRate(currency, web[position], txtTitle);
}
return rowView;
}
So, in getRate I obtain a String from each row of the ListView and replace it by the value in another coin.
My problem is: If I write getRate("EUR",web[position],txtTitle), everything works as intended.
However, if I put the code as it is, it just doesn't update my ListView. I put a breakpoint and currency is "EUR", so it should be equivalent to what I had by hardcoding the string.
I think that probably the ListView isn't getting properly updated, or the function is making some callback that is replacing my TextViews with the original values.
Any ideas?
Thanks in advance,
Two things stand out to me.
First, this block:
public static synchronized String getCurrentSpinner(){
if (_spinnerData == null) {
String _spinnerData;
}
return _spinnerData;
}
This doesn't really make sense. It reads "if the static scoped _spinnerData is null, create another locally scoped String also called _spinnerData and do nothing with it, then return the statically scoped instance."
And second, this logic:
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
ItemData item = (ItemData) (parentView.getItemAtPosition(position));
Log.i("item_1", item.getText());
String spinnerData = getCurrentSpinner();
spinnerData = item.getText();
}
This reads "create a temporary string called spinnerData that is initialized with the return value of getCurrentSpinner() then immediately replace it with the value of item.getText() then throw the whole thing away (because it's never used thereafter)".
It appears to me you have a misunderstanding in how references work. I think what you are trying to do is save the current spinner selection and use that in your getView().
If that's the case, you would do something like this:
private String _spinnerData; // Does not need to be static
// In onItemSelected
ItemData item = (ItemData) (parentView.getItemAtPosition(position));
Log.i("item_1", item.getText());
_spinnerData = item.getText(); // Save the last item selected
// In getView()
// Call this method with the last known spinner selection
getRate(_spinnerData, web[position], txtTitle);
I would have to assume you are new to Java and don't fully understand how references and scope work. Is that a fair assumption? If so, I strongly suggest you take a step back from Android and work on getting more familiar and comfortable with Java before proceeding. Android is complicated enough without having to figure out the language as you go as well.
Hope that helps!
I think you have some problems with the flow of the data in your Activity.
The getView in the listview should return a view with the populated data from a data source that has the data.
I would advice changing to have something like:
Snippet position selected -> call web API to get any data that you need -> when is returned add it to a list of data that is being displayed on the listview -> then call in the adapter of the listview notifyDataSetChanged()
I would recommend this article for more information about using listviews: http://www.vogella.com/tutorials/AndroidListView/article.html

when i scroll the custom list in my android app then the favorite icon which is part of the custom adapter changes on its own

I have created a custom list, the adapter of which contains three components song title, song number and favorite icon. The favorite icon is meant to mark or unmark the favorite item in the list.
Please have a look to the attached video to understand what is the problem.
Video
When I click on the star, the icon gets selected/unselected and fires the setOnFavoriteChangeListener event. In the event I check the isFavorite status and update the database accordingly. Here is the full code of adapter:
public class song_index_adapter extends ArrayAdapter<song_index_model>{ //implements View.OnClickListener {
private ArrayList<song_index_model> dataSet;
Context mContext;
private int lastPosition = -1;
public song_index_adapter(ArrayList<song_index_model> data, Context context) {
super(context, R.layout.song_index_row, data);
this.dataSet = data;
this.mContext=context;
}
// View lookup cache
private static class ViewHolder {
TextView txt_sno;
TextView txt_title;
MaterialFavoriteButton favorite;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
// Get the data item for this position
final song_index_model dataModel = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
final ViewHolder viewHolder; // view lookup cache stored in tag
final View result;
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.song_index_row, parent, false);
viewHolder.txt_sno = (TextView) convertView.findViewById(R.id.sno);
viewHolder.txt_title = (TextView) convertView.findViewById(R.id.songTitle);
viewHolder.favorite = (MaterialFavoriteButton) convertView.findViewById(R.id.indexfav);
result=convertView;
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
result=convertView;
}
Animation animation = AnimationUtils.loadAnimation(mContext, (position > lastPosition) ? R.anim.up_from_bottom : R.anim.down_from_top);
result.startAnimation(animation);
lastPosition = position;
viewHolder.txt_sno.setText(dataModel.getSno());
viewHolder.txt_title.setText(dataModel.getTitle());
//--- following conditional statements take care to
//--- not to show a star with the index letter
if(viewHolder.txt_sno.getText().toString().equals(""))
viewHolder.favorite.setVisibility(View.GONE);
else
viewHolder.favorite.setVisibility(View.VISIBLE);
viewHolder.favorite.setFavorite(dataModel.getFav());
int fsize = (gvar.fontsize * gvar.fontstep) + gvar.fontmin;
viewHolder.txt_title.setTextSize(fsize);
viewHolder.txt_sno.setTextSize(fsize);
viewHolder.favorite.setOnFavoriteChangeListener(new MaterialFavoriteButton.OnFavoriteChangeListener() {
#Override
public void onFavoriteChanged(MaterialFavoriteButton buttonView, boolean isfavorite) {
DBHelper db = new DBHelper(mContext);
SQLiteDatabase sdb = db.getWritableDatabase();
boolean isUpdate = db.updateData(gvar.table,dataModel.getSno(),dataModel.getTitle(),dataModel.getSong(),dataModel.getCategory(),isfavorite);
if(!isUpdate)
Toast.makeText(mContext, "Song Selection could not be saved", Toast.LENGTH_SHORT).show();
else {
Toast.makeText(mContext, "Updated " + dataModel.getSno(), Toast.LENGTH_SHORT).show();
Log.e("UPDATED", dataModel.getSno() + " " + isfavorite);
}
}
});
return convertView;
}
}
This event is inside the adapter file which is set on the listview and it basically checks the status of the favorite star and update the status of the song in the database.
You can see the Toast messages prompting about the update.
My problem is that even if I'm simply scrolling up and down without pressing the star icon, then also the setOnFavoriteChangeListener event keeps firing. This can be seen in the Toast messages and in the Log records. I'm attaching snapshot of the Log records also for you to view.
I personally changed the favorite icon of only song no 9 and 42 in the beginning and 35 in the end. In between I only kept scrolling up and down and you can see how the UPDATE is happening by itself.
My aim is to mark the list of favorite items.
Why is the setOnFavoriteChangeListener getting fired without me touching it.
Is there any other method to mark favorite items from a list and save them in the database.
Thanks in advance
I think I figured out your problem. It has to do with the fact your are recycling your ListView's items. During the initial load everything works fine and nothing is selected and the call dataModel.getFav() returns false so when your call viewHolder.favorite.setFavorite() the MaterialFavoriteButton doesn't fire it's set OnFavoriteChangeListener. The reason for this is, because internally it checks to see if the new favorite state is different from the last as an optimization to prevent unnecessary work (I checked the source code). But, once you make a selection your onFavoriteChanged will be fired because the new state is different and you then store the values inside of your Database. Your problem arises when you start to scroll, since your view are recycled, the MaterialFavoriteButton's favorite state will set but when you call dataModel.getFav() it will return false to and change the MaterialFavoriteButton's favorite state back to false. Thus causing your the MaterialFavoriteButton's old OnFavoriteChangeListener to fire once again (thus why the button isn't favorited). It is also updating your Database with it's previous dataModel (NOTE: this is a closure issue) that is why you see it use the text from a view that was scrolled of screen. The reason the old OnFavoriteChangeListener is being called because the view is being recycled and still has the instance you passed to it during the initial load. So once all view are populated to fill the entire screen, if you scroll the first one to go off the top screen will be passed in to getView() as convertView.You should move the call viewHolder.favorite.setOnFavoriteChangeListener to before the setFavorite() call. If you place a debug statement in the code and step through the call to viewHolder.favorite.setFavorite() you should see what I am talking about. I hope this all makes since to you. If not, comment and I try and give you more assistance. My recommended solution would be to hold off on the database writes until a save button is pressed or the Activity/Fragment is paused or stopped and just store the favorite state inside of an ArrayList that gets accessed using the getView(...) position argument. This is much more efficient because you won't have to constantly access the Database anytime the ListView is scrolled.

Android List View Edit All view apperance

I want to start by saying that I'm not using views here. I simply have 5 views and I swap them around with two selection clicks. I've looked through several questions online and most of them deal with the Recycler and getChildAt etc and it just doesn't work for me.
I understand how Adapters are meant to work so I figured I'd have to pass my background colour into my Adapter. Just a quick background into what I want to do here. The user can shuffle the list items around and they must correspond to the table next to it. Once the user thinks the answers are correct they hit the submit button. At this point the background colours should change to green or red for correct and incorrect.
However when I want to access all the views I can't seem to get a handle on it.
Getting my data source for the adapter
private ArrayList<Triplet<String, Integer, Integer>> testArray = new ArrayList<>();
public ArrayList<Triplet<String, Integer, Integer>> getDefinitionWordsTriplet(){
testArray.add(Triplet.with("Trojan", 0, Color.WHITE));
testArray.add(Triplet.with("Virus", 1, Color.WHITE));
testArray.add(Triplet.with("Spyware", 2, Color.WHITE));
testArray.add(Triplet.with("Ransomware", 3, Color.WHITE));
testArray.add(Triplet.with("Leakware", 4, Color.WHITE));
return testArray;
}
So I'm using an API to use a Triplet, originally this was a Pair but I decided to include the colour Int.
Populating the adapter
//TESTING
private ArrayList<Triplet<String, Integer, Integer>> testTripletArray;
private DefintionListViewAdapterTriplet leftTripletAdapter;
private void EstablishTableLists(){
leftDefinitionLV = (ListView) findViewById(R.id.definitionMainGameLeftLV);
ListView rightDefinitionLV = (ListView) findViewById(R.id.definitionMainGameRightLV);
DefinitionWordList wordAndDefinitionList = new DefinitionWordList();
definitionWordsList = wordAndDefinitionList.getDefinitionWords();
definitionExplanationsList = wordAndDefinitionList.getDefinitionExplanations();
tableLength = definitionWordsList.size();
//TESTING
testTripletArray = wordAndDefinitionList.getDefinitionWordsTriplet();
DefintionListViewAdapter rightAdapter = new DefintionListViewAdapter
(DefinitionGameMainActivity.this, definitionExplanationsList);
//TESTING
leftTripletAdapter = new DefintionListViewAdapterTriplet(DefinitionGameMainActivity.this, testTripletArray);
//leftDefinitionLV.setAdapter(leftAdapter);
rightDefinitionLV.setAdapter(rightAdapter);
//TESTING
leftDefinitionLV.setAdapter(leftTripletAdapter);
leftDefinitionLV.setOnItemClickListener(leftSideItemListener);
}
So this is standard adapter and listView establishment. I pass in my data to the adapter and then give the listView my adapter.
The Adapter
public class DefintionListViewAdapterTriplet extends ArrayAdapter<Triplet<String, Integer, Integer>> {
private Context mContext;
private ArrayList<Triplet<String, Integer, Integer>> mInput;
public DefintionListViewAdapterTriplet(Context c, ArrayList<Triplet<String, Integer, Integer>> input){
super(c, -1, input);
mContext = c;
mInput = input;
Log.i("Test: ", mInput.get(0).getValue0());
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(mContext.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.definition_table_cell, parent, false);
TextView textInputTV = (TextView) rowView.findViewById(R.id.definitionCellTV);
LinearLayout borderColour = (LinearLayout) rowView.findViewById(R.id.definitionBackgroundCell);
borderColour.setBackgroundColor(mInput.get(position).getValue2());
textInputTV.setText(mInput.get(position).getValue0());
/* if(parent.getId() == R.id.definitionTutorialRightTableLV || parent.getId() == R.id.definitionMainGameRightLV){
borderColour.setBackgroundResource(R.drawable.definition_tutorial_box_border_grey);
}else{
}*/
return rowView;
}
Again nothing specifically fancy with the adapter. I've commented out the border box background because I'm testing the colour assignment. So as I put the background colour here from the 3rd parameter of the Triplet I thought I'd be able to modify it in the main activity.
Trying to modify each view and notify
private void checkAnswerValues(){
for(int i = 0; i < tableLength; i++){
//int leftCell = definitionWordsList.get(i).second;
//TESTING
int leftCell = testTripletArray.get(i).getValue1();
int rightCell = definitionExplanationsList.get(i).second;
//TODO Doesn't work, doesn;t update the colours. No crashing but unsure
//TODO Don't think it's getting the right information, need to think how to access a view.
if(leftCell == rightCell){
Log.i(LOG_TAG, "Match");
correctAnswerCount++;
//TODO CHange border colours in both tables to green
testTripletArray.get(i).setAt2(Color.GREEN);
}else{
Log.i(LOG_TAG, "No Match");
//TODO Change border colours to red
testTripletArray.get(i).setAt2(Color.RED);
}
}
leftTripletAdapter.notifyDataSetChanged();
This is the stage at which I want the user to press the check answer button and it cycles through all of the definitions and words to see if they match. It's based on an index set as the 2nd (1st if you count from 0) argument of the Triple.
I finally try to change the background colour int of the triplet with setAt2 and then select the colour. After the for loop I notify the adapter that the information has changed. I was then hoping that the adapter would update the "getView" and apply the new argument found in the colour parameter. Unfortunately nothing updates.
Any advice here would be great as I've been testing lots of difference methods but I've come up short.
Thanks for reading, I know it's a long question.
One of the main reasons notifyDataSetChanged() won't work for you - is, your adapter loses the reference to your list. Do you ever call testTripletArray = new ... exception on the initialization? Please always do testTripletArray.clear() and then testTripletArray.addall(theNewObjects).

Listview data model updated but items not displaying

I have searched these forums for nearly 3 hours and seen several similar questions but none of the answers works for me.
I have a single Activity, with several card views. One of the card views has a Spinner with string values and a very simple ListView. The user selects a value from the Spinner, between 1 and 12. The ListView should then display a number of strings equal to the value selected, based on the position in the spinner list. For example, user selects 2nd item in spinner list and the ListView displays 2 strings. I have a custom adapter on the listview. The ListView itself initially displays a single row, which is correct. However, after the user selects a value from the spinner, the listview is not displaying the extra rows, it still only displays one row. The data for the ListView comes from an ArrayList. I have checked the data model of the adapter after the user selects a value and it has the correct number of entries, as does the ArrayList itself, yet no matter what I try the ListView itself still only display the first row. I have tried NotifyDataSetChanged and every variation of Invalidate without success.
The various code samples:
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (parent == spDoseFrequency){
Toast.makeText(this,String.valueOf(position),Toast.LENGTH_SHORT).show();
rebuildReminderTimesList(position + 1);
}
}
private void rebuildReminderTimesList(int numberOfTimes){
Toast.makeText(this,"yup",Toast.LENGTH_SHORT).show();
//reset selected item to position 1
myApp.iSelectedReminderTimeIndex = 0;
//clear array and list, then rebuild with hourly timeslots
iarrTimes = new int[numberOfTimes][2];
liReminderTimes.clear();
int startTime = 8;
for (int i = 0; i < numberOfTimes; i++){
iarrTimes[i][0] = startTime + i;
iarrTimes[i][1] = 0;
liReminderTimes.add(pad(startTime + i) + ":00");
}
//refresh the listview
myAdapter.notifyDataSetChanged();
}
public class ReminderListAdapter extends ArrayAdapter {
List<String> liTimes;
Context ctx;
LayoutInflater inf;
public ReminderListAdapter(Context ctx, List<String> liTimes) {
super(ctx, R.layout.reminder_time_listview, liTimes);
this.liTimes = liTimes;
this.ctx = ctx;
inf = LayoutInflater.from(ctx);
}
public void setLiTimes(List<String> liTimes){
this.liTimes = liTimes;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder viewHolder;
if (view == null){
view = inf.inflate(R.layout.reminder_time_listview,parent,false);
viewHolder = new ViewHolder();
viewHolder.sTime = (TextView) view.findViewById(R.id.tvTime);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.sTime.setText(liTimes.get(position));
return view;
}
private static class ViewHolder{
TextView sTime;
}
}
Any help would be appreciated as this is driving me crazy.
Quick update to this question: I have just tested supplying the initial list more than one value but even then it only displays the first item. Is there perhaps a problem with using ListView inside a CardView object? All my other cards work fine, only the ListView one fails to display properly.
Also, I have tried amending the code so that instead of changing the number of elements in the list, it just changes the text in the string of the first element and this works fine. So the notifyDataSetChanged appears to be working, but it just won't display more than one item. A quick check of the Adapter.getCount() method also gives the correct number of elements back, but won't display them.
A lot of folks forget to do the notifyDataSetChanged() call, but you've got that. Are you using a custom adapter? If so, that makes this sound like an issue with one or more of the adapter's methods. In particular, it sounds like getCount or getView might not be returning what they should be. That could either be because of a flawed logic issue, the underlying data source isn't being updated correctly, or the underlying data source isn't the same object you think it is. Without seeing your adapter though, it's hard to diagnose.
I found the problem. I had several CardView objects inside a LinearLayout, which itself was inside a ScrollView. As soon as I removed the ScrollView, the List inside the Card displayed properly. This has unfortunately created another problem in that I can no longer scroll the page to see later cards, which I have not yet found a solution for, but that is a different topic.

Dynamic ListView with dynamic graphic user interface [duplicate]

This question already exists:
Closed 10 years ago.
Possible Duplicate:
Dynamic ListView with dynamic GUI
I have to fetch the data from sqlite database. Database will contain countryname , card name and card id and status, I have to display countryname then list of cards dynamically for example if USA has four card then in list view it will display USA then all then four cards then UK then cards of UK and so on it should be implemented with check box for each item and if user clicks on suppose a card which is displaying in USA category then I have to update its status in database for example if a card in USA is checked then in database we have to update "yes" similar function for other cards .. So how to achieve this?
I think you are asking about dynamic list view with check box in its list's cell.
First of all you need an adapter for filling your list...Like This..
ListView listView=(ListView)findViewById(R.id.listView);
listView.setAdapter(new MyListAdapter (<Your ArrayList>));
Now when ever we use check box or editbox in list.Then when we scroll list it call its getview method every time. So we need to manage value or status of components. Here to manage status of checkbox I had used Arraylist of boolean type.
Make an XMl file for your list's cell. Put listeners for list's cell components inside getview method.
public class MyListAdapter extends BaseAdapter
{
private ArrayList<HashMap<String, String>> data;
private ArrayList<Boolean> checks=new ArrayList<Boolean>();
public MyListAdapter ( ArrayList<HashMap<String, String>> d)
{
data=d;
inflater = (LayoutInflater)baseActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = 0; i < d.size(); i++)
{
checks.add(i, false);//as first no check box is checked. So fill it with false value
}
}
public int getCount()
{
return data.size();
}
public Object getItem(int position)
{
return position;
}
public long getItemId(int position)
{
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.list_row, null);
name = (TextView)vi.findViewById(R.id.name);// name
checkBox=(CheckBox)vi.findViewById(R.id.check_box);
email_id = (TextView)vi.findViewById(R.id.e_mail_id); // email ID
mobile_no = (TextView)vi.findViewById(R.id.mobile_no); // mobile
checkBox.setTag(Integer.valueOf(position));
checkBox.setOnClickListener(this);
checkBox.setChecked(checks.get(position));
name.setText(<set name form any data source>);
email_id.setText(<set emailid form any data source>);
mobile_no.setText(<set mobile form any data source>);
return vi;
}
}
Hope this should help you.
Cheers...
So in your case you have to implement Multiselect Expandable listview..
Okay, so what you need first of all is to create an xml file for each row in your ListView to be inflated.
So you have some textview for your country name etc. But now to the tricky part to adding dynamically those "cards" depending on the amount:
In your xml for each row, add a LinearLayout where you want the specified cards to appear, and then in the getView() method inside the ArrayAdapter (the custom one you've created) you need to do something like this:
cardsLayout = (LinearLayout) v.findViewById(R.id.cards_layout);
cardsLayout.removeAllViews(); // rows are reused in listview, so this prevent them to be duplicated
ImageView image;
for(int i = 0; i < country.getCards(); i++){ // I assume you're using array for cards of each country
image = new ImageView(ActivityName.this);
layoutParams = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1.0f);
image.setLayoutParams(layoutParams);
image.setImageBitmap(someBitmapImage);
imageLayout.addView(image);
{
I assume you know how to create your own adapter. And this snippet of code just dynamically creates ImageViews and sets there weight to 1, so they will be equal in size through the row. If you need checkboxes or whatever, you can use the same approach by changing the ImageView to something else.

Categories

Resources