12

I have a separate thread running to get data from the internet. After that, I would like to update the ListView in the main thread by calling adapter.notifyDataSetChanged(). But it does not work. Any workaround for that? Thanks.

Wilson
  • 313
  • 1
  • 3
  • 10

7 Answers7

13

Use AsyncTask ( http://developer.android.com/reference/android/os/AsyncTask.html ).

Invoke adapter.notifyDataSetChanged() in onPostExecute(...) method.

For more details, please read this: http://android-developers.blogspot.com/2009/05/painless-threading.html

Damian Kołakowski
  • 2,731
  • 22
  • 25
12

Overall good advice is http://android-developers.blogspot.com/2009/05/painless-threading.html

Personally I use my custom thread (a class extending Thread ) but send response to the UI thread through a Message. So in the thread's run() function there is:

Message msg;
msg = Message.obtain();
msg.what = MSG_IMG_SET;                     
mExtHandler.sendMessage(msg);

The UI thread defines a message handler.

private Handler mImagesProgressHandler;

public void onCreate(Bundle bundle) {

    mImagesProgressHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {             
            case LoadImagesThread.MSG_IMG_SET:
                mArrayAdapter.setBitmapList(mImagesList);
                mArrayAdapter.notifyDataSetChanged();
                break;
            case LoadImagesThread.MSG_ERROR:
                break;
            }
            super.handleMessage(msg);
        }
    };                 

This is actually easier than AsyncTask.

Scott
  • 3,663
  • 8
  • 33
  • 56
Yar
  • 4,543
  • 2
  • 35
  • 42
  • I forgot to mention: you can also add an Object to the message like: msg.obj = someBitmap; and receive it in the handler (which can for example update an array or list with the received bitmap and notify the adapter that the list's underlying data have changed: mArrayAdapter.notifyDataSetChanged();). In this case no race conditions can happen. – Yar Feb 16 '11 at 09:09
  • 2
    Beware - Here be dragons! This inner Handler class has an implicit reference to the outer class' this. Better to make that Handler static and take a WeakReference to the outer class. – Michael Krause Jul 14 '15 at 22:17
  • 2
    @MichaelKrause, why is that a problem here? The handler's lifecycle will not be longer than the activity's – Gavriel Sep 03 '15 at 15:31
  • @AliTofigh It's a reference to the `mImagesProgressHandler` – ucMedia Dec 31 '18 at 12:32
10

Or, post a message to the listview's message queue (which would execute on the UI thread):

list.post(new Runnable() {                  
    @Override
    public void run() {
       adapter.notifyDataSetChanged();

    }
}); 
Jay Sidri
  • 6,271
  • 3
  • 43
  • 62
4

Assume yourActivity is the activity that your widget has been placed into it, yourView is The widget and adapter is The widget's adapter:

yourActivity.runOnUiThread(new Runnable() {
 public void run() {    
         try{
                adapter.notifyDataSetChanged();
         }
         catch{}
 }
}
Mostafa Lavaei
  • 1,960
  • 1
  • 18
  • 27
3

You can use a combination of RxJava and Retrofit for this purpose.

Here's how you can do it. I will be using the GitHub API for the example.

Create a Java interface for your HTTP API.

public interface GitHubService {
    @GET("users/{user}/repos")
    Observable<List<Repo>> listRepos(@Path("user") String user);
}

Using Observable will convert the response into a stream. Every event being a List of repos.

Use Retrofit class to generate an implementation of the GitHubService interface. You may or may or may not provide a custom HTTP Client.

Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://api.github.com/")
            .client(okHttpClient) // OkHttp Client
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();

GitHubService service = retrofit.create(GitHubService.class);

Now comes the Rx part. You have to add a subscriber to listen to the responses sent back by the server. In other words, react to it.

service.listRepos("octocat")
.subscribeOn(Schedulers.io()) // 1
.observeOn(AndroidSchedulers.mainThread()) // 2
.flatMap(Observable::from) // 3
.subscribe(repoList -> { // 4
    // Update the list view
    // notifyDataSetChanged
});

Here's what the lines commented with numbers are doing -

1 - It tells the OS which thread to be used to make the call. We are choosing the IO thread for that.

2 - It tells the OS which thread to be used to listen for the response. We do that on the main thread, and update the UI on receiving a response.

3 - This line demonstrates the true magic of Rx. This simple little line converts a list of responses to single objects. Hence we will be reacting to every object instead of the whole list altogether. You can read more about it here.

4 - This line actually defines what kind of 'reaction' will be shown to the event. You can update the adapter here.

A more elaborate subscriber looks like this -

new Subscriber<Repo>() {
    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onNext(Repo repo) {

    }
}

P.S. - I have used lambdas in the examples above. You can add that through here.

padfoot27
  • 517
  • 9
  • 13
0

This work for me

this.runOnUiThread(() -> {
            chatAdapter.notifyItemInserted(chatMessages.size()-1);

            binding.chatMessages.scrollToPosition(chatMessages.size()-1);
        });

Where this refers to the current Activity which contains the adapter and recyclerview.

M Umer
  • 353
  • 6
  • 13
-2

The trick here is the position where you put the

mAdapter.notifyDataSetChanged();

Here a simple example :

mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
mAdapter = new myAdapter(......);
mAdapter.notifyDataSetChanged();
mRecyclerView.setAdapter(mAdapter);

this work for me perfectly.

Samir Thebti
  • 305
  • 3
  • 10