Multiple header in recyclerview android - android

I have created a recyclerview for displaying data which is fetching from the server. I had used only single layout for displaying the data.
Now my requirement is like when I upload images or videos, then the uploading status should be displayed on top of the data which is displaying from the server. i-e on 0th position of recyclerview. I can add any number of images or videos.
after image or video successfully upload i also want to remove that row from recyclerview. I thought of doing using getItemViewType(). In this using two layout. I don't know this method is correct or not. I am not getting any solution to this,
Please.....
any help...

You can easily achieve that using the library SectionedRecyclerViewAdapter.
You should first create a section class:
class MySection extends StatelessSection {
String title;
List<String> list;
public MySection(String title, List<String> list) {
// call constructor with layout resources for this Section header, footer and items
super(R.layout.section_header, R.layout.section_footer, R.layout.section_item);
this.title = title;
this.list = list;
}
#Override
public int getContentItemsTotal() {
return list.size(); // number of items of this section
}
#Override
public RecyclerView.ViewHolder getItemViewHolder(View view) {
// return a custom instance of ViewHolder for the items of this section
return new MyItemViewHolder(view);
}
#Override
public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
MyItemViewHolder itemHolder = (MyItemViewHolder) holder;
// bind your view here
itemHolder.tvItem.setText(list.get(position));
}
#Override
public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
return new SimpleHeaderViewHolder(view);
}
#Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
MyHeaderViewHolder headerHolder = (MyHeaderViewHolder) holder;
// bind your header view here
headerHolder.tvItem.setText(title);
}
}
Then you set up the RecyclerView with your Sections:
// Create an instance of SectionedRecyclerViewAdapter
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();
MySection uploadsSection = new MySection("Uploads", uploadList);
MySection downloadsSection = new MySection("Downloads", downloadList);
// Add your Sections
sectionAdapter.addSection(uploadsSection);
sectionAdapter.addSection(downloadsSection);
// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
This way you manage the items from your upload and download lists separately by removing them from uploadList/downloadList then notifying the changes to the adapter. The upload items will always be displayed at the top of the RecyclerView because they are in the first section added to the adapter.
If you have different layouts for uploads and downloads you can create a different Section class for each.

Make two viewTypes like:
private static final int REGULAR_HOLDER = 1;
private static final int LOADING_HOLDER = 2;
Override getItemViewType and return LOADING_HOLDER for position 0, and REGULAR for all others. Also have state if you are loading or not. If you are not loading anything you will return REGULAR_HOLDER for all rows (positions).
Then in onCreate check if you have REGULAR or LOADING viewType, and create proper Holder. Important: make your Adapter implement RecyclerView.Adapter<VH> not your custom implementation of ViewHolder.
onBind executes next. There you will have to check if your viewHolder object you get is instance of RegularViewHolder or instance of LoadingViewHolder, like:
if (holder instance of RegularViewHolder) {
holder.doStuff();
} else if (holder instance of LoadingViewHolder) {
holder.showLoading();
}
Now, before this you should made two layouts. One is for your regular rows, and other is for row that will show loading. Make two classes that implement ViewHolder, in example above i called them RegularViewHolder and LoadingViewHolder.
EDIT: few things to keep in mind. I told you to keep a loading state (loading or not loading), so if you want to remove LOADING row, you could make that change and call notifyDataSetChanged();. Now, getItemViewType should return all REGULAR rows if you did it right.
Also you should keep in mind that if you want to show 10 rows of your data. Your getItemCount() should return 11 (10 + loading row) if there is loading happening. Also, in that case your data rows start from second row (position 1).

Related

Initial data from ListAdapter gets erased when using DiffUtil.ItemCallback

