How to use bind function of groupie library in java? - android

I am trying to show a list of user in recyclerView and trying to connect the textview layout in bind method in groupie library and i don't know how to link the id of layout to recyclerview viewHolder? and also how to use picasso library in viewholder?
private void fetchUser(){
DatabaseReference fireBaseReference = FirebaseDatabase.getInstance().getReference().child("/userList");
fireBaseReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
GroupAdapter groupA = new GroupAdapter<ViewHolder>();
for (DataSnapshot snapshot : dataSnapshot.getChildren()){
Log.d("userList",snapshot.toString());
Users string = snapshot.getValue(Users.class);
groupA.add(new UserItem());
}
recyclerView.setAdapter(groupA);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
class UserItem extends Item<ViewHolder>{
// public UserItem(Users users){
// }
private Users users = new Users();
#Override
public void bind(#NonNull ViewHolder viewHolder, int position) {
viewHolder.itemView.findViewById(R.id.user_name_from_user_list);
viewHolder.
Picasso.get().load(users.getUri()).into(viewHolder.itemView.findViewById(R.id.user_photo_from_user_list));
}
#Override
public int getLayout() {
return R.layout.user_list_view_layout;
}
}

Groupie abstracts away the complexity of multiple item view types. Each Item declares a view layout id, and gets a callback to bind the inflated layout. That's all you need; you can add your new item directly to a GroupAdapter and call it a day.
Item with Kotlin
The Item class gives you simple callbacks to bind your model object to the generated fields. Because of Kotlin Android extensions, there's no need to write a view holder.**
class SongItem(private val song: Song) : Item() {
override fun getLayout() = R.layout.song
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.title.text = song.title
viewHolder.artist.text = song.artist
}
}
Item with data binding:
The Item class gives you simple callbacks to bind your model object to the generated binding. Because of data binding, there's no need to write a view holder.
If you're converting existing ViewHolders, you can reference any named views (e.g. R.id.title) directly from the binding instead.
#Override public void bind(SongBinding binding, int position) {
binding.title.setText(song.getTitle());
}
or you can do this way
#Override
public void bind(#NonNull final ViewHolder viewHolder, final int position) {
circleImageView = viewHolder.itemView.findViewById(R.id.circleImageViewForLatestMessage);
userMessage = viewHolder.itemView.findViewById(R.id.textView2);
userName = viewHolder.itemView.findViewById(R.id.textView41);
userName.setText(name);
userMessage.setText(messages);
You can also mix and match BindableItem and other Items in the adapter, so you can leave legacy viewholders as they are by making an Item.
For more information visit Groupie Library

Related

How delete a row of recycler view from ViewHolder

thanks to stop by.
Not a long time ago someone explain to me that you should'nt store data in ViewHolder, I get why. But then it complicate something.
I want to remove a Row if user click on a button inside the row. So i need to access adapter. But I can't store it on ViewHolder. What the way ?
I'am also looking for the best recyclerview article that you know because it look like people don't know what they talking about in most of the stack overflow and give bad advise (Store data inside ViewHolder)
EDIT : I'am trying one solution but getTag return me null when I'am in adapter
Here is my item and the binding :
public final class ItemViewMail extends RecyclerView.ViewHolder {
private AppCompatImageButton cancelButton;
public ItemViewMail(View itemView) {
super(itemView);
this.cancelButton = itemView.findViewById(R.id.profile_item_edit_email_cancel_image_button);
}
public void bind(Data data, View.OnClickListener deleteOnClickListener) {
this.itemView.setTag(data);
if (cancelButton != null) {
cancelButton.setOnClickListener(deleteOnClickListener);
}
}
}
My adapter :
private ArrayList<Data> rowDataArrayList;
MyPVPViewAdapter(ArrayList<Data> rowDataArrayList) {
this.rowDataArrayList = rowDataArrayList;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int itemType) {
return new ItemViewMail(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.profile_item_edit_email, viewGroup ,false));
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder myViewHolder, int position) {
((ItemViewMail) myViewHolder).bind((Data) rowDataArrayList.get(position).getData(), onClickListener);
}
#Override
public int getItemViewType(int position) {
return rowDataArrayList.get(position).getType();
}
#Override
public int getItemCount() {
return rowDataArrayList.size();
}
View.OnClickListener onClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
Data data = (Data) v.getTag();
}
};
}
You can remove the item from the adapter and notify it about the removed row and it will update smoothly with the removal. Also remove the item from your data source, seems like you're doing that already though.
mAdapter.getItems().remove(mPosition);
mAdapter.notifyItemRemoved(mPosition);
data class MyItem(val name: String)
class MyViewHolder(itemView: View,
private val onRemoveClickListener: View.OnClickListener) : RecyclerView.ViewHolder(itemView) {
fun bind(item: MyItem) {
itemView.setOnClickListener(onRemoveClickListener)
itemView.setTag(item)
//todo: eg. itemView.name.setText(item.name)
}
}
class MyRecyclerViewAdapter(private val onRemoveClickListener: View.OnClickListener): RecyclerView.Adapter<MyViewHolder>() {
var items = listOf<MyItem>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.viewholder_my)
return MyViewHolder(view, onRemoveClickListener)
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(items[position])
}
}
Now you can create MyRecyclerViewAdapter inside your activity/fragment/etc and put onClickListener as a parameter.
class MyActivity: AppCompatActivity() {
private lateinit var adapter: MyRecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = MyRecyclerViewAdapter(View.OnClickListener { view ->
val clickedItem = view.getTag() as MyItem
adapter.items.remove(clickedItem)
adapter.notifyDataSetChanged()
})
}
}
I want to remove a Row if user clic on a button inside the row. So i need to access adapter. But I can't store it on ViewHolder. What the way ?
The base implementation of RecyclerView.ViewHolder provides two methods you can call when you need access to position information: getAdapterPosition() and getLayoutPosition(). In your case, it sounds like you want the first one.
This lets you write code like this:
public void soSomethingOnClick() {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
myDataStructure.removeItemAt(position);
myAdapter.notifyItemRemoved(position);
}
}
Not a long time ago someone explain to me that you should'nt store data in ViewHolder, I get why. But then it complicate something.
I'm not sure what you heard, but storing information in a ViewHolder is totally fine. You have to be careful to make sure you always update whatever data you're storing so that it doesn't get out of sync when a ViewHolder is recycled, but very many ViewHolder implementations take express advantage of the fact that ViewHolders are a great place to save data.

