How to communicate with fragments inside FragmentStatePagerAdapter? - java

I'm trying to update the content of all the fragments inside TabAdapter class based on the data input in the GradeFragment. In the GradeFragment I'm updating Grupe object but I'm unable to notify other fragments when it happens. I've tried to use an interface but I can't attach it to the GradeFragment. How can I achieve this?
Here's my TabAdapter class:
public class TabAdapter extends FragmentStatePagerAdapter {
private int totalTabs;
private Grupe grupe;
private GradeFragment gradeFragment;
public TabAdapter(FragmentManager fm, int totalTabs, Grupe grupe) {
super(fm);
this.totalTabs = totalTabs;
this.grupe = grupe;
}
// this is for fragment tabs
#Override
public Fragment getItem(int position) {
Bundle bundle = new Bundle();
bundle.putSerializable("gr", grupe);
switch (position) {
case 0:
gradeFragment = new GradeFragment();
gradeFragment.setArguments(bundle);
return gradeFragment;
case 1:
GradeAverageFragment gradeAverageFragment = new GradeAverageFragment();
gradeAverageFragment.setArguments(bundle);
return gradeAverageFragment;
case 2:
GroupAverageFragment groupAverageFragment = new GroupAverageFragment();
groupAverageFragment.setArguments(bundle);
return groupAverageFragment;
case 3:
TopGradesFragment topGradesFragment = new TopGradesFragment();
topGradesFragment.setArguments(bundle);
return topGradesFragment;
default:
return null;
}
}
// this counts total number of tabs
#Override
public int getCount() {
return totalTabs;
}
}

Set GradeFragment as target fragment for each other Fragment (GradeAverageFragment, GroupAverageFragment, TopGradesFragment ...) like this:
GradeAverageFragment gradeAverageFragment = new GradeAverageFragment();
gradeAverageFragment.setArguments(bundle);
gradeAverageFragment.setTargetFragment(gradeFragment, 0);
return gradeAverageFragment;
Simple prototype:
interface OnUpdateGrupeListener {
void onGrupeUpdated(Grupe newGrupe);
}
interface UpdateGrupeHost {
void onAttachUpdateGrupeListener(OnUpdateGrupeListener listener);
void onDetachUpdateGrupeListener(OnUpdateGrupeListener listener);
}
public static class GradeFragment extends Fragment implements UpdateGrupeHost {
private final ArrayList<OnUpdateGrupeListener> listeners = new ArrayList<>();
#Override
public void onAttachUpdateGrupeListener(OnUpdateGrupeListener listener) {
listeners.add(listener);
}
#Override
public void onDetachUpdateGrupeListener(OnUpdateGrupeListener listener) {
listeners.remove(listener);
}
private void notifyOthers(Grupe newGrupe) {
for (OnUpdateGrupeListener l : listeners) {
l.onGrupeUpdated(newGrupe);
}
}
}
/*
GradeAverageFragment,
GroupAverageFragment,
TopGradesFragment
*/
public static class GradeAverageFragment extends Fragment implements OnUpdateGrupeListener {
#Nullable
private UpdateGrupeHost callback;
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (getTargetFragment() instanceof UpdateGrupeHost) {
callback = (UpdateGrupeHost) getTargetFragment();
callback.onAttachUpdateGrupeListener(this);
}
}
#Override
public void onDetach() {
super.onDetach();
if (callback != null) {
callback.onDetachUpdateGrupeListener(this);
}
callback = null;
}
#Override
public void onGrupeUpdated(Grupe newGrupe) {
// updated grupe received
}
}

Related

LiveData with ModelView not updating adapter

I am learning new components and try to use LiveData with DataBinding and ModelView. I have a problem with showing items on recyclerview (updating UI). Here is my ModelView:
 
