36

There are many examples how to push new list to adapter on LiveData change.

I'm trying to update one row (e.g number of comments for post) in the huge list. It would be stupid to reset whole list to change only one field.

I am able to add observer onBindViewHolder, but I can't understand when should I remove observer

@Override
public void onBindViewHolder(ViewHolder vh, int position) {
    Post post = getPost(position);
    vh.itemView.setTag(post);
    post.getLiveName().observeForever(vh.nameObserver);
    ... 
}
Andrew Matiuk
  • 924
  • 1
  • 7
  • 21

5 Answers5

20

Like @Lyla said, you should observe the whole list as LiveData in Fragment or Activity, when receive changes, you should set the whole list to the adapter by DiffUtil.

Fake code:

PostViewModel {
    LiveData<List<Post>> posts;  // posts comes from DAO or Webservice
}

MyFragment extends LifecycleFragment {
    PostAdapter postAdapter;

    ...

    void onActivityCreated() {
        ...
        postViewModel.posts.observer(this, (postList) -> {
            postAdapter.setPosts(postList);
        }
    }       
}

PostAdapter {
    void setPosts(List<Post> postList) {
        DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {...}
        ...
    }
}
Spark.Bao
  • 5,573
  • 2
  • 31
  • 36
  • 3
    using MyFragment as 'this' in the observer is not recommended. The reason behind this is when the fragment goes in backstack, the fragment is not destroyed. Hence, the same fragment with different instances will observe this live data. Better way is to use getViewLifecycleOwner. – Karan Dhillon Jun 12 '20 at 05:10
15

Using DiffUtil might help with updating one row in a huge list. You can then have LiveData wrap the list of comments instead of a single comment or attribute of a comment.

Here's an example of using DiffUtil within a RecyclerView adapter and the list LiveData observation code in the fragment.

Lyla
  • 2,767
  • 1
  • 21
  • 23
  • 1
    Hi Lyla, your link to the "list LiveData Observaion code in the fragment" is not working. It's missing the "/ui/" in the URL. The correct URL is [ProductListFragment.java#L75](https://github.com/googlesamples/android-architecture-components/blob/master/BasicSample/app/src/main/java/com/example/android/persistence/ui/ProductListFragment.java#L75) - Luke – lukegjpotter Nov 17 '17 at 15:20
  • Fixed the link! Thanks! – Lyla Nov 20 '17 at 18:11
4

Use Transformations.switchMap() to swap the underlying Post object. Then there is no need to remove and re-add observers when the cell is recycled.

@Override
public void onBindViewHolder(PostViewHolder vh, int position) {
    Post post = getPost(position);
    vh.bind(post);
}

Then in your ViewHolder class

public class PostViewHolder extends RecyclerView.ViewHolder {
    private final MutableLiveData<Post> post = new MutableLiveData<>();

    public PostViewHolder(View itemView) {
        super(itemView);

        LiveData<String> name = Transformations.switchMap(post, new Function<Post, LiveData<String>>() {
            @Override
            public LiveData<String> apply(Post input) {
                return input.getLiveName();
            }
        });

        name.observeForever(new Observer<String>() {
            @Override
            public void onChanged(@Nullable String name) {
                // use name
            }
        });
    }

    public void bind(Post post) {
        post.setValue(post);
    }
}
tom-pratt
  • 338
  • 4
  • 9
0

I think you should use LiveAdapter for RecyclerView Adapter instead of creating an extra class for the adapter.

It has DiffUtil implementation as well, so only single item will be updated. and without calling notifyDatasetChange.

// Kotlin sample
LiveAdapter(
            data = liveListOfItems,
            lifecycleOwner = this@MainActivity,
            variable = BR.item )
           .map<Header, ItemHeaderBinding>(R.layout.item_header) {
               onBind{

               }
               onClick{

               }
               areContentsTheSame { old: Header, new: Header ->
                   return@areContentsTheSame old.text == new.text
               }
               areItemSame { old: Header, new: Header ->
                   return@areContentsTheSame old.text == new.text
               }
           }
           .map<Point, ItemPointBinding>(R.layout.item_point) {
               onBind{

               }
               onClick{

               }
               areContentsTheSame { old: Point, new: Point ->
                   return@areContentsTheSame old.id == new.id
               }
               areItemSame { old: Header, new: Header ->
                   return@areContentsTheSame old.text == new.text
               }
           }
           .into(recyclerview)
RBK
  • 2,481
  • 2
  • 30
  • 52
-1

add context to your adapterClass construstor : AdpaterClass(context: Context)

then smart cast the context to AppCompactActivity

livedata.observe(context as AppCompatActivity, Observer {it ->

        //perform action on it(livedata.value)
    })

when calling the adapter from anywhere activity, fragment pass the context into the adpater

X-Black...
  • 1,376
  • 2
  • 20
  • 28