Implement onItemClickListener using MVP pattern

I am learning MVP and got confused where and how should I implement onClickListener while not ruining mvp concept here.
Followed this guide: https://android.jlelse.eu/recyclerview-in-mvp-passive-views-approach-8dd74633158
My implementation.
Adapter:
public class RepositoriesRecyclerAdapter extends RecyclerView.Adapter<RepositoriesRecyclerAdapter.RepoViewHolder> {
private final RepositoriesListPresenter presenter;
public RepositoriesRecyclerAdapter(RepositoriesListPresenter repositoriesPresenter) {
this.presenter = repositoriesPresenter;
}
#Override
public RepositoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new RepositoryViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.cell_repo_view, parent, false));
}
#Override
public void onBindViewHolder(RepositoryViewHolder holder, int position) {
presenter.onBindRepositoryRowViewAtPosition(position, holder);
}
#Override
public int getItemCount() {
return presenter.getRepositoriesRowsCount();
}
}
RepositoryViewHolder's
public class RepositoryViewHolder extends RecyclerView.ViewHolder implements RepositoryRowView {
TextView titleTextView;
TextView starsCountTextView;
public RepositoryViewHolder(View itemView) {
super(itemView);
titleTextView = itemView.findViewById(R.id.repoTitleText);
starsCountTextView = itemView.findViewById(R.id.repoStarsCountText);
}
#Override
public void setTitle(String title) {
titleTextView.setText(title);
}
#Override
public void setStarCount(int starCount) {
starsCountTextView.setText(String.format("%s ★", starCount));
}
}
RepositoryRowView
interface RepositoryRowView {
void setTitle(String title);
void setStarCount(int starCount);
}
All guides I saw was about creating onClickListener object in Adapter and then use it in ViewHolder, but in this implementation, I override all adapter function in my presenter and passing onClickListener (android related stuff) would contradict mvp pattern. What to do in this case. Maybe someone could write a solution - really confused.
My main goal would be to click a recyclerview item and get item name (via toast)
OnClickListener is an interface from Android SDK. Your presenter should not know anything about the Andriod SDK. It should be pure Java so it can be tested just by using Unit test on the JVM. It shouldn't know anything about views, RecyclerView, Adapter nor ViewHolder.
Your onBindViewHolder doesn't violate this principle because it's separated by an abstract interface - RepositoryRowView.
You should implement OnClickListener in adapter/viewholder and call your presenter from there.
public class RepositoryViewHolder extends RecyclerView.ViewHolder implements RepositoryRowView, View.OnClickListener {
TextView titleTextView;
TextView starsCountTextView;
RepositoriesListPresenter presenter;
public RepositoryViewHolder(View itemView, RepositoriesListPresenter presetner) {
super(itemView);
titleTextView = itemView.findViewById(R.id.repoTitleText);
starsCountTextView = itemView.findViewById(R.id.repoStarsCountText);
this.presenter = presenter;
itemView.setOnClickListener(this);
}
#Override
public void setTitle(String title) {
titleTextView.setText(title);
}
#Override
public void setStarCount(int starCount) {
starsCountTextView.setText(String.format("%s ★", starCount));
}
#Override
public void onClick(View view) {
if (presenter != null) {
presenter.onItemInteraction(getAdapterPosition());
}
}
}
Instead of calling the presenter inside your adapter, I would rather make an interface of the click to call it from the view, since you will instantiate this adapter in your view, it's a good thing to keep the MVP pattern with the click of the elements inside your view and not in the adapter itself.
This example is in Kotlin, but I'm sure you will understand it.
First, just make a simple interface to call your click event whenever the user clicks on any item in your list.
class EquipmentAdapter(private val context: Context,private var equipmentList:ArrayList<Equipment>,itemListener:RecyclerViewClickListener): RecyclerView.Adapter<EquipmentAdapter.EquipmentViewHolder>() {
interface RecyclerViewClickListener {
fun recyclerViewListClicked(v: View?, position: Int)
}
companion object{
var itemClickListener: RecyclerViewClickListener? = null
var equipmentSearchList:ArrayList<Equipment>? = null
}
init {
equipmentSearchList = equipmentList
itemClickListener = itemListener
}
Then , inside your ViewHolder you should call this interface to handle the click
inner class EquipmentViewHolder(itemView: View): RecyclerView.ViewHolder(itemView), View.OnClickListener {
val equipmentName:TextView = itemView.txt_equipmentname
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
itemClickListener?.recyclerViewListClicked(v, adapterPosition)
}
}
Lastly, just implement the interface of the click in the view that you are calling the adapter, and then just manage the presenter interactions there instead inside the adapter
class EquipmentActivity : BaseActivity(), EquipmentContract.EquipmentView, EquipmentAdapter.RecyclerViewClickListener ...
And implement the click method
override fun recyclerViewListClicked(v: View?, position: Int) {
presenter.onItemInteraction(position)
}
Doing this, you are making sure that the click of the elements in the list are being made from the view itself and not from the adapter, here, you can interact with the presenter as always and also do more things that will keep your project clean.