public class PlacesViewModel extends ViewModel {
private MutableLiveData<List<Place>> placeList;
private Repository repository;
public PlacesViewModel() {
repository = Repository.getInstance();
}
public LiveData<List<Place>> getPlaceList() {
if (placeList == null) {
placeList = new MutableLiveData<List<Place>>();
getPlaces();
}
return placeList;
}
private void getPlaces() {
repository.getLocationsFromBackend(new DataSource.GetLocationsCallback() {
#Override
public void onSuccess(MutableLiveData<List<Place>> body) {
if (body != null) {
placeList = body;
} else {
placeList = new MutableLiveData<>();
}
}
#Override
public void onError(int code) {
}
#Override
public void onUnknownError() {
}
#Override
public void onNoInternet() {
}
#Override
public void onNoServer() {
}
#Override
public void onAlreadyPairedError() {
}
#Override
public void onNoCustomerError() {
}
#Override
public void onLoadIndicator(boolean active) {
}
#Override
public void reAuthenticate() {
}
});
}
The problem comes, when I launch the app, screen is "empty". But, when I am in debug mode and in line getPlaceList(), I can switch to background thread to execute api call using retrofit. Only than it works...
Here is my Repository singleton with method:
public class Repository implements DataSource {
private static Repository INSTANCE;
private ApiService service = new RetrofitFactory().getService();
private static final int RESPONSE_OK = 0;
private final MutableLiveData<List<Place>> data = new MutableLiveData<>();
public synchronized static Repository getInstance() {
if (INSTANCE==null) {
INSTANCE=new Repository();
}
return(INSTANCE);
}
private Repository() {
}
private <T extends BaseResponse> void handleActualResult(BaseLoadCallback<T> dataSource,
MutableLiveData<List<Place>> actualResult) {
final int resultCode = 0;
switch (resultCode) {
case RESPONSE_OK:
dataSource.onSuccess(actualResult);
break;
default:
dataSource.onError(resultCode);
break;
}
}
#Override
public void getLocationsFromBackend(GetLocationsCallback presener) {
final SecurityRequest request = new SecurityRequest();
service.getPlaces(request).enqueue(new Callback<GetPlacesResponse>() {
#Override
public void onResponse(#NonNull Call<GetPlacesResponse> call, #NonNull Response<GetPlacesResponse> response) {
if (response.isSuccessful()) {
if (response.body() != null) {
data.postValue(response.body().getLokali());
handleActualResult(presener, data);
}
}
}
#Override
public void onFailure(#NonNull Call<GetPlacesResponse> call, #NonNull Throwable t) {
Log.d("Error: ", t.getMessage());
}
});
}
#Override
public List<Place> getLocations() {
return null;
}
}
It goes to onResposne and than back to getPlaceList() and returns filled list with objects. Here is how I make an instance of viewmodel and observe LiveData object:
public class LocationFragment extends Fragment {
private PlacesViewModel placesViewModel;
private RecyclerView recyclerView;
private PlaceCustomAdapter customAdapter;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView = view.findViewById(R.id.recyclerViewId);
placesViewModel = ViewModelProviders.of(LocationFragment.this).get(PlacesViewModel.class);
customAdapter = new PlaceCustomAdapter(getLayoutInflater());
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
recyclerView.setHasFixedSize(false);
placesViewModel.getPlaceList().observe(this, places -> {
customAdapter.setList(places);
recyclerView.setAdapter(customAdapter);
});
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return(inflater.inflate(R.layout.activity_main, container, false));
}
Here is also my adapter:
public class PlaceCustomAdapter extends RecyclerView.Adapter<PlaceCustomAdapter.CustomLocationViewHolder> {
private LayoutInflater layoutInflater;
private List<Place> placeList;
public PlaceCustomAdapter(LayoutInflater layoutInflater) {
this.layoutInflater = layoutInflater;
}
#NonNull
#Override
public PlaceCustomAdapter.CustomLocationViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
ViewDataBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.single_item_row_recycler_view, parent, false);
return new PlaceCustomAdapter.CustomLocationViewHolder(binding);
}
#Override
public void onBindViewHolder(#NonNull PlaceCustomAdapter.CustomLocationViewHolder holder, int position) {
Place p = placeList.get(position);
holder.bind(p);
}
void setList(List<Place> placeList) {
this.placeList = placeList;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return(placeList == null ? 0 : placeList.size());
}
public class CustomLocationViewHolder extends RecyclerView.ViewHolder {
private final ViewDataBinding binding;
public CustomLocationViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(Object obj) {
binding.setVariable(BR.model, obj);
binding.executePendingBindings();
}
}
So, it does not crash, only recycler is empty...If you need any extra explanation, just ask.
I would appreciate any help. Thanx
I just figured it out what was wrong. I was calling a method handleActualResult(presener, data);
in wrong place. Instead in onSuccess I put it out to handle response...
#Override
public void getLocationsFromBackend(GetLocationsCallback presener) {
final SecurityRequest request = new SecurityRequest();
service.getPlaces(request).enqueue(new Callback<GetPlacesResponse>() {
#Override
public void onResponse(#NonNull Call<GetPlacesResponse> call, #NonNull Response<GetPlacesResponse> response) {
if (response.isSuccessful()) {
if (response != null) {
data.postValue(response.body().getLokali());
} else {
data = new MutableLiveData<>();
}
}
}
#Override
public void onFailure(#NonNull Call<GetPlacesResponse> call, #NonNull Throwable t) {
Log.d("Error: ", t.getMessage());
}
});
handleActualResult(presener, data);
}
If anyone counter the same problem...Enjoy!

