How to use livedata and viewmodel with a viewholder as Lifecycle Owner?
Asked Answered
E

2

14

I'm having a recyclerview (verticalRV) which scrolls vertically. Each item in this recyclerview(horizontalRV) is a Horizontal recyclerview.

Inside the verticalRV itemViewHodler im trying to fetch data from the viewmodel and observe for any chages and update the horizontalRV adapter accordingly.

But the observers is onChanged method is not getting called.

I have implemented the LifecycleOwner interface to manage the the lifecyle of the view holder with livedata and setting the state accordingly form the adapter of verticalRV

public class VeritcalRVHolderItem implements LifecycleOwner {
    private static final String TAG = LDFeedListAdapterHolder.class.getSimpleName();
    private final FragmentActivity activity;
    private final RvHorizontalListAdapter adapter;
    private RecyclerView rvHorizontalList;


    public VeritcalRVHolderItem(Context context, View itemView, FragmentActivity activity) {
        super(context, itemView);
        this.activity = activity;
        rvHorizontalList = itemView.findViewById(R.id.rvHorizontalList);
        LinearLayoutManager layout = new LinearLayoutManager(getContext(), LinearLayout.HORIZONTAL, false);
        rvHorizontalList.setLayoutManager(layout);
        adapter = new RvHorizontalListAdapter(this.activity);
        rvHorizontalList.setAdapter(adapter);
        LDViewModel LDViewModel = ViewModelProviders.of(activity).get(LDViewModel.class);
        LDViewModel.getTopicsForFeed().observe(this, new Observer<List<Topic>>() {
            @Override
            public void onChanged(List<Topic> topics) {
                //adding live discussion model at first position
                adapter.updateLiveList(topics);
                adapter.notifyItemChanged(0);
                Log.d(TAG, "discussion model calls");
            }
        });
    }

    private LifecycleRegistry lifecycleRegistry;

    public void onAppear() {
        lifecycleRegistry.markState(Lifecycle.State.CREATED);
    }

    public void onDisappear() {
        lifecycleRegistry.markState(Lifecycle.State.DESTROYED);
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return lifecycleRegistry;
    }

}

Please let me know what am I missing here.

Esmerolda answered 22/2, 2019 at 10:58 Comment(0)
S
15

There are 2 approaches to the problem, one is the Shyak's answer, that manages to observe the changes outside the adapter and notifies the change through a change of the data backing the list. This is actually respecting the pattern of the recycler views and adapters.

Sometimes though, we want to observe and show on the recylerview some data from a model, but also some external events and/or data. Instead of combining all these informations on a new model that comprises the aggregated data, it could be handy to observe some of the changes directly on the adapter itself.

In this case you can add an observer to the ViewHolder and react to the changes in this way:

  1. Pass the LiveData on the constructor of your adapter class:
class MyAdapter(private var data: LiveData<Int>) : RecyclerView.Adapter<MyViewHolder>() {
  1. add an observer at the creation fo the viewHolder:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val holder = MyViewHolder(
        LayoutInflater.from(parent.context).inflate(
            R.layout.my_layout, parent,
            false
        )
    )

    data.observe(holder.itemView.context as LifecycleOwner, Observer {
        // action to be performed by the observer
    })

    return holder
}
  1. Set the right visual state for the viewHolder when the viewHolder is associated with a model element
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    if (data.value!! >= 0 && data.value == position) {
        holder.setSelected(true) // or whatever is visually necessary
    } else {
        holder.setSelected(false) // or whatever is visually necessary
    }
}

Things to watch out: holder.itemView.context as LifecycleOwner this basically means that the recyclerview is inside a fragment that is a lifecycle owner.

This approach can be efficient cause ViewHolder are reused, so we don't have to create new observers for the various elements of the list.

Shawm answered 10/12, 2019 at 17:24 Comment(6)
It should be taken into account that you shouldn't use lifecycleOwner from Fragments but viewLifecycleOwner instead. Therefore you'd need to check if the context is a fragment and then get the viewLifecycleOwner.Nuggar
Or just pass a LifeCycleOwner as a constructor arg to the adapter.Headfirst
For the second approach, doesn't the view holder get recycled when it scrolls out of view?Waterspout
@Waterspout i don't see the problem.Shawm
@AndrewBloom, I guess there isn't an issue after all... My concern was that when an item view got recycled, its LiveData subscription would be disposed, and it would no longer respond to changes. But then I realized that the next time the view becomes visible, it will be rebound. As long as you subscribe in onBindViewHolder and not in onCreateViewHolder, it will work.Waterspout
How about a viewholder with its own lifecycle and it's own Viewmodel? Check this library github.com/vivekgupta4Git/AwakeningViewHolderLifeWinnebago
W
2

From my perspective, it's better to observe the data into your Fragment or Activity class and pass the data to Recyclerview. In Recyclerview override the method getItemViewType to handle the vertical item and horizontal item.

Example:

ViewModel

public class ViewModel extends AndroidViewModel {
    private MutableLiveData<Model> modelMutableLiveData;

    public ViewModel(@NonNull Application application) {
        super(application);
        modelMutableLiveData = new MutableLiveData<>();
    }

    public MutableLiveData<Model> getModelMutableLiveData() {
        return modelMutableLiveData;
    }

    public final void yourMethod(Model model) {
        // Do something
    }
}

Fragment class

public class Fragment extend BaseFragment {
    private void initViewModelData() {
        viewModel.getModelMutableLiveData().observe(this, new Observer<Model>() {
            @Override
            public void onChanged(@Nullable Model model) {
                if (model != null) {
                    modelList.add(model);
                    adapter.notifyItemInserted(modelList.size()- 1);
                }
            }
        });
    }
}

RecyclerViewAdater

class RecyclerViewAdater extend Adapter<ViewHolder>{
    @Override
    public int getItemViewType(int position) {
        return mDataList.get(position).getContainerType();
    }
}

Based on this you can write your RecyclerView code

Walford answered 23/2, 2019 at 6:26 Comment(1)
Shayak I guess you got it wrong, these vertical and horizontal receyclerviews are different , Vertaical one contains the horizontal ones. But like pointed my current solution is passing data from activity to 1st recyceler view and then that will pass to the next recyclerview, which is little messy. Thought handling this in the viewholder is a better way but it didn't seem to work, could be bcoz the viewholders are recycled by the recyclerview.Esmerolda

© 2022 - 2024 — McMap. All rights reserved.