How to implement Infinite Scrolling with RecyclerView?

I have a recycler and inside of it there are cardviews where I fetch information from a REST service, I'm trying to implement an endless scroll, It's supposed that user will see 10 cardviews every time he scrolls down until there are no more cardviews to show, How can I achieve that?
I've seen a few examples but none of them really helped me about how to do it. I don't even know what I need to put in adapter.class or in my Fragment.class because I don't understand how to implement that, it would be great if someone could tell me the correct way to implement the infinite scroll in my code...
Thanks in advance.
MainAdapter.class
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> implements View.OnClickListener
{
private ArrayList<Business> businessList;
private Activity activity;
private int layoutMolde,idb;
public MainAdapter(Activity activity, ArrayList<Business> list, int layout)
{
this.activity = activity;
this.businessList = list;
layoutMolde = layout;
}
#Override
public MainAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_row, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position)
{
holder.mTitle.setText(businessList.get(position).getBusiness_name());
holder.number_rating.setText(businessList.get(position).getRating().toString());
Glide.with(activity).load(businessList.get(position).getLogo_url_string()).into(holder.mImg);
}
#Override
public int getItemCount() {
return businessList.size();
}
#Override
public void onClick(View v)
{
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTitle;
public ImageView mImg;
public ImageView logo;
public RatingBar main_rating;
public TextView number_rating;
public ViewHolder( View itemView)
{
super(itemView);
mTitle = (TextView) itemView.findViewById(R.id.nom_business_main);
number_rating = (TextView) itemView.findViewById(R.id.number_rating);
mImg = (ImageView) itemView.findViewById(R.id.img_main);
main_rating=(RatingBar) itemView.findViewById(R.id.rating_main);
main_rating.setRating((float)1);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v)
{
Intent in = new Intent(v.getContext(), BusinessPremium.class);
int position = getAdapterPosition();
idb = businessList.get(position).getId();
in.putExtra("no", idb);
v.getContext().startActivity(in);
}
});
}
}
}
FeedsFragment.class
public class FeedsFragment extends Fragment
{
private ArrayList<Business> arrayBusiness,arrayBasics;
private Gson gson;
private static final Type BUSINESS_TYPE = new TypeToken<ArrayList<Business>>() {}.getType();
private RecyclerView.LayoutManager mLayoutManager;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View android = inflater.inflate(R.layout.fragment_feeds, container, false);
if (!internetConnectionCheck(FeedsFragment.this.getActivity()))
{
Toast.makeText(getApplicationContext(), "Error de Conexión", Toast.LENGTH_LONG).show();
}
new RequestBase(getActivity()) {
#Override
public JsonObject onHttpOk(JsonObject response) throws JSONException {
JsonObject objeto, pagination_details = null, details, premium_img;
JsonArray data;
if (getActivity() == null)
return response;
if (response.get("pagination") == null)
{
objeto = response;
} else {
objeto = response;
pagination_details = response.get("pagination").getAsJsonObject();
data = objeto.get("data").getAsJsonArray();
gson = new Gson();
arrayBusiness = gson.fromJson(data, BUSINESS_TYPE);
Log.d("size", String.valueOf(arrayBusiness.size()));
FeedsFragment.this.getActivity().runOnUiThread(new Runnable()
{
#Override
public void run()
{
RecyclerView recycler = (RecyclerView) FeedsFragment.this.getActivity().findViewById(R.id.recycler_main);
MainAdapter adapter = new MainAdapter(getActivity(), arrayBusiness, R.layout.main_row);
recycler.setNestedScrollingEnabled(false);
mLayoutManager = new GridLayoutManager(FeedsFragment.this.getActivity(), 2);
recycler.setLayoutManager(mLayoutManager);
recycler.setAdapter(adapter);
GifTextView loading = (GifTextView)FeedsFragment.this.getActivity().findViewById(R.id.loading);
TextView loadingText = (TextView)FeedsFragment.this.getActivity().findViewById(R.id.loadingText);
loading.setVisibility(View.GONE);
loadingText.setVisibility(View.GONE);
}
});
}
if (pagination_details.isJsonNull()) {
Log.d("Paginacion", pagination_details.toString());
}
return objeto;
}
#Override
public void onHttpCreate(JsonObject response) throws JSONException
{
}
#Override
public void onHttpUnprocessableEntity(JsonObject response) throws JSONException
{
this.cancel(true);
final String error = response.get("errors").toString();
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getActivity().getApplicationContext(), error, Toast.LENGTH_LONG).show();
}
});
}
}.execute("businesses/premiums", "GET");
return android;
}
}
you can refresh using SwipeRefreshLayout in android to refresh and in the on refresh override method call your api
note:put your API call request in a method and call that method inyour onRefresh method of SwipeRefreshLayout
When writing RecyclerView.Adapter, you anyway need to provide the getItemCount method that returns the correct number of items (may be large). RecyclerView will call on its own initiative the onBindViewHolder(holder, position) method of this adapter. All you need is to provide functionality of retrieving data, relevant to this position. There is no difference at all, if your list is smaller than screen, slightly larger than screen or Integer.MAX_VALUE size. RecyclerView will take care not to fetch/allocate too much extra items.
You do not need to implement scroll listeners or otherwise explicitly handle the scrolling.
The only tricky part is that you may need to take a long action like server call to get some items. Then just return uninitialized holder (empty view) on the first invocation and start fetching the needed row in the background thread. When you have it, call notifyDataSetChanged or notifyItemRangeChanged, and RecyclerView will take care to update itself.
For performance reasons I would strongly recommend to update content in chunks of the fixed size rather than sending individual server request per every row displayed. For some public servers like Google Books this is clearly a requirement, as they have quota limits per request.
If you need to view the full source code on how this possibly could be implemented, there is an open source project here in GitHub.
Make a static boolean variable named "ready" and initialize it to false.
Add the if ready condition in the onLoadMore method as below.
public boolean onLoadMore(int page, int totalItemsCount) {
if (ready) {
//load more from API
}
return false;
}
set ready to true in onBindViewHolder when the position of item is last.
Here is a way that a colleague of mine introduced. we worked in it together and i implemented it successfully with no issues. I wanted to give back to anyone having this issue.
in your adapter you need to set the count to be infinite size and then when you want the position of an item you should use val loopPos = position % dataSource.size anytime you need the position. lets take a look how this can be done in a recyclerView adapter but could also be applied to FragmentStatePagerAdapter.
class InfiniteLoopingHorizontalRecyclerViewAdapter(var dataSource: ArrayList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflatedView: View = LayoutInflater.from(parent.context)
.inflate(R.layout.your_finite_layout, parent, false)
return ItemHolder(inflatedView)
}
override fun getItemCount(): Int {
return Integer.MAX_VALUE //***** this should be high enough - wink wink ******
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
//****** this is critical here when you need the position use the loopPos ****/
val loopPos = position % dataSource.size
(holder as? ItemHolder)?.bind(dataSource[loopPos], loopPos)
}
inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(myString: String, position: Int) = with(itemView) {
myTextView.setText(myString)
}
}
}
how it works:
lets say your dataSource size is 50 but your position is at 51 that means the following: 51%50 . which gives you position 1. and lets say again your position is 57 and again your dataSource size is still 50. that means your position is 7. so to be clear, anytime you need a infinite affect you can use the modules of the position and the dataSource size.
ps:
lets go crazy and say we scrolled to position 11323232323214 then that means 11323232323214%50 = 14 so its position 14 in your datasource that will be used. you can then polish off the affect with wrapping your recyclerview in a SnapHelper class
You can add a scrollListener to your recyclerview.
Check a similar answer here
And the main SO post here
Where, the scrollListener will check where exactly are you in the recyclerview and based on some logic (which you can flexibly write) make a second call!

