I'm working on a newsreader, and I have two main activites, main and stories. Main calls asynctask and gets all the rss feeds, and then displays headlines when it's complete. Stories displays articles from specific feeds. There is a navbar in main that calls stories.
My downloaded and parsed articles are in a hashmap of arraylists of the feeds. I only have one adapter class, arrayAdapter. Everything works fine, and I create separate instances of arrayAdapter each time I create a new listview, but the problem is when I go back from stories to main and click on anything in the list, particularly when there are fewer items in stories then the headlines in main, it crashes with:
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131099654, class android.widget.ListView) with Adapter(class package.package.ArticleListAdapter)]
I think my error is coming from the adapters not acting like separate instances, despite the fact that I think I've made them as separate. I looked around for help with this, but most of what I saw was about people changing their data from a background thread, not inside a separate activity.
Unfortunately, this code is paraphrased, partially for readability, and partially because I'm writing it for someone else, closed source yadda yadda...
class main{
public HashMap<Integer, ArrayList<ArticleHolder>> allArticles;
public ArticleListAdapter ArtListAdapter;
public void onCreate(Bundle savedInstanceState) {
allArticles = new HashMap<Integer, ArrayList<ArticleHolder>>();
myApp appstate = (myApp)getApplicationContext();
appState.setParsedArticles(allArticles); //Added this for universal availability of the articles
//download things
//background.execute();
//...
}
private class downloader extends Asynctask ...{
...
onPostExecute(){
ArticleListAdapter = new ArticleListAdapter(context c, list_item L, allArticles.get(1));
listView1 = (ListView)findViewById(R.id.listView1);
listView1.setAdapter(ArtListAdapter);
}
}
class stories{
public HashMap<Integer, ArrayList<ArticleHolder>> allArticles;
public ArticleListAdapter ArtListAdapter;
public void onCreate(Bundle savedInstanceState) {
myApp appstate = (myApp)getApplicationContext();
allArticles = appState.getParsedArticles();
//set layout
//get article to display from Bundle extras
ListView LV = (ListView) findViewById(R.id.listView1);
ArtListAdapter = new ArticleListAdapter(StoriesScreenActivity.this, R.layout.article_list_item, allCleanArticles.get(buttonFeedNum));
LV = (ListView)findViewById(R.id.listView1);
LV.setAdapter(ArtListAdapter);
}
}
Code for the adapter as follows and is exact:
public class ArticleListAdapter extends ArrayAdapter<ArticleHolder>{
private static ArrayList<ArticleHolder> ArticleList;
private Context context;
private LayoutInflater mInflater;
public ArticleListAdapter(Context pcontext, int layoutResourceId, ArrayList<ArticleHolder> results) {
super(pcontext, layoutResourceId, results);
context = pcontext;
ArticleList = results;
mInflater = LayoutInflater.from(context);
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.article_list_item, null);
holder = new ViewHolder();
holder.txtMain = (TextView) convertView.findViewById(R.id.textView1);
holder.txtblurb = (TextView) convertView.findViewById(R.id.textView2);
holder.pic_view = (ImageView) convertView.findViewById(R.id.imageView1);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.txtMain.setText(ArticleList.get(position).getTitle());
holder.txtblurb.setText(ArticleList.get(position).getMainText());
holder.pic_view.setContentDescription(context.getString(R.string.GenImgDesc));
UrlImageViewHelper.setUrlDrawable(holder.pic_view, ArticleList.get(position).getPicName(),R.drawable.ic_placeholder);
return convertView;
}
public int getCount() {
return ArticleList.size();
}
public ArticleHolder getItem(int position) {
return ArticleList.get(position);
}
public long getItemId(int position) {
return position;
}
static class ViewHolder {
TextView txtMain;
TextView txtblurb;
ImageView pic_view;
}
}
Thanks for your help, I'm really stuck.
Also, many thanks and props to koush and Jwsonic on Github for their work on UrlImageViewer. It's awesome.
Edit:
Here's my AsyncTask.doInBackground(). Sorry I took so long, I was out of town for a while without my computer. Thanks again for all your help.
protected Boolean doInBackground(String... params) {
boolean succeeded = false;
String downloadPath = null;
for(Integer num: feedArray){
downloadPath = "example.com/rss/"+Integer.toString(num);
doSax(downloadPath, num);
}
for(Integer num: feedArray){
currentDirtyFeedArticles = allDirtyArticles.get(num);
for(ArticleHolder dirtyArticle: currentDirtyFeedArticles){
dirtyArticle.setTitle(textCleaner(dirtyArticle.getTitle()));
//some text cleaning to make it look pretty
}
}
succeeded = true;
return succeeded;
}
Alright, so I took Jedi_warriors advice and attempted to add the notifyDataSetChanged(), but I couldn't figure it how to get it to mesh with my custom adapter. It put me on the right path though, as I realized the listview on the homescreen wasn't getting updated when I returned to it. This led me to look into having an onResume method, and after some trial and error, I ended up calling the code to associate the view with adapter in onResume. The trick here was that the app crashed when it opened because that code wasn't ready, so I prevented the code in onResume from running until after the app was ready to go. As far as I can tell, that seems to have fixed the problem. Thanks for all your help!
use in asynctask:
#Override
protected Void doInBackground(String... arg0) {
// TODO Auto-generated method stub
}
cause this has acces of Ui .
And For ListView use listview.notifyDataSetChanged();
hope this helps
Related
i have an activity to show user contacts list. if contact exists in my app i want to show a follow button , else i want to show a whatsapp and telegram icon to invite them.
when i open the activity every thing is fine as i want , but when i scroll down and come back up follow buttons and whats app icons get mixed for different contacts ! the users who had follow button may see whatsapp icon or the others may see follow button !
and everytime i scroll down and up again positions will change !
i should say all contacts name and mobile number are fixed and correct ! just images get mixed !
i know the problem is from my getView function but dont know how to fix it :(
how can i fix it ? tnx :)
Here is all of my adapter code :
public class LazyAdapterContactsList extends BaseAdapter {
private Activity activity;
private ArrayList<HashMap<String, String>> data;
private static LayoutInflater inflater=null;
public ImageLoader profileImageLoader;
HashMap<String, String> song;
public LazyAdapterContactsList(Activity a, ArrayList<HashMap<String, String>> d) {
activity = a;
data=d;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
profileImageLoader=new ImageLoader(activity.getApplicationContext());
}
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;
ViewHolder holder = null;
if(vi==null) {
vi = inflater.inflate(R.layout.contacts_list_row, null);
holder = new ViewHolder();
holder.listID = (TextView) vi.findViewById(R.id.MyContactslistIDPosition);
holder.name = (TextView) vi.findViewById(R.id.MyContactslistName);
holder.mobile = (TextView) vi.findViewById(R.id.MyContactslistMobileNumber);
holder.whatsAppIcon = (ImageView) vi.findViewById(R.id.MyContactsListWhatsApp);
holder.telegramIcon = (ImageView) vi.findViewById(R.id.MyContactsListTelegram);
holder.followBtn = (Button) vi.findViewById(R.id.MyContactsListFollowBtn);
holder.linearLayout = (LinearLayout) vi.findViewById(R.id.MyContactsthumbnail);
holder.profile_thumb_image = (ImageView) vi.findViewById(R.id.MyContactslist_image_profilephoto);
vi.setTag(holder);
}
else {
holder = (ViewHolder)vi.getTag();
}
song = data.get(position);
// Setting all values in listview
holder.listID.setText(song.get(MyContacts.KEY_ID));
holder.name.setText(song.get(MyContacts.KEY_NAME));
holder.mobile.setText(song.get(MyContacts.KEY_MOBILE));
if (song.get(MyContacts.KEY_USER_EXISTS).equals("1"))
{
if (song.get(MyContacts.KEY_THUMB_PROFILE_URL).equals("no")) {
} else {
profileImageLoader.DisplayImage(song.get(MyContacts.KEY_THUMB_PROFILE_URL), holder.profile_thumb_image);
}
}else {
holder.linearLayout.setVisibility(View.VISIBLE);
holder.followBtn.setVisibility(View.GONE);
}
return vi;
}
public static class ViewHolder {
public TextView textView ,listID ,name,mobile;
public ImageView whatsAppIcon ,telegramIcon;
public Button followBtn;
public LinearLayout linearLayout;
public ImageView profile_thumb_image;
}
}
Whatever action you do with any view of ListView like HIDE or VISIBLE in condition (if), you must have to set opposite HIDE or VISIBLE in opposite condition (else).
Try this solution just change code as per below:
if (song.get(MyContacts.KEY_USER_EXISTS).equals("1"))
{
holder.linearLayout.setVisibility(View.GONE);
holder.followBtn.setVisibility(View.VISIBLE);
if (song.get(MyContacts.KEY_THUMB_PROFILE_URL).equals("no")) {
} else {
profileImageLoader.DisplayImage(song.get(MyContacts.KEY_THUMB_PROFILE_URL), holder.profile_thumb_image);
}
}else {
holder.linearLayout.setVisibility(View.VISIBLE);
holder.followBtn.setVisibility(View.GONE);
}
Simple answer is, use floatingaction button. The position of the button will remain same, and you wouldn't feel annoyed
You view gets reused/recycled from system - so you have to (re)set profile_thumb_image every time in getView.
If not, there is a chance that you get a recycled view where the image was set in a previous call of getView.
First don't make this variable global
HashMap song;
Take this into ur getView() method if possible make it final.
As #coyer told
You view gets reused/recycled from system - so you have to (re)set profile_thumb_image every time in getView
Hope this help u...if any questions u can ask
I have a Runnable class that calculates model values for my composite list views, in that runnable there's a UI thread inside of a custom thread. There I have adapter's notifyDataSetChanged() call, but after notifyDataSetChanged() I try updating some TextView value in the main layout. The problem is when running TextView gets updated first and only then ListViews and getting updated. That means notifyDataSetChanged() of the Adapter custom class gets updated last which is not suitable for me. Is there any possibility to synchronize those screen updates?
Here's the sample code:
public class TestRunnable implements Runnable {
private TestAdapter adapter;
#Override
public void run() {
Context.runOnUiThread(new Runnable() {
#Override
public void run() {
adapter.notifyDataSetChanged();
MainActivity.setTextViewValue("Something...");
}
});
}
}
public class TestAdapter extends ArrayAdapter<TestModel> {
#Override
public View getView(final int position, View view, ViewGroup parent) {
View row = view;
TestHolder holder;
Boolean rowIsNew = false;
if (row == null) {
rowIsNew = true;
LayoutInflater layoutInflater = ((Activity) context)
.getLayoutInflater();
row = layoutInflater.inflate(layoutResourceId, parent, false);
holder = new TestHolder();
...
row.setTag(holder);
} else {
holder = (TestHolder) row.getTag();
}
TestModel testModel = data.get(position);
holder.property = testModel.property;
...
if (rowIsNew) {
holder.....setTypeface(...);
holder.....setTypeface(...);
}
return row;
}
}
I have revised the source code of ArrayAdapter and I see no way of executing code after it has called the onChanged() on it's observer so my answer would be:
Implement your own even on onChanged() being called
Call ListView.setAdapter with a brand new adapter with the new dataset
P.S. Number 1 is the optimum solution but number 2 is the easy solution, depending on your time and performance requirement use what you need, but I recommend taking some time and implementing Number 1.
I presume your MainActivity.setTextViewValue("Something..."); line is trying to print some data from the adapter and you're getting the old value, is that so?
I'm not 100% sure of this, perhaps someone else can help confirm this, but I think notifyDataSetChanged() only marks the current data on the adapter as dirty, so the adapter will know that it has to refresh the data, but it doesn't do it immediately when you do the call.
EDIT:
If my first paragraph is correct, you should try to update the text view with data from the data source instead of the adapter, this would be a nicer way to solve the problem.
I'm trying to add images in a ListView which has an ArrayAdapter. Fyi, the toList() is a conversion from iterator to a list of the given DBObject.
I override the View getView() and set a textview and an image.
private static class EventAdapter extends ArrayAdapter<DBObject> {
public EventAdapter(Context context, int resource, Iterable<DBObject> events) {
super(context, resource, toList(events));
}
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
LayoutInflater vi = LayoutInflater.from(getContext());
v = vi.inflate(R.layout.adapter_event_list, null);
DBObject event = getItem(position);
if (event != null) {
//Get the logo if any
if( ((DBObject)event.get("events")).containsField("logo") ){
String logoURL = ((DBObject)((DBObject)event.get("events")).get("logo")).get("0").toString();
ImageView eventLogo = (ImageView) v.findViewById(R.id.eventLogoList);
new setLogo().execute(logoURL, eventLogo);
}
TextView title= (TextView) v.findViewById(R.id.eventTitleList);
title.setText( ((DBObject)event.get("events")).get("title").toString() );
}
return v;
}
protected static <T> List<T> toList( Iterable<T> objects ) {
final ArrayList<T> list = new ArrayList<T>();
for( T t : objects ) list.add(t);
return list;
}
//setLogo() method here. See below
}
The text in the textview is fine. However the images are getting messed up. They seem to load in wrong places in the list. The route of the code is: 1)Get from the DB (async) 2)populate the ListView 3) while populating load each image(second async).
Here is the setLogo() AsyncTask which is inside the EventAdapter above:
private class setLogo extends AsyncTask<Object,Void,Bitmap>{
ImageView eventLogo = null;
#Override
protected Bitmap doInBackground(Object...params) {
try{
Bitmap eventImage = downloadBitmap((String) params[0]);
eventLogo = (ImageView) params[1];
return eventImage;
}
catch(Exception e){
e.printStackTrace();
return null;
}
}
#Override
protected void onPostExecute(Bitmap eventImage) {
if(eventImage!=null && eventLogo!=null){
eventLogo.setImageBitmap(eventImage);
}
}
}
I did so (using an Async) which I believe is the correct way to load images from urls. I saw this post on multithreading and from which I borrowed the downloadBitmap() method.
As explained above the images are loaded in wrong places of the ListView. What can be a robust way to load them?
Also the idea to pass the v.findViewById(R.id.eventLogoList) inside the AsyncTask is that the program will distinguish each adapter's ImageView but it seems it doesn't.
Update
After following the problem that is causing this mix I found this SO question.
I altered my code in order to check if the if is causing the problem.
//Get the logo if any
if( ((DBObject)event.get("events")).containsField("logo") ){
String logoURL = ((DBObject)((DBObject)event.get("events")).get("logo")).get("0").toString();
ImageView eventLogo = (ImageView) row.findViewById(R.id.eventLogoList);
//new setLogo().execute(logoURL, eventLogo);
TextView title= (TextView) row.findViewById(R.id.eventTitleList);
title.setText( "Shit happens" );
}
Let's say I have 40 items. The Shit happens is set on the fields that a logo field exists. If I scroll down/up the order changes and the text gets messed up. It is because the stack created inside the loop is small than the maximum of the list..I guess... I am still struggling.
PS: I found this easy library to load images asynchronously instead of DYI stuff.
Update 2
I added an else with a static url. Because of the time it take to the image to load they are still misplaced.
I would really go for a good library like Picasso.
It will handle all the hard part for you and it's very well written.
http://square.github.io/picasso/
Situation:
I have an android app where users do certain actions and those actions should be shown in a feed like Facebook's feed. Actions are 4 types - become friends with, follow somebody, comment and rate.
What I know how to do (see example below*)
I know how to extend BaseAdapter and customize its getView method so I can create a custom list item that I can later populate a listview with. Example - a list of a user's friends having their avatars, their names and a couple of buttons for each.
What I dont know how to do and Im asking your help with
I dont know how to make the feed show different customized layouts for the last 10 actions that happened - this guy became friends with that guy, then this guy rated that guy's work, then this guy commented on that thing...
How do I implement that?
*An example of creating a custom adapterable item and then populating a list view with a bunch of them:
public class MyProfileFriendAdapter extends BaseAdapter {
private ArrayList<String> userNames = new ArrayList<String>();
private ArrayList<String> userAvatarPaths = new ArrayList<String>();
private ArrayList<Integer> userIds = new ArrayList<Integer>();
private Context context;
private LayoutInflater layoutInflater;
private ImageButton iMyFriendsFriendAvatarImage;
private TextView tvMyFriendsListFriendName;
private Button bMyFriendsMessage;
private Button bMyFriendsUnfriend;
public MyProfileFriendAdapter(Context context, ArrayList<String> userNames, ArrayList<String> userAvatarPaths, ArrayList<Integer> userIds){
this.context = context;
this.userNames = userNames;
this.userAvatarPaths = userAvatarPaths;
this.userIds = userIds;
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return userIds.size();
}
#Override
public Object getItem(int arg0) {
return userIds.get(arg0) ;
}
#Override
public long getItemId(int position) {
return userIds.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null) {
convertView = layoutInflater.inflate(R.layout.item_adapterable_my_profile_friend, parent, false);
}
iMyFriendsFriendAvatarImage = (ImageButton) convertView.findViewById(R.id.iMyFriendsFriendAvatarImage);
tvMyFriendsListFriendName = (TextView) convertView.findViewById(R.id.tvMyFriendsListFriendName);
bMyFriendsMessage = (Button) convertView.findViewById(R.id.bMyFriendsMessage);
bMyFriendsUnfriend = (Button) convertView.findViewById(R.id.bMyFriendsUnfriend);
tvMyFriendsListFriendName.setText(userNames.get(position));
iMyFriendsFriendAvatarImage.setImageBitmap(BitmapFactory.decodeFile(userAvatarPaths.get(position)));
return convertView;
}
}
then in the activity that holds the list view I do as follows:
listOfFriends = (ListView) findViewById(R.id.lvMyProfileFriendsHolder);
MyProfileFriendAdapter myProfileFriendAdapter = new MyProfileFriendAdapter(this, userNames, userAvatarPaths, userIds);
listOfFriends.setAdapter(myProfileFriendAdapter);
So what do I do when I need to use various different adapterable items in the same list, according to the last actions that users did in my app?
Have a look at this:
http://www.jiahaoliuliu.com/2012/08/android-list-view-with-different-views.html
It's a siple exmaple.
For further information you can look for getViewCount & getItemViewType
I have a list of questions, and on each item there is a yes and no checkbox. This is created using an abstract class (because there are lots of lists), a child class, and the array adapter. Here is the abstract class code creating the list:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
List<Question> questions = getQuestions(1L);
setContentView(R.layout.activity_questions);
items = (ListView) findViewById(R.id.items);
adapter = new QuestionsAdapter(this, getCurrentContext(), questions, 1L, getDbData());
items.setAdapter(adapter);
}
Here is the QuestionsAdapter:
public View getView(int position, final View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.row_questions, parent, false);
holder = new QuestionHolder();
holder.question = (TextView) row.findViewById(R.id.question);
holder.yes = (CheckBox) row.findViewById(R.id.yes);
holder.no = (CheckBox) row.findViewById(R.id.no);
row.setTag(holder);
} else {
holder = (QuestionHolder) row.getTag();
}
Question question = questions.get(position);
holder.question.setText(question.getQuestion());
setStateCheckboxes(holder, question);
holder.yes.setTag(getItem(position));
holder.no.setTag(getItem(position));
holder.yes.setOnCheckedChangeListener(listen);
holder.no.setOnCheckedChangeListener(listen);
return row;
}
I have to create the holder to be able to have a listview with checkboxes. Up to this point, everything is working fine.
I then have a view for each element on the list. It is very basic:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dbData = new DbData(this);
this.setContentView(R.layout.single_question);
CheckBox yes = (CheckBox) findViewById(R.id.yes_single);
CheckBox no = (CheckBox) findViewById(R.id.no_single);
}
In this view I can change the status of the checkboxes. This change is reflected in the db, but when I return to the main list, it is only reflected on refresh. I have overridden the onRestart():
#Override
protected void onRestart() {
// Change this
questions = getQuestions(1L);
adapter.notifyDataSetChanged();
super.onRestart();
}
The adapter is pulling the data from the questions ArrayList, so I am repolling it and the notifying the adapter that the data has changed, but this does not change my view. If I refresh the view, then the current state of everything is updated. I know this is a long question, but any help would be appreciated.
Use the onResume() method to call through to notifyDataSetChanged.
You will need code to manage whether the adapter has been created. You don't want to be calling it too early in the process or you run the risk of exceptions.
Generally I do this through a method that is responsible for initialising the adapter and adding it to the listview, which makes it safer to call this irrespective of whether the activity/fragment is starting for the first time or being returned to from another activity (e.g. through the back button), as well as avoiding recreating the adapter or replacing an existing adapter on the listview.
Also better to call the super.onRestart() before your own code.