Change displayed fragment inside ViewPager

I have ViewPager that is showing fragments : SearchFragment,FriendFragment. What I am struggling to do is to change SearchFragment to ChatFragment
There is a lot of feedback in replace-fragment-inside-a-viewpager/ yet I have some trouble with making any of these solutions work.
I think there might be something wrong with my way of creating interface, I had some trouble to make it callable from ViewPager as SearchActivity.newInstance(new MyListener {...} )
I reached the point now where I get
java.lang.NullPointerException: Attempt to invoke virtual method
'android.support.v4.app.FragmentTransaction
android.support.v4.app.FragmentManager.beginTransaction()' on a null
object reference
at
com.example.ashaneen.firebaseapp.adapters.MainScreenAdapter$1.onFragmentChange(MainScreenAdapter.java:44)
at
com.example.ashaneen.firebaseapp.fragments.SearchFragment$4.onComplete(SearchFragment.java:132)
MainScreenAdapter.class
public class MainScreenAdapter extends FragmentPagerAdapter implements SearchFragment.MyListener {
private static final String TAG = "MainScreenAdapter";
private FragmentManager fragmentManager;
private Fragment fragment1;
public MainScreenAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position)
{
switch (position)
{
case 0:
if (fragment1 == null)
{
fragment1 = SearchFragment.newInstance(new SearchFragment.MyListener() {
#Override
public void onFragmentChange() {
fragmentManager.beginTransaction().remove(fragment1).commit();
fragment1 = ChatFragment.newInstance();
notifyDataSetChanged();
}
});
}
return fragment1;
case 1:
return new FriendsFragment();
default:
return new ChatFragment();
}
}
#Override
public int getItemPosition(#NonNull Object object) {
return POSITION_NONE;
}
#Override
public void onFragmentChange() {
}
SearchFragment.class
public class SearchFragment extends android.support.v4.app.Fragment {
public SearchFragment() {
}
private String TAG = "Profile2Fragment";
public interface MyListener
{
void onFragmentChange();
}
static MyListener myListener = new MyListener() {
public void onFragmentChange() {
}
};
public static SearchFragment newInstance(MyListener myListener) {
SearchFragment.myListener = myListener;
return new SearchFragment();
}
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_search, container, false);
button = view.findViewById(R.id.btn_search);
waitingRoomReference = db.collection("awaiting_room");
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view)
{
myListener.onFragmentChange();
}
}
});
return view;
}
Try changing this
public MainScreenAdapter(FragmentManager fm) {
super(fm);
}
to
public MainScreenAdapter(FragmentManager fm) {
super(fm);
fragmentManager = fm;
}

How pass data from RecyclerView to activity