Delete Firebase object with swipe

I wish to delete an object from Firebase on Swipe left. Everything works fine now with the swipe and it it removed from the view, but it stays in the database.
I've added the following to my onCreate:
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
Toast.makeText(ListBox.this, "Item removed", Toast.LENGTH_SHORT).show();
//Remove swiped item from list and notify the RecyclerView
}
};
and this is how i populate my ViewHolder.
#Override
protected void populateViewHolder(final BoxViewHolder viewHolder, final Box model, int position) {
viewHolder.setTitle(model.getTitle());
final String boxUniqueKey = model.getBoxkey();
final DatabaseReference postRef = getRef(position);
final String postKey = postRef.getKey();
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Launch BoxItem view
final Intent intent = new Intent(ListBox.this, AddBoxItem.class);
String boxkey = model.getBoxkey();
String boxName = model.getTitle();
startActivity(intent);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(allBoxes);
}
});
}
but how (and where) do I get the position of the item, and how can I send the remove query to the Firebase Database?
I have coded something very similar to what you are trying to achieve. This is one way you could achieve it.
First, extend the ItemTouchHelper.SimpleCallback class to make your own custom class.
public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
private RecyclerAdapter adapter; // this will be your recycler adapter
private DatabaseReference root = FirebaseDatabase.getInstance().getReference();
/**
*
Make sure you pass in your RecyclerAdapter to this class
*/
public CallBack(int dragDirs, int swipeDirs, RecyclerAdapter adapter) {
super(dragDirs, swipeDirs);
this.adapter = adapter;
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition(); // this is how you can get the position
Object object = adapter.getObject(position); // You will have your own class ofcourse.
// then you can delete the object
root.child("Object").child(object.getId()).setValue(null);// setting the value to null will just delete it from the database.
}
Calling viewHolder.getAdapterPosition() returns the position of the view in the adapter. You can use this position to get the Object from the ArrayList contained in your recycler adapter.
In my adapter, I have created a getObject method. This just returns the object from the ArrayList that my adapter has. Once I have the object, I can call the associated Firebase Realtime Database method and delete the object. In my Object class, I have stored the unique key within the object so I can easily delete it. I get the unique id by calling getId(). I pass this to the associated Firebase Realtime Database method and set the value to null which deletes it.
After doing this you can add it to your recycler view like this.
ItemTouchHelper.SimpleCallback swipeToDeleteCallback = new
SwipeToDeleteCallback(0, ItemTouchHelper.RIGHT, choreRecyclerAdapter, getContext()); // Making the SimpleCallback
ItemTouchHelper touchHelper = new ItemTouchHelper(swipeToDeleteCallback);
touchHelper.attachToRecyclerView(recyclerView); // then attach it to your recycler view
First, you make a simple callback and make sure you instantiate the custom class that you extended. Be sure to pass your recycler adapter.
Notice I only support right swipe by passing ItemTouchHelper.Right. You can support left or pass in both left and right.
Then create an ItemTouchHelper object and pass it your simple callback.
Lastly, you attach your touch helper to your recycler view and that's all.