In my app I have two LiveData objects, one for getting items from 0-10 and second to get the items from 11-20. I'm trying to load the data in a RecyclerView but instead of having 20 items, the first 10 (0-10) are replaces with new 10 (11-20). This is what I have tried:
recyclerView = findViewById(R.id.recycler_view);
adapter = new ItemsAdapter();
recyclerView.setAdapter(adapter);
viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
To get items from 0-10 I use this method:
private void getInitialItems() {
ItemListLiveData liveData = viewModel.getItemsLiveData();
liveData.observe(this, itemtList -> adapter.submitList(itemtList));
}
To get items from 11-20 I use this method:
private void getNextlItems() {
ItemListLiveData liveData = viewModel.getItemsLiveData();
liveData.observe(this, itemtList -> adapter.submitList(itemtList));
}
This is my ViewModel class:
public class ItemListViewModel extends ViewModel {
private ItemListRepository repository = new ItemListRepository();
ItemListLiveData getItemsLiveData() {
return repository.getItemListLiveData();
}
}
In the repository I only get the items from a back-end server. This is my adapter class:
public class ItemsAdapter extends ListAdapter<Item, ItemsAdapter.ItemViewHolder> {
ItemsAdapter() {
super(diffCallback);
}
#NonNull
#Override
public ItemViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
//Inflate the view
}
#Override
public void onBindViewHolder(#NonNull final ItemViewHolder holder, int position) {
//Bind the Item according to position
}
private static DiffUtil.ItemCallback<Item> diffCallback = new DiffUtil.ItemCallback<Item>() {
#Override
public boolean areItemsTheSame(#NonNull Item oldItem, #NonNull Item newItem) {
return oldItem.id.equals(newItem.id);
}
#Override
public boolean areContentsTheSame(#NonNull Item oldItem, #NonNull Item newItem) {
return oldItem.equals(newItem);
}
};
}
My expectation is when using DiffUtil.ItemCallback to get both lists as a cumulative list since all the objects are different. Even if I pass both lists to the same adapter, I end up having only ten items (11-20). How to use submit list so I can have 20 items in my list and not only 10 (11-20)?
DiffUtil.ItemCallback is used for animating smoothly changes in dataset in adapter.
For example if you have have 10 items, than submit list with 9 items that were contained in previous 10, DiffUtil.ItemCallback will determine difference between old and new list, which position that element was and animate changes accordingly. What you are looking for in your case is Pagination where you can expand/show items while scrolling.
You don't need two LiveData for this one, you cast fetch data from some source add it to LiveData of Pagination. First it will be showed 10 items, then if you scroll to end another 10, and so on. You can adjust type of pagination by your needs with provided Configuration.
To do all that without Pagination.
liveData.observe(this, itemtList -> adapter.submitList(adapter.getCurrentList().addAll(itemtList)));
Get previous data, on top of that data add new data and it will all be shown.

add recyclerView inside another recyclerView

I have a list of data to be displayed in a recycler view and that is working fine. Now I have another dynamic list of data to be displayed inside the parent recycler-view. So I tried using another recycler-view inside the parent recycler-view, that is not working fine. It will be good if I get some idea of using recycler-view inside another one. Thanks in advance..!
I have illustrated my problem with an example:
For eg: I have a parent recyclerView with five linearLayout and I have created a child recyclerView inside the Linearlayout with visibility GONE. Now when I click the first Linearlayout I am changing the visibility of child recyclerView for the first Linearlayout to VISIBLE and attaching a separate view to it and same concept for all the other Linearlayouts. What happens is when I click first, second, third and fourth linearLayout the child recyclerView is not displaying date which I pass to it, all those first, sec, third and fourth data are accumulated and displayed in the last (i.e) inside fifth linearLayout.
Here is my parent recyclerview code:
class CardAdapter extends RecyclerView.Adapter<CardAdapter.MyViewHolder>
{
RecyclerView insideCardRecyclerView,recyclerView;
List<String> monthsWeek = new ArrayList<>();
List<String> dealers = new ArrayList<>();
List<String> dealersList = new ArrayList<>();
List<String> date = new ArrayList<>();
HashSet<String> dealersListHash = new HashSet<>();
public CardAdapter(List<String> monthsWeek,List<String> dealers,List<String> date)
{
this.monthsWeek = monthsWeek;
this.dealers = dealers;
this.date = date;
}
public class MyViewHolder extends RecyclerView.ViewHolder
{
ProgressBar progressBar;
RecyclerView recyclerView;
TextView period;
LinearLayout linearLayoutParent,linearLayoutCardDetails;
public MyViewHolder(View view)
{
super(view);
linearLayoutParent = (LinearLayout) view.findViewById(R.id.card_view_linear_parent_layout);
linearLayoutCardDetails = (LinearLayout) view.findViewById(R.id.linear_card_layout_details);
period = (TextView) view.findViewById(R.id.period_summary_graph_card);
insideCardRecyclerView = (RecyclerView) view.findViewById(R.id.summary_graph_card_view_recycler_view);
}
}
#Override
public CardAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.from(getActivity())
.inflate(R.layout.summary_card_view,parent,false);
recyclerView = (RecyclerView) parent;
return new CardAdapter.MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(final MyViewHolder holder, final int position)
{
holder.period.setText(monthsWeek.get(position));
holder.linearLayoutParent.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view)
{
if(searchClick)
{
for (String date1 : date)
{
if(Objects.equals(date1,monthsWeek.get(position)))
{
Log.e("Summary123 date..///", date1);
dealersList.add(dealers.get(date.indexOf(date1)));
}
}
searchClick = false;
holder.linearLayoutCardDetails.setVisibility(View.VISIBLE);
dealersListHash.addAll(dealersList);
dealersList.clear();
dealersList.addAll(dealersListHash);
//if the condition is true i am attaching another recyclerview inside this.
cardAdapterList = new CardAdapterList(dealersList);
LinearLayoutManager mLayoutManager1 = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL,true);
mLayoutManager1.setReverseLayout(false);
insideCardRecyclerView.setLayoutManager(mLayoutManager1);
insideCardRecyclerView.setItemAnimator(new DefaultItemAnimator());
insideCardRecyclerView.setAdapter(cardAdapterList);
}
else
{
searchClick = true;
holder.linearLayoutCardDetails.setVisibility(View.GONE);
}
}
});
}
#Override
public int getItemCount() {
return monthsWeek.size();
}
}
I've been in trouble sometimes with RecyclerView when multiple lists are needed to be shown in a single page of the application. Its not a very good idea actually to have multiple lists in a single layout but however, the idea of having a ScrollView and the lists inside that ScrollView is even worse.
I had to implement a ListView inside a ScrollView once and yes it was not a very good experience. Firstly, my list was not scrolling at all. Then I had to add some code to disable the scrolling when the touch is detected inside the list. It was not a very good idea of solving the actual problem. I had another problem of having a fixed height of the ListView. In case of list items with dynamic heights, the solution failed.
Having two lists in the layout, one after one is not a good idea either. As the first list need to have a fixed height.
So, after searching for suggestions about how can I implement two lists in a single layout file, I found most of the developers suggests of having a single list with a header and footer if necessary. Later, I could manage to show two lists in a single RecyclerView using my custom Adapter. I thought I should save some of my code for future use and hence, you see this note.
You can refer this sample code.

