3

So I have a list of strings (ISBNs), and I need to fill a listview with the objects (Book objects) associated with these strings. The problem is that the function I have to get the Book object using the string takes time, and so to get, say 30 books, the wait approaches 4 or 5 seconds.

One approach I've thought of is to get the Book objects one at a time, and to add them to the list as I get them. But this process will freeze the UI until it's done adding them all. If I try to put this process in a new thread, it won't let me add to the any UI objects (since it's from another thread). If I try to put it in an AsyncTask, I can't access the ListView since it's in the MainActivity class.

There must be a way to dynamically update a UI element, I'm sure I've seen it done. Any suggestions?

EDIT:

This is the code I'm using to actually add items to the list:

//List view and adapter setup
listView = (ListView) findViewById(R.id.listViewCheckout);
bookAdapter = new SearchBookAdapter(getApplicationContext(), R.layout.search_row_layout);
listView.setAdapter(bookAdapter);

for(int i = 0; i < searches.size(); i++) {

    //Get the book
    Book book = BackendFunctions.getBookFromISBN(fbSnapshot, searches.get(i));

    //Assign data to the adapter variables
    Bitmap cover = book.getCover();
    String title = book.getTitle();
    String author = book.getAuthor();

    //Add data to the adapter and set the list
    SearchBookDataProvider dataProvider = new SearchBookDataProvider(cover, title, author);
    bookAdapter.add(dataProvider);
    bookAdapter.notifyDataSetChanged();
}
Vedvart1
  • 302
  • 4
  • 21
  • 1
    try passing book list from asynctask to mainActivity using an interface – Aswin P Ashok Dec 04 '17 at 04:33
  • 1
    post your code ? – Sush Dec 04 '17 at 04:35
  • 2
    https://stackoverflow.com/questions/12575068/how-to-get-the-result-of-onpostexecute-to-main-activity-because-asynctask-is-a – OneCricketeer Dec 04 '17 at 04:36
  • Do you have to use an Asynctask? If you are parsing JSON data in there, you can try Retrofit with Gson – OneCricketeer Dec 04 '17 at 04:37
  • I will suggest using a callback mechanism for this – Ezio Dec 04 '17 at 04:50
  • @Ezio How would a callback help me here? – Vedvart1 Dec 04 '17 at 04:55
  • 1
    Pass Listview instance to your AsyncTask. – nhoxbypass Dec 04 '17 at 04:56
  • Pass an interface to the async task(or background thread) and have your activity implement the same interface, as soon as you are done downloading and parsing the JSON, call the method of that interface and pass the parsed json object to that activity and populate the listview. Please refer to this answer for details https://stackoverflow.com/questions/42407909/async-task-which-is-trigger-in-different-class-and-callback-function-is-implemen/42408862#42408862. – Ezio Dec 04 '17 at 04:58
  • You should really move `notifyDataSetChanged` outside the loop – OneCricketeer Dec 04 '17 at 05:14

5 Answers5

1

Can you make some changes to your code like this it simple it think it will work

//List view and adapter setup
listView = (ListView) findViewById(R.id.listViewCheckout);
bookAdapter = new SearchBookAdapter(getApplicationContext(), R.layout.search_row_layout);
SearchBookDataProvider dataProvider;
listView.setAdapter(bookAdapter);

 new AsyncTask() {
            @Override
            protected Object doInBackground(Object[] objects) {
                for(int i = 0; i < searches.size(); i++) {

                //Get the book
                Book book = BackendFunctions.getBookFromISBN(fbSnapshot, searches.get(i));

                //Assign data to the adapter variables
                Bitmap cover = book.getCover();
                String title = book.getTitle();
                String author = book.getAuthor();

                //Add data to the adapter and set the list
                dataProvider = new SearchBookDataProvider(cover, title, author);
                bookAdapter.add(dataProvider);
                }
            }
            @Override
            protected void onPostExecute(Object o) {
                if (bookAdapter!= null) {
                    bookAdapter.notifyDataSetChanged();
                }
                super.onPostExecute(o);
            }
 }.execute();
