RxTextView.textChanges(editText)
.map(CharSequence::toString)
.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(input -> {
output = //...do something with input
editText.setText(ouput)
}));
When I setText(output) it goes in loop. To set the text I first need to remove listener and then set listener again. How can I do this using RxJava?
When I setText(output) it goes in loop. To set the text I first need to remove listener and then set listener again. How can I do this using RxJava?
To meet the requirement I managed to extend the RxBinding source code as follows.
EditableTextViewTextObservable.java:
public class EditableTextViewTextObservable extends InitialValueObservable<CharSequence> {
private final TextView view;
EditableTextViewTextObservable(TextView view) {
this.view = view;
}
#Override
protected void subscribeListener(Observer<? super CharSequence> observer) {
EditableTextViewTextObservable.Listener listener = new EditableTextViewTextObservable.Listener(view, observer);
observer.onSubscribe(listener);
view.addTextChangedListener(listener);
}
#Override protected CharSequence getInitialValue() {
return view.getText();
}
final static class Listener extends MainThreadDisposable implements TextWatcher {
private final TextView view;
private final Observer<? super CharSequence> observer;
Listener(TextView view, Observer<? super CharSequence> observer) {
this.view = view;
this.observer = observer;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if (!isDisposed()) {
view.removeTextChangedListener(this);
observer.onNext(s);
view.addTextChangedListener(this);
}
}
#Override
protected void onDispose() {
view.removeTextChangedListener(this);
}
}
}
EditableRxTextView.java:
public final class EditableRxTextView {
#CheckResult
#NonNull
public static InitialValueObservable<CharSequence> textChanges(#NonNull TextView view) {
return new EditableTextViewTextObservable(view);
}
}
Usage:
EditableRxTextView.textChanges(editText)
.map(CharSequence::toString)
.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(input -> {
output = //...do something with input
editText.setText(ouput)
}));
My solution was to use editText.getText().replace(...) as to not trigger the TextWatcher when setting the text
Related
Problem
I am trying to develop a gallery selector page for my app. A user can select multiple images/videos from the listed items (from the users phone). The user can select up to 10 items at once and the order of selection should be shown for each item. The selection order should also adjust based on selection and deselection.
eg : If user deselects selection 1 then all other selection after selection 1 should decrement by one.
What I have done
I have already made the recyclerview adapter with diffUtils and handled the multiple selection using recyclerview-selection library. But I can't find a way to show the selection order and adjusting it based on user action.
What an trying to achieve
Code
GalleryAdapter
public class GalleryViewAdapter extends ListAdapter<GalleryThumbnailsModel,GalleryViewAdapter.ViewHolder> {
private Context context;
private ArrayList<GalleryThumbnailsModel> selectedItemModels;
private Interaction interaction;
private SelectionTracker<Long> selectionTracker;
private static int POST_TYPE_IMAGE = 1;
private static int POST_TYPE_VIDEO = 3;
public GalleryViewAdapter(GalleryViewDiffCallback diffCallback,Context context,ArrayList<GalleryThumbnailsModel> selectedItemModels) {
super(diffCallback);
this.context = context;
this.selectedItemModels = selectedItemModels;
setHasStableIds(true);
}
#NonNull
#Override
public GalleryViewAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_gallery_view,parent,false);
return new GalleryViewAdapter.ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final GalleryViewAdapter.ViewHolder holder, final int position) {
GalleryThumbnailsModel item = getItem(position);
holder.bind(item,selectionTracker.isSelected((long) position));
}
#Override
public long getItemId(int position) {
return (long) position;
}
public void setSelectionTracker(SelectionTracker<Long> selectionTracker) {
this.selectionTracker = selectionTracker;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView postImage;
ImageView postTypeVideo;
ProgressBar progressBar;
TextView postOrderCount;
FrameLayout selectedIcon;
public ViewHolder(View itemView) {
super(itemView);
postImage = itemView.findViewById(R.id.post_gallery_image);
postTypeVideo = itemView.findViewById(R.id.user_post_video);
progressBar = itemView.findViewById(R.id.progress_loader);
selectedIcon =itemView.findViewById(R.id.gallery_selected_item);
postOrderCount = itemView.findViewById(R.id.selected_post_order);
}
public void bind(GalleryThumbnailsModel item, boolean selected) {
new GlideImageLoader(context,postImage,progressBar).load(item.getThumbnail(),null);
if(selected){
selectedIcon.setVisibility(View.VISIBLE);
interaction.onItemSelected(getAdapterPosition(),item);
}else {
selectedIcon.setVisibility(View.INVISIBLE);
interaction.onItemDeselected(getAdapterPosition(),item);
}
if(item.getMediaType() == POST_TYPE_VIDEO){
postTypeVideo.setVisibility(View.VISIBLE);
}else {
postTypeVideo.setVisibility(View.INVISIBLE);
}
}
public ItemDetailsLookup.ItemDetails<Long> getItemDetails(){
return new ItemDetailsLookup.ItemDetails<Long>() {
#Override
public int getPosition() {
return getAdapterPosition();
}
#Nullable
#Override
public Long getSelectionKey() {
return getItemId();
}
#Override
public boolean inSelectionHotspot(#NonNull MotionEvent e) {
return true;
}
};
}
}
public static class GalleryViewDiffCallback extends DiffUtil.ItemCallback<GalleryThumbnailsModel>{
#Override
public boolean areItemsTheSame(#NonNull GalleryThumbnailsModel oldItem, #NonNull GalleryThumbnailsModel newItem) {
return oldItem.getUriPath().equals(newItem.getUriPath());
}
#Override
public boolean areContentsTheSame(#NonNull GalleryThumbnailsModel oldItem, #NonNull GalleryThumbnailsModel newItem) {
return oldItem.equals(newItem);
}
}
public interface Interaction{
void onItemSelected(int position,GalleryThumbnailsModel item);
void onItemDeselected(int position,GalleryThumbnailsModel item);
}
public void setInteraction(Interaction interaction) {
this.interaction = interaction;
}
}
Setting adapter in Fragment
private void setAdapter() {
galleryViewAdapter = new GalleryViewAdapter(new GalleryViewAdapter.GalleryViewDiffCallback(),context,selectedItemsModels);
gridLayoutManager = new GridLayoutManager(context,3,RecyclerView.VERTICAL,false);
galleryViewAdapter.submitList(galleryThumbnailsModels);
galleryViewRecycler.setLayoutManager(gridLayoutManager);
galleryViewRecycler.setHasFixedSize(true);
galleryViewRecycler.setAdapter(galleryViewAdapter);
selectionTracker = new SelectionTracker.Builder<Long>(
"selection",
galleryViewRecycler,
new StableIdKeyProvider(galleryViewRecycler),
new RecyclerSelectionLookup(galleryViewRecycler),
StorageStrategy.createLongStorage()
).withSelectionPredicate(new SelectionTracker.SelectionPredicate<Long>() {
#Override
public boolean canSetStateForKey(#NonNull Long key, boolean nextState) {
// 10 - max selection size
return !nextState || selectionTracker.getSelection().size() < 10;
}
#Override
public boolean canSetStateAtPosition(int position, boolean nextState) {
return false;
}
#Override
public boolean canSelectMultiple() {
return true;
}
})
.build();
selectionTracker.addObserver(new SelectionTracker.SelectionObserver<Long>() {
#Override
public void onSelectionChanged() {
super.onSelectionChanged();
int selectedItemCount = selectionTracker.getSelection().size();
if(selectedItemCount == 0){
addMediaLayout.setVisibility(View.INVISIBLE);
}else if(selectedItemCount > 0 && selectedItemCount < 11){
selectedCountTextView.setText(String.valueOf(selectedItemCount));
addMediaLayout.setVisibility(View.VISIBLE);
}
}
});
galleryViewAdapter.setSelectionTracker(selectionTracker);
galleryViewAdapter.setInteraction(new GalleryViewAdapter.Interaction() {
#Override
public void onItemSelected(int position,GalleryThumbnailsModel item) {
item.setSelectedOrder(selectedItemsModels.size()+1);
selectedItemsModels.add(item);
}
#Override
public void onItemDeselected(int position,GalleryThumbnailsModel item) {
}
});
}
I am only adding the adapter code here. Otherwise the question would become too large.I will share the complete code if this doesn't provide necessary information.
you can pass selectionTracker.selection.size() to bind function in your ViewHolder class, so you will have always the latest count
#Override
public void onBindViewHolder(#NonNull final GalleryViewAdapter.ViewHolder holder, final int position) {
int count = selectionTracker.selection.size()
holder.bind(item,count,selectionTracker.isSelected((long) position));
}
Image
I have a RecyclerView in which fields are created dynamically. To save the stored data, I used TextWatcher. But I do not know how I sum up the values that are in EditText -Cost and display the value in the TextView at the bottom of the screen
public class ItemsAdapter extends
ListAdapter<Item,ItemsAdapter.ViewHolder> {
private List<Item> mItems=new ArrayList<>();
public static final DiffUtil.ItemCallback<Item>DIFF_CALLBACK=
new DiffUtil.ItemCallback<Item>() {
#Override
public boolean areItemsTheSame( Item oldItem, Item newItem) {
return oldItem.getId()==newItem.getId();
}
#Override
public boolean areContentsTheSame(Item oldItem, Item newItem) {
return(oldItem.getName()==newItem.getName()&&oldItem.getCost()==newItem.getCost());
}
};
public ItemsAdapter(){super(DIFF_CALLBACK);}
public void addMorreItems(List<Item> newItems){
int insertionPosition=mItems.size();
mItems.addAll(newItems);
notifyItemRangeInserted(insertionPosition,newItems.size());
submitList(mItems);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
Context context=parent.getContext();
LayoutInflater inflater=LayoutInflater.from(context);
View itemView=inflater.inflate(R.layout.custom_edittext_layout,parent,false);
ViewHolder viewHolder=new ViewHolder(itemView);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder viewHolder,int position){
viewHolder.editTextName.setText(mItems.get(position).getName());
viewHolder.editTextCost3.setText(mItems.get(position).getCost());
Item item=getItem(position);
EditText editText=viewHolder.editTextName;
editText.setText(item.getName());
EditText editText1=viewHolder.editTextCost3;
editText1.setText(item.getCost());
}
public class ViewHolder extends RecyclerView.ViewHolder {
public EditText editTextName;
public EditText editTextCost3;
public TextView txtTipAmount3;
public Button buttonDelete;
public Button buttonOK;
public ViewHolder(View itemView){
super(itemView);
editTextCost3=(EditText)itemView.findViewById(R.id.ediCost3);
editTextName=(EditText)itemView.findViewById(R.id.editTextEnterYourName);
txtTipAmount3=(TextView)itemView.findViewById(R.id.txtTipAmount3);
buttonDelete=(Button)itemView.findViewById(R.id.buttonDelete);
buttonOK=(Button)itemView.findViewById(R.id.buttonOk);
buttonDelete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position=getAdapterPosition();
try {
mItems.remove(position);
notifyItemRemoved(position);
}catch (ArrayIndexOutOfBoundsException e){e.printStackTrace();}
}
});
editTextName.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mItems.get(getAdapterPosition()).setmName(editTextName.getText().toString());
}
#Override
public void afterTextChanged(Editable s) {
}
});
editTextCost3.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mItems.get(getAdapterPosition()).setmCost(editTextCost3.getText().toString());
}
#Override
public void afterTextChanged(Editable s) {
}
});
}
} }
Add callback for the Activity/Fragment that holds TextView for sum cost:
interface OnCostChangeListener {
void onChange(List<Item> list);
}
Implement it:
class MyActivity extends AppCompatActivity implements OnCostChangeListener {
//...
#Override
public void onChange(List<Item> list) {
int sum = 0;
for (Item it : list) {
sum += it.getCost();
}
((TextView) findViewById(R.id.totalCost)).setText(String.valueOf(sum));
}
//...
}
Subscribe the adapter:
private OnCostChangeListener mListener;
public ItemsAdapter(OnCostChangeListener listener){
super(DIFF_CALLBACK);
this.mListener = listener;
}
and use the listener in TextWatcher:
editTextCost3.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mItems.get(getAdapterPosition()).setmCost(editTextCost3.getText().toString());
if (mListener != null) {
mListener.onChange(mItems);
}
}
#Override
public void afterTextChanged(Editable s) {
}
});
UPD. It's supposed cost is stored in Item as number (integer, in the sample), not string
I am building a custom component in Java and trying to display that in React Native but the component is not appearing. I've been looking through forums and documentation for solutions but can't find a solution. I am not sure if it's an issue on the native side or the React Native side. Any help or advice on improvement is highly appreciated.
Bottom Sheet View
public class BottomSheetView extends NestedScrollView {
private BottomSheetBehavior bSheetBehavior;
public BottomSheetView(Context context) {
super(context);
init();
}
private void init() {
inflate(getContext(), R.layout.bottom_sheet, this);
CoordinatorLayout coordinaterLayout = findViewById(R.id.coordinate_layout);
View bottomSheet = coordinaterLayout.findViewById(R.id.bottom_sheet_component);
bSheetBehavior = BottomSheetBehavior.from(bottomSheet);
bSheetBehavior.setHideable(false);
bSheetBehavior.setPeekHeight((int) PixelUtil.toPixelFromDIP(200));
}
#Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
}
}
Bottom Sheet Manager
public class BottomSheetManager extends ViewGroupManager<BottomSheetView> {
public static final String REACT_CLASS = "BottomSheet";
private BottomSheetView bottomSheet;
#Override
public String getName() {
return REACT_CLASS;
}
#Override
protected BottomSheetView createViewInstance(ThemedReactContext reactContext) {
return new BottomSheetView(reactContext);
}
}
Bottom Sheet Package
public class BottomSheetPackage implements ReactPackage {
private Activity mActivity = null;
BottomSheetPackage(Activity activity) {
mActivity = activity;
}
public BottomSheetPackage() {
}
#Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = Collections.emptyList();
return modules;
}
#Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new BottomSheetManager());
}
}
React Native
class BottomSheet extends Component {
render() {
return (<BottomSheet style={{height: 200}}/>);
}
}
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
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.