I need to pass data from recyclerView adapter to main activity on click on image of recyclerview. Can someone help?
public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoHolder> {
private ArrayList<Video> mData;
private ArrayList<Video> mData2;
private Activity mACtivity;
public VideoAdapter(ArrayList<Video> data, ArrayList<Video> data2, Activity activity) {
this.mData = data;
this.mData2 = data2;
this.mACtivity = activity;
}
#Override
public VideoHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.anteprima_list_item, parent, false);
//view.setOnClickListener(mOnClickListener);
return new VideoHolder(view);
}
#Override
public void onBindViewHolder(VideoHolder holder, int position) {
Video video = mData.get(position);
final Video video2 = mData2.get(position);
holder.setTitolo(video.getTitolo());
holder.setSottoTitolo(video.getSottotitolo());
holder.setData(video.getData());
holder.setData(video.getData());
/* holder.setAddress(restaurant.getAddress());
holder.setCost("Average cost for 2: " + restaurant.getCurrency() + restaurant.getCost());
holder.setRating(restaurant.getRating());*/
Glide.with(mACtivity)
.load(video2.getPic())
.into(holder.restaurantImageView);
holder.restaurantImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// handle click event here
System.out.println("PIC"+video2.getPic());
}
});
}
#Override
public int getItemCount() {
if (mData == null)
return 0;
return mData.size();
}
public class VideoHolder extends RecyclerView.ViewHolder {
ImageView restaurantImageView;
TextView restaurantNameTextView;
TextView restaurantAddressTextView;
TextView restaurantRatingTextView;
TextView costTextView;
TextView distanceTextView;
LinearLayout linearlayout;
public VideoHolder(View itemView) {
super(itemView);
linearlayout=(LinearLayout) itemView.findViewById((R.id.linearlayout));
restaurantImageView = (ImageView) itemView.findViewById(R.id.imageview_restaurant);
restaurantNameTextView = (TextView) itemView.findViewById(R.id.textview_restaurant_name);
restaurantAddressTextView = (TextView) itemView.findViewById(R.id.restaurant_address_textview);
distanceTextView = (TextView) itemView.findViewById(R.id.restaurant_distance_textview);
/* costTextView = (TextView) itemView.findViewById(R.id.cost_for_two_textview);
restaurantRatingTextView = (TextView) itemView.findViewById(R.id.rating);*/
}
public void setTitolo(String titolo) {
restaurantNameTextView.setText(titolo);
}
public void setSottoTitolo(String sottotitolo) {
restaurantAddressTextView.setText(sottotitolo);
}
public void setData(String data) {
distanceTextView.setText(data);
}
/* public void setPic(String pic) {
distanceTextView.setText(pic);
}
public void setCost(String cost) {
costTextView.setText(cost);
}
public void setDistance(String distance) {
distanceTextView.setText(distance);
}*/
}
}
Create a listener Interface and let your MainActivity implement it. That way you can call your callback method in your onClick method.
Interface:
public interface OnImageClickListener {
void onImageClick(String imageData);
}
MainActivity:
public class MainActivity implements OnImageClickListener {
#Override
public void onImageClick(String imageData) {
// handle image data
}
//...
}
Your VideoAdapter:
//...
private OnImageClickListener onImageClickListener;
public VideoAdapter(ArrayList<Video> data, ArrayList<Video> data2, Activity activity, OnImageClickListener onImageClickListener) {
this.mData = data;
this.mData2 = data2;
this.mACtivity = activity;
this.onImageClickListener = onImageClickListener;
}
//...
#Override
public void onBindViewHolder(VideoHolder holder, int position) {
//...
holder.restaurantImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onImageClickListener.onImageClick(video2.getPic());
}
});
//...
}
//...
If you want to pass value from onclick to your Parent activity, use onMethodCallback in your MainActivity:
public class MainActivity extends Activity implements AdapterCallback {
private MyAdapter mMyAdapter;
#Override
public void onMethodCallback(String yourValue) {
// get your value here
}
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mMyAdapter = new MyAdapter(this);
}
}
In your Adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private AdapterCallback mAdapterCallback;
public MyAdapter(Context context) {
try {
this.mAdapterCallback = ((AdapterCallback) context);
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement AdapterCallback.");
}
}
#Override
public void onBindViewHolder(final MyAdapter.ViewHolder viewHolder, final int i) {
// simple example, call interface here
// not complete
viewHolder.itemView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mAdapterCallback.onMethodCallback();
}
});
}
public static interface AdapterCallback {
void onMethodCallback(String yourValue);
}
}
If you are working with kotlin, the solution is much simpler.
Original Answer here
pass the lamda val itemClick: (Int) -> Unit to your adapter.
class MyRecyclerViewAdapter(params , val itemClick: (Int) -> Unit): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
internal inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
...
itemView.setOnClickListener( {itemClick(layoutPosition)} )
}
}
In your Activity use the returned value position
val myAdapter = MyRecyclerViewAdapter(params) { position ->
// do something
}
Write interface in your adapter like bellow :
interface clickItemListener {
void onItemClick(String order);
}
And your constructor should be :
privat clickItemListener clickItemListeners;
public VideoAdapter(ArrayList<Video> data, ArrayList<Video> data2, Activity activity, clickItemListener clickItemListeners) {
this.mData = data;
this.mData2 = data2;
this.mACtivity = activity;
this.clickItemListeners = clickItemListeners;
}
Then you can pass to Activity :
yourLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
clickItemListeners.onItemClick(yourValue);
}
});
And implement your interface in your activity like bellow :
public class yourActivity extends AppCompatActivity implements
YourAdapter.clickItemListener{
#Override
public void onItemClick(String order) {
//You can get your value here
}
}
Remember that you should pass activity to adapter like bellow :
YourAdapter adapter = new YourAdapter (... , ... , ... , activity.this);

