0

Im trying to have my ListView add more items once the bottom of the list has been reached. The problem I keep having is the list freezes once it reaches the bottom instead updating the view. In EndlessScrollListenener, i check if the last item is visible, if it is, we add more posts to our List posts. Then I do a notifyDataSetChanged() but it is not working. Any help?

Here is my custom EndlessScrollListener class:

    import java.util.ArrayList;
    import java.util.List;

    import sukh.app.ireddit.PostsFragment.PostAdapter;
    import android.widget.AbsListView;
    import android.widget.AbsListView.OnScrollListener;
    import android.widget.ArrayAdapter;

public class EndlessScrollListener implements OnScrollListener {
List<Post> posts;
PostsHolder postsHolder;
ArrayAdapter<Post> postAdapter;
private int count, incrementSize;

public EndlessScrollListener() {
}
public EndlessScrollListener(List<Post> posts, PostsHolder postsHolder, ArrayAdapter<Post> adapter){
    this.posts = posts;
    this.postsHolder = postsHolder;
    this.postAdapter = adapter;
    count = incrementSize = adapter.getCount();
}
public void onScroll(AbsListView view, int firstVisible, int visibleCount, int totalCount) {
    // TODO Auto-generated method stub
    boolean loadMore =   
            firstVisible + visibleCount >= totalCount;

        if(loadMore) {
            count += incrementSize; // or any other amount
            new Thread(){
                public void run(){                      
                    posts.addAll(postsHolder.fetchMorePosts());
                }
            }.start();  
            postAdapter.notifyDataSetChanged();
        }
}

@Override
public void onScrollStateChanged(AbsListView arg0, int arg1) {
    // TODO Auto-generated method stub

}
}

my PostFragment class

import java.util.ArrayList;
import java.util.List;

import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

/**
 * While this looks like a lot of code, all this class
 * actually does is load the posts in to the listview.
 */
public class PostsFragment extends Fragment{

    ListView postsList;
    ArrayAdapter<Post> adapter;
    Handler handler;

    String subreddit;
    List<Post> posts;
    PostsHolder postsHolder;

    public PostsFragment(){
        handler=new Handler();
        posts=new ArrayList<Post>();
    }    

    public static Fragment newInstance(String subreddit){
        PostsFragment pf=new PostsFragment();
        pf.subreddit=subreddit;
        pf.postsHolder=new PostsHolder(pf.subreddit);        
        return pf;
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                             ViewGroup container,
                             Bundle savedInstanceState) {
        View v=inflater.inflate(R.layout.posts
                                , container
                                , false);
        postsList=(ListView)v.findViewById(R.id.posts_list);
        return v;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {    
        super.onActivityCreated(savedInstanceState);
        initialize();
    }

    private void initialize(){
        // This should run only once for the fragment as the
        // setRetainInstance(true) method has been called on
        // this fragment

        if(posts.size()==0){

            // Must execute network tasks outside the UI
            // thread. So create a new thread.

            new Thread(){
                public void run(){
                    posts.addAll(postsHolder.fetchPosts());

                    // UI elements should be accessed only in
                    // the primary thread, so we must use the
                    // handler here.

                    handler.post(new Runnable(){
                        public void run(){
                            createAdapter();
                        }
                    });
                }
            }.start();
        }else{
            createAdapter();
        }
    }

    /**
     * This method creates the adapter from the list of posts
     * , and assigns it to the list.
     */
    private void createAdapter(){

        // Make sure this fragment is still a part of the activity.
        if(getActivity()==null) return;

        adapter=new PostAdapter(posts);
        postsList.setAdapter(adapter);
        postsList.setOnScrollListener(new EndlessScrollListener(posts, postsHolder, adapter));
    }

    protected class PostAdapter extends ArrayAdapter<Post> {

        public int incrementSize
                 , count = 0;

        public PostAdapter(List<Post> posts){
            super(getActivity(), R.layout.post_item, posts);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if(convertView == null){
                convertView = getActivity().getLayoutInflater().inflate(R.layout.post_item, null);
            }

            Post thePost = getItem(position);

            TextView postTitleTextView = (TextView) convertView.findViewById(R.id.post_title);
            postTitleTextView.setText(thePost.getTitle());

            TextView postDetailsTextView = (TextView) convertView.findViewById(R.id.post_details);
            postDetailsTextView.setText(thePost.getDetails());

            TextView postScoreTextView = (TextView) convertView.findViewById(R.id.post_score);
            postScoreTextView.setText(thePost.getScore());

            return convertView;
        }


    }

}

my PostsHolder class:

import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONObject;

import android.util.Log;

/**
 * This is the class that creates Post objects out of the Reddit
 * API, and maintains a list of these posts for other classes
 */
public class PostsHolder {    