Android Room Pagination not working

I am trying to use android Room API to load records from sQlite in pages.
The issue is Paging library is loading entire database into model class and binding it with the adapter which is making UI thread skip frames. It suppose to load 20 records and then keep on adding more when required
This is my view model class
public class UserViewModel extends ViewModel {
public LiveData<PagedList<User>> userList;
public UserViewModel() {
}
public void init(UserDao userDao) {
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(10)
.setPageSize(20).build();
userList = (new LivePagedListBuilder(userDao.usersByFirstName(),
pagedListConfig))
.build();
}
}
Paged adapter
public class UserAdapter extends PagedListAdapter<User, UserAdapter.UserItemViewHolder> {
protected UserAdapter() {
super(User.DIFF_CALLBACK);
}
#Override
public UserItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.item_user_list, parent, false);
return new UserItemViewHolder(view);
}
#Override
public void onBindViewHolder(UserItemViewHolder holder, int position) {
User user= getItem(position);
if(user!=null) {
holder.bindTo(user);
}
}
static class UserItemViewHolder extends RecyclerView.ViewHolder {
TextView userName, userId;
public UserItemViewHolder(View itemView) {
super(itemView);
userId = itemView.findViewById(R.id.userId);
userName = itemView.findViewById(R.id.userName);
}
public void bindTo(User user) {
userName.setText(user.firstName);
userId.setText(String.valueOf(user.userId));
}
}
}
Binding with recycler View:
UserViewModel viewModel =
ViewModelProviders
.of(this)
.get(UserViewModel.class);
viewModel.init(userDao);
final UserAdapter userUserAdapter = new UserAdapter();
viewModel.userList.observe(this, pagedList -> {
Toast.makeText(this, "Page " + pagedList.size(), Toast.LENGTH_SHORT).show();
Log.e("Paging ", "Page " + pagedList.size());
userUserAdapter.setList(pagedList);
});
recyclerView.setAdapter(userUserAdapter);
02-18 10:19:40.409 15310-15310/com.androidkt.archpaging E/Paging: Page
200
Any idea what I am missing.
By the paging implementation, your result count should indeed be the full size of the query (200), as you configured to do so, the RecyclerView will receive placeholders null for the views which data is not ready yet. This is intended for when you want to show the whole list views but only bind the content of it when the data is available. But your RecyclerView should not call onCreateViewHolder and onBindViewHolder for the entire count unless it is visible.
Check (put a breakpoint) the method onMeasure or onLayout on your RecyclerView to see if the method is not returning a bigger height than expected (probably the expected is something around the size of your screen). Sometimes the actual height of RecyclerView is much bigger than the screen and the adapter call onBindViewHolder() for the total number of items because it's "visible" to it instead of the number we can see. This would trigger the DataSource to query the database and bind the views before you want.
try userUserAdapter.setList(pagedList); put out observe . observe use listner list change . You . You need to initialize the list and set up recyclerview normally .
The object list should be included in the pageAdapter in the usual way
You should call userUserAdapter.submitList(pagedList).
setList() is used for RecyclerView.Adapter not for PagedListAdapter.

Force resort of Android appcompat `SortedList`?