how to use Architecture Components ViewModel inside RecyclerView Adapter?

I have multiple ViewHolders that work as separated views inside a vertical RecyclerView. I'm practicing with the new Architecture Components ViewModel.
Inside my ViewModel I have a couple of MutableLiveData lists that i want to observe, but how can I call
ViewModelProviders.of((AppCompatActivity)getActivity()).get(FilterViewModel.class)
and
mFilterViewModel.getCountries().observe(this, new Observer<ArrayList<TagModel>>() {
#Override
public void onChanged(#Nullable ArrayList<TagModel> tagModels) {
}
});
without leaking the activity or save the activity inside the adapter?
my ViewModel
public class FilterViewModel extends ViewModel {
private final MutableLiveData<ArrayList<TagModel>> mCountries;
private final MutableLiveData<ArrayList<TagModel>> mSelectedCountryProvinceList;
private final MutableLiveData<ArrayList<TagModel>> mDistanceList;
public FilterViewModel(){
mCountries = new MutableLiveData<>();
mSelectedCountryProvinceList = new MutableLiveData<>();
mDistanceList = new MutableLiveData<>();
TagStore.getInstance().subscribe(new StoreObserver<TagSearchList>() {
#Override
public void update(TagSearchList object) {
mCountries.setValue(object.getCountries());
}
#Override
public void update(int id, TagSearchList object) {
if (id == 5){
TagStore.getInstance().unSubcribe(this);
update(object);
}
}
#Override
public void error(String error) {
}
}).get(5,"parent");
TagStore.getInstance().subscribe(new StoreObserver<TagSearchList>() {
#Override
public void update(TagSearchList object) {
mSelectedCountryProvinceList.setValue(object.toList());
}
#Override
public void update(int id, TagSearchList object) {
if (id == 6){
TagStore.getInstance().unSubcribe(this);
update(object);
}
}
#Override
public void error(String error) {
}
}).get(6,"parent");
TagStore.getInstance().subscribe(new StoreObserver<TagSearchList>() {
#Override
public void update(TagSearchList object) {
mDistanceList.setValue(object.toList());
}
#Override
public void update(int id, TagSearchList object) {
if (id == 51){
TagStore.getInstance().unSubcribe(this);
update(object);
}
}
#Override
public void error(String error) {
}
}).get(51,"parent");
}
public void selectCountry(final TagModel country){
TagStore.getInstance().subscribe(new StoreObserver<TagSearchList>() {
#Override
public void update(TagSearchList object) {
mSelectedCountryProvinceList.setValue(object.toList());
}
#Override
public void update(int id, TagSearchList object) {
if (id == country.getId()){
TagStore.getInstance().unSubcribe(this);
update(object);
}
}
#Override
public void error(String error) {
}
}).get(country.getId(),"parent");
}
public LiveData<ArrayList<TagModel>> getCountries(){
return mCountries;
}
public LiveData<ArrayList<TagModel>> getDistances(){
return mDistanceList;
}
public LiveData<ArrayList<TagModel>> getProvinces(){
return mSelectedCountryProvinceList;
}
I am using Room Persistence library. Below is my code for recyclerview adapter using MVVM.
You can see CartViewModel and I have initialized it into the constructor. The constructor gets the context from the activity, and I have cast it into FragmentActivity.
private CartViewModel cartViewModel;
public CartListAdapter(Context context, List<CartModel> cartModels) {
this.context = context;
this.cartModels = cartModels;
cartViewModel = ViewModelProviders.of((FragmentActivity) context).get(CartViewModel.class);
}
Here is my full adapter class. I hope it will help.
public class CartListAdapter extends RecyclerView.Adapter<CartListAdapter.CartListViewHolder> {
private static final String TAG = "CartListAdapter";
private Context context;
private List<CartModel> cartModels;
private Double totalQuantity = 0.0;
private CartViewModel cartViewModel;
public CartListAdapter(Context context, List<CartModel> cartModels) {
this.context = context;
this.cartModels = cartModels;
cartViewModel = ViewModelProviders.of((FragmentActivity) context).get(CartViewModel.class);
}
#NonNull
#Override
public CartListViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new CartListViewHolder(LayoutInflater.from(context).inflate(R.layout.list_all_cart_item,parent,false));
}
#Override
public void onBindViewHolder(#NonNull CartListViewHolder holder, int position) {
CartModel cartModel = cartModels.get(position);
Glide.with(context)
.load(cartModel.getPPICLocate())
.into(holder.cartItemImage);
holder.tvCartProductName.setText(cartModel.getProductName());
holder.tvCartProductCategory.setText(cartModel.getPCategorySubID());
holder.tvCartProductPrice.setText(cartModel.getPPriceSales());
holder.etCartProductQuantity.setText(cartModel.getPQuantity());
holder.btnCartPQtIncrease.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
totalQuantity = Double.valueOf(holder.etCartProductQuantity.getText().toString());
totalQuantity = totalQuantity+1;
cartModel.setPQuantity(totalQuantity.toString());
updateCart(cartModel);
}
});
holder.btnCartPQtDecrease.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
totalQuantity = Double.valueOf(holder.etCartProductQuantity.getText().toString());
totalQuantity = totalQuantity-1;
cartModel.setPQuantity(totalQuantity.toString());
updateCart(cartModel);
}
});
}
#Override
public int getItemCount() {
return cartModels.size();
}
public class CartListViewHolder extends RecyclerView.ViewHolder{
private ImageView cartItemImage;
private TextView tvCartProductName,tvCartProductCategory,tvCartProductPrice,
etCartProductQuantity,tvCartProductPrevPrice;
private ImageButton btnCartPQtIncrease,btnCartPQtDecrease;
public CartListViewHolder(#NonNull View itemView) {
super(itemView);
cartItemImage= itemView.findViewById(R.id.cartItemImage);
tvCartProductName= itemView.findViewById(R.id.tvCartProductName);
tvCartProductCategory= itemView.findViewById(R.id.tvCartProductCategory);
tvCartProductPrice= itemView.findViewById(R.id.tvCartProductPrice);
etCartProductQuantity= itemView.findViewById(R.id.etCartProductQuantity);
tvCartProductPrevPrice= itemView.findViewById(R.id.tvCartProductPrevPrice);
btnCartPQtIncrease= itemView.findViewById(R.id.btnCartPQtIncrease);
btnCartPQtDecrease= itemView.findViewById(R.id.btnCartPQtDecrease);
}
}
public void addItems(List<CartModel> cartModels) {
this.cartModels = cartModels;
notifyDataSetChanged();
}
private void updateCart(CartModel cartModel){
String tqt = String.valueOf(cartModel.getPQuantity());
Log.d(TAG, "updateQuantity: "+tqt);
/*cartRepository.updateCartRepo(cartModel);*/
cartViewModel.updateCartItemVM(cartModel);
}
}
You could create an OnClickListener interface instead, then implement the onClick method (defined in your interface) in your fragment or activity where you have access to your view model.
My solution to this issue is;
create a new variable(ViewModel) for the layout (fragment or activity layout)
in your activity or fragment get the instance of your viewModel with ViewModelProviders
hold the data which fills your recyclerView in MutableLiveData and observe it and fill the adapter of recyclerView with the value you observed
here you mut be careful not to create adapter instance in observe method.
if you create it in observe method, it will make leaks