Large number of items in RecyclerView.Adapter - Memory Issue

Overview: I'm having a chat application. Till now, I was using CursorAdapter with a Listview to load my chat items in the list. But now, I'm planning to refactor the code to use RecyclerView with RecyclerView.Adapter and a "Load More" functionality like whatsapp.
Issue: Memory consumption. With CursorAdapter, items not in viewable area were getting Garbage Collected, but now since I'm using an ArrayList of my CustomModal, once you load all the items in the list (by clicking on the "Load More" button) I'm seeing high memory consumption in the memory logs (No Garbage Collection).
My guess is now, I'm loading all the items in an ArrayList and that is causing the issue. Is that it?
Is there a way to avoid the issue or optimize the problem?
EDIT:
Can't post the complete code here, but here is a snippet of the kind of Adapter that I've implemented:
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MyViewHolder> {
private ArrayList<MyModal> mMyModals;
public MessageAdapter(ArrayList<MyModal> mMyModals) {
this.mMyModals = mMyModals;
//... Some fields initialization here
}
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals = myModals;
//... Some fields initialization here
notifyDataSetChanged();
}
public void toggleLoadMore(boolean isLoadMoreEnabled){
if(isLoadMoreEnabled){
//..Checks if load more is already enabled or not
//..If not then enables it by adding an item at 0th poition of MyModal list
//..Then notifyDataSetChanged()
}else{
//..Checks if load more is already disabled or not
//..If not then disables it by removing an item at 0th poition of MyModal list
//..Then notifyDataSetChanged()
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder messageViewHolder = null;
View itemLayoutView = null;
MyModal.MessageType messageType = MyModal.MessageType.getMessageTypeFromValue(viewType);
switch (messageType){
case MESSAGE_TYPE1:
itemLayoutView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout1, null);
messageViewHolder = new Type1ViewHolder(itemLayoutView);
break;
case MESSAGE_TYPE2:
itemLayoutView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout2, null);
messageViewHolder = new Type2ViewHolder(itemLayoutView);
break;
}
return messageViewHolder;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final MyModal myModal = mMyModals.get(position);
MyModal.MessageType messageType = myModal.getMessageType();
holder.initialize(myModal);
}
#Override
public int getItemCount() {
return (mMyModals != null)?mMyModals.size():0;
}
#Override
public int getItemViewType(int position) {
return mMyModals.get(position).getMessageType().getValue();
}
public abstract class MyViewHolder extends RecyclerView.ViewHolder {
public MyViewHolder(View itemLayoutView) {
super(itemLayoutView);
}
public abstract void initialize(MyModal myModal);
}
class Type1ViewHolder extends MyViewHolder {
//...Variables
public Type1ViewHolder(View itemLayoutView) {
super(itemLayoutView);
//...variables initialization here
}
#Override
public void initialize(MyModal myModal) {
//...Setting values in view using myModal
}
}
class Type2ViewHolder extends MyViewHolder {
//...Variables
public TextViewHolder(View itemLayoutView) {
super(itemLayoutView);
//...variables initialization here
}
#Override
public void initialize(MyModal myModal) {
//...Setting values in view using myModal
}
}
}
First of all :
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals = myModals;
//... Some fields initialization here
notifyDataSetChanged();
}
Here you are creating a new arraylist and assigning it to your mMyModals. This means there are 2 arraylists at this point and they take up twice the amount of space than required. GC doesnt work the way you expect it to. Since the arraylist is initialized in your activity it will persist as long as the arraylist persists and so will the initial arraylist.
Instead of creating a new arraylist in your activity and passing it to changeList. Just clear your old arraylist and pass that.And also in adapter changeList method you can do the below
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals.clear();
this.mMyModels.addAll(myModels);
//... Some fields initialization here
notifyDataSetChanged();
}
Please let me know if i am not clear. Also show your activity code if this does not work.
Instead of replacing the whole ArrayList and calling notifyDataSetChanged, try adding the items to the ArrayList and then call notifyItemRangeInserted(int positionStart, int itemCount), maybe that could work. Also, you dont have to replace the Adapter's ArrayList. Your Activity/Fragment probably has the same ArrayList, just editing this list in your Activity/Fragment and then calling notifyItemRangeInserted(int positionStart, int itemCount) should do the trick. Also, instead of retrieving all the messages, you could also try to only get the next X amount of messages, so you wont retrieve the messages you already retrieved before (if you didn't do that already).

Categories

Resources