I have a SortedList being displayed in a RecyclerView by my RecyclerView.Adapter.
I use 2 custom Comparator instances from withing the SortedListAdapterCallback.compare() method to either sort A-Z or Z-A.
static class A2Z implements Comparator<Item> {
#Override
public int compare(Item t0, Item t1) {
return t0.mText.compareTo(t1.mText);
}
}
static class Z2A extends A2Z {
#Override
public int compare(Item t0, Item t1) {
return -1 * super.compare(t0, t1);
}
}
Item simply contains a single String mText;
I use my comparators in the SortedListAdapterCallback.compare() method:
private Comparator<Item> a2z = new A2Z();
private Comparator<Item> z2a = new Z2A();
private Comparator<Item> comparator = z2a;
#Override
public int compare(Item t0, Item t1) {
return comparator.compare(t0, t1);
}
I change the comparators on a button press. The list on screen does not update.
After logging values in the various methods, I can tell that the list itself is not updating. Notifying the adapter of changes simply redraws the old list, without resorting it.
So how do I force the underlying SortedList to resort all the items?
Perhaps it is best to just create a new Adapter each time, as in this question:
RecyclerView change data set
SortedList does not have functionality to resort itself - each instance only has a single sort order.
Went with creating a new adapter for each resort, as per Yigit's answer to the above referenced question:
If you have stable ids in your adapter, you can get pretty good
results (animations) if you create a new array containing the filtered
items and call
recyclerView.swapAdapter(newAdapter, false);
Using swapAdapter hints RecyclerView that it can re-use view holders.
(vs in setAdapter, it has to recycle all views and re-create because
it does not know that the new adapter has the same ViewHolder set with
the old adapter).
Use a switch statement inside the compare method with a local control flag (an enum is a good idea).
After changing the switch flag, call sortedList.replaceAll.
#Override
public int compare(PmpRole pmpRoleA, PmpRole pmpRoleB) {
switch (mSorter){
case IDX:
return pmpRoleA.getIdx().compareTo(pmpRoleB.getIdx());
case TITLE:
return pmpRoleA.getTitleIdx().compareTo(pmpRoleB.getTitleIdx());
case ID_IDX:
return pmpRoleA.getIdIdx().compareTo(pmpRoleB.getIdIdx());
}
return -1;
}
public void setSorter(Sorter sorter){
mSorter = sorter;
mPmpRoleSortedList.replaceAll(mPmpRoles);
}
Maintains animation functionality etc.

Display new items at the top of a ListView

I'm using a list to populate a ListView (). The user is able to add items to the list. However, I need the items to be displayed at the top of the ListView. How do I insert an item at the beginning of my list in order to display it in reverse order?
By default list adds elements at bottom. That is why all new elements you add will show at bottom. If you want it in reverse order, may be before setting to listadapter/view reverse the list
Something like:
Collections.reverse(yourList);
Another solution without modifying the original list, override getItem() method in the Adapter
#Override
public Item getItem(int position) {
return super.getItem(getCount() - position - 1);
}
Updated: Example
public class ChatAdapter extends ArrayAdapter<ChatItem> {
public ChatAdapter(Context context, List<ChatItem> chats) {
super(context, R.layout.row_chat, chats);
}
#Override
public Item getItem(int position) {
return super.getItem(getCount() - position - 1);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = inflater.inflate(R.layout.row_chat, parent, false);
}
ChatItem chatItem = getItem(position);
//Other code here
return convertView;
}
}
You should probably use an ArrayAdapter and use the insert(T, int) method.
Ex:
ListView lv = new ListView(context);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, R.id...);
lv.setAdapter(adapter);
...
adapter.insert("Hello", 0);
The ListView displays the data as it is stored in your data source.
When you are adding in your database, it must be adding the elements in the end. So, when you are getting all the data via the Cursor object and assigning it to the ArrayAdapter, it is in that order only. You should basically be trying to put data in the beginning of the database, rather that in the end, by having some time-stamp maybe.
Using ArrayList, you can do it by Collections.reverse(arrayList) or if you are using SQLite, you can use order by.
You can add element at the beginning of the list: like
arraylist.add(0, object)
then it will always display the new element at the top.
mBlogList is a recycler view...
mBlogList=(RecyclerView) findViewById(R.id.your xml file);
mBlogList.setHasFixedSize(true);
LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
mBlogList.setLayoutManager(mLayoutManager);//VERTICAL FORMAT
You could always have a datestamp in your objects and sort your listview based on that..
public class CustomComparator implements Comparator<YourObjectName> {
public int compare(YourObjectName o1, YourObjectName o2) {
return o1.getDate() > o2.getDate() // something like that.. google how to do a compare method on two dates
}
}
now sort your list
Collections.sort(YourList, new CustomComparator());
This should sort your list such that the newest item will go on top
You can always use a LinkedList instead and then use addFirst() method to add elements to your list and it will have the desired behaviour (new items at the top of the ListView).

Categories

Resources