How to use one RecyclerView adapter for objects of different types using generics?

I have a RecyclerView which I want to be populated with String objects sometimes & with Product objects some other time. So I started creating its manager adapter this way:
// BaseSearchAdapter is the class that contains the 'List<T> mItems' member variable
public class SearchAdapter<T> extends BaseSearchAdapter<SearchAdapter.ViewHolder, T> {
private Context mContext;
public SearchAdapter(Context context, List<T> items) {
mContext = context;
mItems = new ArrayList<>(items);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
T item = mItems.get(position);
holder.bind(item);
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView textLabel;
public ViewHolder(View v) {
}
public void bind(T item) {
textLabel.setText(...); // How to handle T ?
}
}
}
where T could be String or Product according to the plan.
My question is how can I appropriately bind the data (whether it's a String or a Product) object to its corresponding view in this situation? Or is there a better way to handle this ?
// BaseSearchAdapter is the class that contains the 'List<T> mItems' member variable
public class SearchAdapter<T> extends BaseSearchAdapter<SearchAdapter.ViewHolder<T>, T> {
private Context mContext;
private ViewHolderBinder<T> mBinder;
public SearchAdapter(Context context, List<T> items, ViewHolderBinder<T> binder) {
mContext = context;
mItems = new ArrayList<>(items);
mBinder = binder;
}
#Override
public void onBindViewHolder(ViewHolder<T> holder, int position) {
T item = mItems.get(position);
holder.bind(item);
}
public static class ViewHolder<T> extends RecyclerView.ViewHolder {
ViewHolderBinder<T> mBinder;
TextView textLabel;
public ViewHolder(View v, ViewHolderBinder<T> binder) {
textLabel = (TextView)v.findViewById(R.id.text_label);
this.mBinder = binder;
}
public void bind(T item) {
binder.bind(this, item);
}
}
public interface ViewHolderBinder<T> {
void bind(ViewHolder<T> viewHolder, T item);
}
public static class StringViewHolderBinder implements ViewHolderBinder<String> {
#Override
public void bind(ViewHolder<String> viewHolder, String item) {
viewHolder.textLabel.setText(item);
}
}
public static class ProductViewHolderBinder implements ViewHolderBinder<Product> {
#Override
public void bind(ViewHolder<Product> viewHolder, Product item) {
viewHolder.textLabel.setText(item.getName());
}
}
}
What I do on my projects is create a class BaseRecyclerAdapter that has all of my common operations. Then for most adapters all I have to define is the ViewHolder and the layout.
UPDATE
As Requested I posted a fuller version of my BaseRecyclerAdapter (it varies a bit based upon project need). Also include is a simple gesture callback that allows you to easily enable swipe to remove or drag to reorder operations.
NOTE: This version updates how the recycler item layouts are inflated. I now prefer to inflate the in the BaseRecyclerAdapter.ViewHolder constructor allowing the layout to be specified in the extending ViewHolder's constructor.
Example Base Adapter
public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<BaseRecyclerAdapter.ViewHolder> {
private final List<T> items = new ArrayList<>();
OnItemSelectedListener<T> onItemSelectedListener = SmartNull.create(OnItemSelectedListener.class);
public BaseRecyclerAdapter setOnItemSelectedListener(OnItemSelectedListener<T> onItemSelectedListener) {
if (onItemSelectedListener == null) {
this.onItemSelectedListener = SmartNull.create(OnItemSelectedListener.class);
} else {
this.onItemSelectedListener = onItemSelectedListener;
}
return this;
}
public boolean isEmpty() {
return items.isEmpty();
}
public void setItems(List<T> items) {
this.items.clear();
this.items.addAll(items);
notifyDataSetChanged();
}
public void addItems(T... items) {
addItems(Arrays.asList(items));
}
public void addItems(List<T> items) {
int startPosition = this.items.size() - 1;
this.items.addAll(items);
notifyItemRangeInserted(startPosition, items.size());
}
public void removeItem(int position) {
T item = items.remove(position);
if (itemRemovedListener != null) {
itemRemovedListener.onItemRemoved(item);
}
notifyItemRemoved(position);
}
public void removeItem(T t) {
int index = items.indexOf(t);
if (index >= 0) {
removeItem(index);
}
}
public void addItem(T item) {
items.add(item);
notifyItemInserted(getItemCount() - 1);
}
public void moveItem(int startPosition, int targetPosition) {
if (startPosition < targetPosition) {
for (int i = startPosition; i < targetPosition; i++) {
Collections.swap(items, i, i + 1);
}
} else {
for (int i = startPosition; i > targetPosition; i--) {
Collections.swap(items, i, i - 1);
}
}
notifyItemMoved(startPosition, targetPosition);
}
public List<T> getItems() {
return new ArrayList<>(items);
}
public void setItemAt(int position, T item){
items.set(position, item);
notifyItemChanged(position);
}
public void refreshItem(T item) {
int i = items.indexOf(item);
if (i >= 0) {
notifyItemChanged(i);
}
}
protected void setItemWithoutUpdate(int position, T item){
items.set(position, item);
}
public int indexOf(T t) {
return items.indexOf(t);
}
#SuppressWarnings("unchecked")
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bindItemAt(getItemAt(position), position);
}
#Override
public int getItemCount() {
return items.size();
}
public T getItemAt(int position) {
return items.get(position);
}
private void onItemSelected(int position) {
if (isValidPosition(position)) {
onItemSelectedListener.onItemSelected(getItemAt(position));
}
}
boolean isValidPosition(int position) {
return position >=0 && position < items.size();
}
public abstract class ViewHolder<T> extends RecyclerView.ViewHolder implements View.OnClickListener {
public ViewHolder(ViewGroup parent, #LayoutRes int layoutId) {
super(LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false));
itemView.setOnClickListener(this);
}
public ViewHolder(View view) {
super(view);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
onItemSelected(getAdapterPosition());
}
public abstract void bindItemAt(T t, int position);
}
public interface OnItemSelectedListener<T> {
void onItemSelected(T t);
}
}
Example Implementation
public class ExampleAdapter extends com.stratospherequality.mobileworkforce.modules.common.BaseRecyclerAdapter<Long> {
#Override
protected RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(parent);
}
private class ViewHolder extends RecyclerViewHolder<Long> {
TextView text;
public ViewHolder(ViewGroup parent) {
super(parent, R.layout.row_my_layout);
// I typically use ButterKnife here but this works as well
text = (TextView) itemView.findViewById(R.id.text);
}
#Override
public void setItem(Long value, int position) {
text.setText("#" + value);
}
}
}
Base GestureCallback
public class AdapterGestureCallback extends ItemTouchHelper.SimpleCallback {
public interface OnRemoveItemCallback<T> {
void onRemoveItem(BaseRecyclerAdapter<T> adapter, T t);
}
public enum Direction {
UP(ItemTouchHelper.UP),
DOWN(ItemTouchHelper.DOWN),
LEFT(ItemTouchHelper.LEFT),
RIGHT(ItemTouchHelper.RIGHT),
START(ItemTouchHelper.START),
END(ItemTouchHelper.END);
public final int value;
Direction(int value) {
this.value = value;
}
}
private final BaseRecyclerAdapter adapter;
private OnRemoveItemCallback onRemoveItemCallback;
private boolean enabled = true;
public AdapterGestureCallback(BaseRecyclerAdapter adapter) {
super(0, 0);
this.adapter = adapter;
}
public AdapterGestureCallback setOnRemoveItemCallback(OnRemoveItemCallback onRemoveItemCallback) {
this.onRemoveItemCallback = onRemoveItemCallback;
return this;
}
public AdapterGestureCallback setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
adapter.moveItem(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (onRemoveItemCallback == null){
adapter.removeItem(position);
} else {
onRemoveItemCallback.onRemoveItem(adapter, adapter.getItemAt(position));
}
}
public AdapterGestureCallback withDragDirections(Direction... dragDirections) {
setDefaultDragDirs(valueFor(dragDirections));
return this;
}
public AdapterGestureCallback withSwipeDirections(Direction... swipeDirections) {
setDefaultSwipeDirs(valueFor(swipeDirections));
return this;
}
#Override
public boolean isItemViewSwipeEnabled() {
return enabled;
}
#Override
public boolean isLongPressDragEnabled() {
return enabled;
}
public int valueFor(Direction... directions) {
int val = 0;
for (Direction d : directions) {
val |= d.value;
}
return val;
}
public AdapterGestureCallback attach(RecyclerView recyclerView) {
new ItemTouchHelper(this).attachToRecyclerView(recyclerView);
return this;
}
}
GestureCallback with swiping
new AdapterGestureCallback(adapter)
.withSwipeDirections(AdapterGestureCallback.Direction.LEFT, AdapterGestureCallback.Direction.RIGHT)
.setOnRemoveItemCallback(this)
.attach(recyclerView);
Can use generic for Holder like this:
public abstract class ActionBarAdapter<T,VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
private Context mContext;
public SearchAdapter(Context context, List<T> items) {
mContext = context;
mItems = new ArrayList<>(items);
}
}
The approach that, for example, ArrayAdapter<T> uses is to call toString() on whatever T you pass. This will of course work for a String, and you'll have to implement toString() in your Product to return a meaningful representation.

Categories