    /**
     * We will be fetching JSON data from the API.
     */
    private final String URL_TEMPLATE=
                "http://www.reddit.com/r/SUBREDDIT_NAME/"
               +".json"
               +"?after=AFTER";

    String subreddit;
    String url;
    String after;

    PostsHolder(String sr){
        subreddit=sr;    
        after="";
        generateURL();
    }

    /**
     * Generates the actual URL from the template based on the
     * subreddit name and the 'after' property.
     */
    private void generateURL(){
        url=URL_TEMPLATE.replace("SUBREDDIT_NAME", subreddit);
        url=url.replace("AFTER", after);
    }

    /**
     * Returns a list of Post objects after fetching data from
     * Reddit using the JSON API.
     * 
     * @return
     */
    ArrayList<Post> fetchPosts(){
        String raw=RemoteData.readContents(url);
        ArrayList<Post> list=new ArrayList<Post>();
        try{
            JSONObject data=new JSONObject(raw)
                                .getJSONObject("data");
            JSONArray children=data.getJSONArray("children");

            //Using this property we can fetch the next set of
            //posts from the same subreddit
            after=data.getString("after");

            for(int i=0;i<children.length();i++){
                JSONObject cur=children.getJSONObject(i)
                                    .getJSONObject("data");
                Post p = new Post();
                p.title=cur.optString("title");
                p.url=cur.optString("url");
                p.numComments=cur.optInt("num_comments");
                p.points=cur.optInt("score");
                p.author=cur.optString("author");
                p.subreddit=cur.optString("subreddit");
                p.permalink=cur.optString("permalink");
                p.domain=cur.optString("domain");
                p.id=cur.optString("id");
                if(p.title!=null)
                    list.add(p);
            }
        }catch(Exception e){
            Log.e("fetchPosts()",e.toString());
        }
        return list;
    }

    /**
     * This is to fetch the next set of posts
     * using the 'after' property
     * @return
     */
    List<Post> fetchMorePosts(){
        generateURL();
        return fetchPosts();
    }
}
sanghas26
  • 161
  • 1
  • 2
  • 10

1 Answers1

0

You should notify postAdapter.notifyDataSetChanged(); after posts are added. Now Thread runnable could end afterpostAdapter.notifyDataSetChanged().

michal.luszczuk
  • 2,883
  • 1
  • 15
  • 22
  • You mean move the postAdapter.notifyDataSetChanged() into the thread? – sanghas26 Feb 03 '14 at 20:37
  • You must use handler to back this `notifyDataSetChanged` call to `UI Thread` because you can't call notifyDataSetChanged directly from `Thread` – michal.luszczuk Feb 03 '14 at 20:38
  • Things [like this](http://stackoverflow.com/questions/8665676/how-to-use-notifydatasetchanged-in-thread) or using Handler like I said – michal.luszczuk Feb 03 '14 at 20:40
  • If the OP used an AsyncTask, it could be a tad cleaner than using the runOnUiThread. Could load the posts in doInBackground and in onPostExecute(), just add them to the list and call notifyDataSetChanged(). – natez0r Feb 03 '14 at 20:41
  • So with AsyncTask will go in my EndlessScrollListener class? – sanghas26 Feb 03 '14 at 20:44