Vivek Barai
  • 1,338
  • 13
  • 26
  • A problem occurs in that when I run this, I get an error saying that "Only the original thread that created a view hierarchy can touch its views" on bookAdapter.add(dataProvider). If I move this line into onPostExecute(), the program freezes up again like it did in the first place. – Vedvart1 Dec 04 '17 at 21:43
  • @Vedvart1 you the above same code made any change to code lines? – Vivek Barai Dec 05 '17 at 04:53
0

you can use TimerTask to update the listview or runUiThread() or doInBackground(). But remember should use notifysetChanges() when you update the list.

Peter
  • 587
  • 6
  • 16
0

Step 1: Declare a Executer service

 private ExecutorService mExecuterService = null;

Step 2:Declare a class for your list iteration and view update

  class ListViewUpdater implements Runnable{

            public ListViewUpdater(/* if you need you can pass list params here */){

            }

            @Override
            public void run() {
                for(int i = 0; i < searches.size(); i++) {

                    //Get the book
                    Book book = BackendFunctions.getBookFromISBN(fbSnapshot, searches.get(i));

                    //Assign data to the adapter variables
                    Bitmap cover = book.getCover();
                    String title = book.getTitle();
                    String author = book.getAuthor();



                }
//below code is important for Updating UI ,you should run UI Updates in UI thread
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        bookAdapter.notifyDataSetChanged();
                    }
                });
            }
        }

Step 3: Initilize and call below methods

//Add data to the adapter and set the list
    SearchBookDataProvider dataProvider = new SearchBookDataProvider(cover, title, author);
   bookAdapter.add(dataProvider);
    mExecuterService = Executors.newSingleThreadExecutor()
    mExecuterService.execute(new ListViewUpdater());

It may solve your problems.

CLIFFORD P Y
  • 16,974
  • 6
  • 30
  • 45
  • The problem I’m having is that no class except the one that created the ListView can touch it, so this ListViewUpdated class couldn’t modify it. – Vedvart1 Dec 04 '17 at 05:52
  • Using UI objects in another thread you need to use `runOnUiThread(new Runnable() { @Override public void run() { bookAdapter.notifyDataSetChanged(); } });` – CLIFFORD P Y Dec 04 '17 at 05:55
  • @CLIFFORD P Y Is the code in the runOnUiThread section guaranteed to only run after the long running background code in run() has completed? – AJW Nov 10 '21 at 15:59
  • @AJW the code in the run() method execute serially. – CLIFFORD P Y Nov 10 '21 at 17:42
0

I think if you are open to use a open source project ,then my suggestion will be use RX-JAVA.Which is based in reactive and push based model.

link for rx-java.

rx-java example.

gati sahu
  • 2,576
  • 2
  • 10
  • 16
-1

You can get the list of the books in a Thread and send a Broadcast with the data received. Register a broadcast receiver in your MainActivity class and update the Adapter in the receiver. That should not freeze the UI.

EDIT -

  BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        Book book = (Book)intent.getSerializableExtra("Book");
        SearchBookDataProvider dataProvider = new SearchBookDataProvider(cover, title, author);
        bookAdapter.add(dataProvider);
        bookAdapter.notifyDataSetChanged();
    }
};

Thread thread = new Thread(){
    @Override
    public void run()
    {
        for(int i = 0; i < searches.size(); i++) {

            //Get the book
            Book book = BackendFunctions.getBookFromISBN(fbSnapshot, searches.get(i));

            Intent intent = new Intent();
            intent.setAction("Book Received");
            intent.putExtra("Book",book);
            sendBroadcast(intent);
        }
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    listView = (ListView) findViewById(R.id.listView);
    listView.setAdapter(bookAdapter);
    registerReceiver(broadcastReceiver,new IntentFilter("Book Received"));
    thread.start();

}
Kshitij
  • 392
  • 2
  • 13