Using RecyclerView.setAdapter from worker thread - java

Using memberRecList RecyclerView, I'm trying to list every member of a group horizontally, but I get this issue when I set its adapter:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
NOTE: I've included some notes in the comments, scroll to the bottom to see them.
Here is the RecycleView initialization:
// I do InitMemberList() in the OnCreate method of my activity
private void InitMemberList()
{
LinearLayoutManager layoutManager =
new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
memberRecList = (RecyclerView) findViewById(R.id.member_list);
memberRecList.setHasFixedSize(true);
memberRecList.setLayoutManager(layoutManager);
//NOTE 1 (scroll to bottom)
}
My dataset is the ArrayList members. I retrieve it from a worker thread using FirebaseDatabase like so:
//this is also in the OnCreate() method, doesn't matter if it's before
//or after InitMembersList() because it starts a separate thread.
//I'm showing it just in case I have any errors here
private void GetMemberInfo(final String userId)
{
//NOTE 2 (scroll to bottom)
//Getting the reference of the database
DatabaseReference ref = userDB.getReference().getRef();
Query query = ref.child(userId);
//Firebase's method names are confusing. These below just retrieve the
//data from the database ONCE.
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
tempMember = dataSnapshot.getValue(User.class); //Getting member info
members.add(tempMember); //adding member info to the list
memberCount --; //number of members left to retrieve
if(memberCount == 0) {
//NOTE 3 (scroll to bottom)
PopulateMemberList(); //the adapter is set inside here
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
I set members adapter in the PopulateMemberList() method like so:
private void PopulateMemberList()
{
MembersListAdapter adapter = new MembersListAdapter(members);
memberRecList.setAdapter(adapter);
}
Here is my custom MembersListAdapter:
public class MembersListAdapter extends RecyclerView.Adapter<MembersListAdapter.MemberViewHolder> {
private ArrayList<User> users = new ArrayList<>();
public MembersListAdapter(ArrayList<User> users) {
this.users = users;
}
public class MemberViewHolder extends RecyclerView.ViewHolder{
TextView rep_txt;
ImageView memberImageView;
public MemberViewHolder(View itemView) {
super(itemView);
rep_txt = (TextView) itemView.findViewById((R.id.member_rep_text));
memberImageView = (ImageView) itemView.findViewById(R.id.member_profile_image);
}
}
#Override
public int getItemCount() {
return users.size();
}
#Override
public MemberViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//NOTE 4 (scroll to bottom)
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.member_list_item, parent, true);
return new MemberViewHolder(itemView);
}
#Override
public void onBindViewHolder(MemberViewHolder holder, int position) {
final User currentMember = users.get(position);
//changing the text of my textviews
//Loading Member Image
Picasso.with(GroupActivity.this)
.load(currentMember.getProfile_picture_ref())
.into(holder.memberImage);
}
NOTES:
NOTE 1: I tried setting the memberRecList adapter as an empty adapter at first inside InitMemberList() and then setting the new adapter once I got the data from Firebase. Didn't work. Gave me the same error.
NOTE 2: I use the GetMemberInfo() inside a loop where I pass the userId one by one. Firebase gurus will tell me to instead make a foreach loop with dataSnapshot.getChildren() to retrieve users one by one and then filter them, but it's not feasible because of the size of my database. I'd be scanning through a million users just to retrieve 5 of them.
NOTE 3: Using memberCount to find if member list retrieval is finished allows me to start working with the data after it is fully retrieved. Without this, trying to access the data gives Nullpointerexception because most of the it can't be received in time.
NOTE 4: I'm assuming this is where my error lies? I tried getting the itemView parent and then removing **itemView* from it (exactly what the error message says I should do):
#Override
public MemberViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.member_list_item, parent, true);
ViewGroup mParent = (ViewGroup) itemView.getParent();
if(mParent!=null)
mParent.removeView(itemView);
return new MemberViewHolder(itemView);
}
but I get a million instances of the following error:
E/AndroidRuntime: at android.view.ViewGroup.resetResolvedLayoutDirection(ViewGroup.java:6688)
and then a StackOverflow error and the app crashes:
D/Error: ERR: exClass=java.lang.StackOverflowError
D/Error: ERR: exMsg=stack size 8MB
D/Error: ERR: file=ViewGroup.java
D/Error: ERR: class=android.view.ViewGroup
D/Error: ERR: method=resetResolvedLayoutDirection line=6687
D/Error: ERR: stack=java.lang.StackOverflowError: stack size 8MB
at android.view.ViewGroup.resetResolvedLayoutDirection(ViewGroup.java:6687)
Afterwards, the last line is repeated at least 100 times and at the end:
D/Error: ERR: TOTAL BYTES WRITTEN: 41276
EDIT: Forgot to mention, I know FirebaseRecyclerAdapter exists, but I can't use it. If you know of other libraries which could help me, please do tell. Also, would've preferred to use a ListView rather than a RecyclerView, but I need to list the items horizontally (so that I can scroll left/right) and I think that's impossible with ListView.

You get an IllegalStateException because of this:
#Override
public MemberViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//NOTE 4 (scroll to bottom)
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.member_list_item, parent, true);
return new MemberViewHolder(itemView);
}
When you inflate a view for a RecyclerView you shouldn't add it to its parent, android does it for you.
Change the flag to false in the following line to not add view to parent:
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.member_list_item, parent, false);

Related

How to show 'NEW' tag after updating category recyclerView

How can I show NEW tag after updating category from database. Like this image
Only after if my category get Updated and show for 24 hrs.
This is my Adapter of Categories
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.viewHolder> {
ArrayList<RecipeModels> list;
Context context;
public RecyclerAdapter(ArrayList<RecipeModels> list, Context context) {
this.list = list;
this.context = context;
}
#NonNull
#Override
public viewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.recycler_view_set,parent,false);
return new viewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull viewHolder holder, int position) {
RecipeModels models = list.get(position);
holder.imageView.setImageResource(models.getPic());
holder.textView.setText(models.getText());
holder.itemView.setOnClickListener(view -> {
// It is sending data to category activity.
//Intent intent = new Intent(context, CategoryActivity.class);
//intent.putExtra("title",fruits.get(position).getTitle());
//intent.putExtra("name", fruits.get(position).getName());
//context.startActivity(intent);
});
}
#Override
public int getItemCount() {
return list.size();
}
public static class viewHolder extends RecyclerView.ViewHolder{
ImageView imageView;
TextView textView;
public viewHolder(#NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.imageView);
textView = itemView.findViewById(R.id.textView);
}
}
}
I don't have any idea to do this. Any Idea or code to implement this? I can add more code if you want, but please help to solve this issue!
simply query your data layer for lastUpdated <= now() - 24hrs window. All the responses from DB would be new elements only.
If you want distinction b/w new and old data within 1 result set, you can use if-else in the query to set a boolean flag isNew. basically, something like
select D.id, (IF D.lastUpdated >= now() - 24hrs THEN 1 ELSE 0) AS isNew from table D;
where
LastUpdated is a column on table D of type timestamp.
And is filled by application while writing the data to DB.
This should better to offload on DB, rather than App, since DB can use indexes to do this filter rather quick.
The above answer assumes there is a DB associated with app
If that's not the case, you can't do this labelling since you app does not have any state to compute the diff with. All vectors are filled only when app starts
You can use DiffUtils in your adapter to get the Changed/Updated data.Based on that, you can set the visibility of "New" tag from your card.
class​ ​CategoriesAdapter​(): BaseAdapter<Category>(
diffCallback = ​object​ : ​DiffUtil​.​ItemCallback​<​Category​>()
{
override​ fun areItemsTheSame(oldItem: ​Category​, newItem: ​Category​): ​Boolean​ {
TODO​("​Not​ yet implemented")
   }
override​ fun areContentsTheSame(oldItem: ​Category​, newItem: ​Category​): ​Boolean​ {
         ​TODO​("​Not​ yet implemented")     }
}) { }
This is how your Base Adapter's declaration will look like:
abstract​ ​class​ ​BaseAdapter​<​T​>(
diffCallback​:​ ​DiffUtil​.​ItemCallback​<​T​>)
:​ ​ListAdapter​<​T​, ​BaseViewHolder​>(
AsyncDifferConfig​.​Builder​<​T​>(diffCallback)
.setBackgroundThreadExecutor(​Executors​.newSingleThreadExecutor())
.build()
) { }
If possible, try and get a timestamp for each image from the server.
Then, compare it to the android system's current time.
Using an if else statement, if the time gap is within the 24 hour range, display the 'new' label. or else, set it to View.GONE.
Now, If that's not possible, You would have to create a database within the app itself which also creates its own time stamp of the images.
Then compare for each image and display label when necessary.

Android RecyclerView : Memory usage increases during scrolling if 'setIsRecyclabe(false)' is not used

In my android application(Java) I am displaying a list of around 1800 contacts in a recyclerview. While doing a memory profile it was found that when scrolling the recycler view the memory usage was increasing rapidly. So I found this question here which was mentioning the same problem and tried out the solution which was to setIsRecyclable(false) in onBindViewHolder and it worked. The profiling results are given below.
Case 1 : setIsRecyclable(False) not used
Initial memory usage : ~ 40M
[ Java=5.9M Native=5M Graphics=20.3M Stack=0.3M Code=5.3M Others =0.8M ]
Peak memory usage : ~ 345M
[ Java=187.5M Native=39.1M Graphics=101.5M Stack=0.4M Code=11.6M Others =6.5M ]
Also the peak memory usage was found to increase with increase in number of items in the list. After the continuous scrolling is stopped for a while the memory usage does come down but only to around 162 MB.
Case 2 : after adding setIsRecyclable(False) to onBindViewHolder
Initial memory usage : ~ 42M
[ Java=5.8M Native=5.5M Graphics=20.2M Stack=0.3M Code=9.4M Others =0.8M ]
Peak memory usage : ~ 100M
[ Java=43.9M Native=9.7M Graphics=32.6M Stack=0.4M Code=11.7M Others =2.2M ]
Also, in this case, memory usage was not affected significantly by increasing number of items in list. Although peak memory usage is about 100MB the average stays at around 70 MB for most of the time which is even better.
Source Code of fragment containing recyclerView
Note :
* Adapter class is defined as an inner class of Fragment class and ViewHolder class is defined as an inner class of Adapter class.
* 'App.personList' is a static arrayList holding the list of contacts and App is the ViewModel class.
* adapter1 is the only adapter of interest. Please avoid adapter2(handles another small list)
public class FragmentAllContacts extends Fragment
{
public static MainActivity main;
public RecyclerView contactsView, tagsView;
LinearLayoutManager llm, lln;
Button filterCloseButton;
CardView filterView;
Adapter_ContactListView adapter1;
Adapter_TagListView adapter2;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState)
{
setHasOptionsMenu(true);
main = (MainActivity) getActivity();
return inflater.inflate(R.layout.fragment_all_contacts, container, false);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
adapter1 = new Adapter_ContactListView(App.personList,getContext());
adapter2 = new Adapter_TagListView(App.tagList,getContext());
filterView = getView().findViewById(R.id.cardView7);
FloatingActionButton fab = getView().findViewById(R.id.create_contact_fab);
contactsView = getView().findViewById(R.id.allContacts_recyclerView);
contactsView.setAdapter(adapter1);
llm = new LinearLayoutManager(main.getBaseContext());
contactsView.setLayoutManager(llm);
contactsView.scrollToPosition(App.AllConnections.scrollPosition);
tagsView = getView().findViewById(R.id.allTags_recyclerView);
tagsView.setAdapter(adapter2);
lln = new LinearLayoutManager(main.getBaseContext(), LinearLayoutManager.HORIZONTAL, false);
tagsView.setLayoutManager(lln);
}
class Adapter_ContactListView extends RecyclerView.Adapter<Adapter_ContactListView.ViewHolder> implements Filterable {
List<Person_PersistentData> contactsFiltered;
Context context;
public Adapter_ContactListView(List<Person_PersistentData> list, Context context)
{
this.contactsFiltered = list;
this.context = context;
}
#Override
public Adapter_ContactListView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view_list, parent, false);
Adapter_ContactListView.ViewHolder pane = new Adapter_ContactListView.ViewHolder(v);
return pane;
}
#Override
public void onBindViewHolder(Adapter_ContactListView.ViewHolder pane, int position)
{
pane.setIsRecyclable(false);
final Person_PersistentData rec = contactsFiltered.get(position);
pane.nameView.setText(rec.personName + " (" + rec.personID + ")");
Uri imageUri = App.FSManager.getProfilePic(rec.personID);
if (imageUri != null) {pane.imageView.setImageURI(imageUri);}
else {pane.imageView.setImageResource(R.drawable.ico_60px);}
if (App.AllConnections.personSelectionStack.contains(rec.personID)) {pane.cv.setBackgroundColor(context.getResources().getColor(R.color.rgb_000_070_100));}
else
{pane.cv.setBackgroundColor(context.getResources().getColor(R.color.rgb_020_020_020));}
pane.cv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view)
{
if(App.AllConnections.selectionMode)
{
App.Person_SelectionInput(rec.personID);
Adapter_ContactListView.this.notifyDataSetChanged();
}
else
{
App.PersonInfo.id = rec.personID;
main.startTask(T.personInfo);
}
}
});
pane.cv.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view)
{
App.Person_SelectionInput(rec.personID);
Adapter_ContactListView.this.notifyDataSetChanged();
return false;
}
});
//animate(holder);
}
#Override
public int getItemCount() {
//returns the number of elements the RecyclerView will display
return contactsFiltered.size();
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
#Override
public Filter getFilter() { ... }
#Override
public long getItemId(int position)
{
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
class ViewHolder extends RecyclerView.ViewHolder {
CardView cv;
TextView nameView;
ImageView imageView;
public ViewHolder(#NonNull View itemView)
{
super(itemView);
cv = itemView.findViewById(R.id.cardView);
nameView = itemView.findViewById(R.id.name);
imageView = itemView.findViewById(R.id.imageViewZ);
}
}
}
}
Question
So 'setIsRecyclable(False)' is supposed to prevent recycling of views and this should be causing more memory usage. But instead it is showing the opposite behavior. Also i think the app will surely crash if it has to handle an even larger list without using setIsRecyclable(false). Why is this happening ?
getItemId(int) and getItemViewType(int) should NEVER return position itself, you're violating recyclerview contract by forcing it to create new viewholders for every single position intead of re-using existing views.
This is the cause of your issue - every position has unique itemViewType so they start to fill up recycledViewPool very rapidly since they're only being inserted and never being taken out of it. setIsRecyclable(False) circumvents the issue by not putting them in recyclerViewPool but that doesn't fix the problem of lack of view recycling.
Just delete getItemId and getItemViewType overrides because you're not using them properly.

I'm trying to filter a RecyclerView based on the object's value

so I got here a block of code which gets all my objects from Firebase/Firestore and set them in my RecyclerView:
public void getFromDatabase(String sortBy, String collectionPath, int minPrice, int maxPrice) {
Query query = db.collection(collectionPath).orderBy(sortBy, Query.Direction.DESCENDING);
FirestoreRecyclerOptions<Gpu> options = new FirestoreRecyclerOptions.Builder<Gpu>()
.setQuery(query, Gpu.class)
.build();
adapter = new FirestoreRecyclerAdapter<Gpu, GpuHolder>(options) {
#Override
protected void onBindViewHolder(#NonNull GpuHolder holder, int position, #NonNull Gpu gpu) {
holder.textViewModel.setText(gpu.getModel());
holder.textViewPrice.setText(String.valueOf(gpu.getPrice()));
holder.textViewBench.setText(String.valueOf(gpu.getBench()));
holder.textViewValue.setText(String.valueOf(gpu.getValue()));
holder.textViewType.setText(String.valueOf(gpu.getType()));
}
#NonNull
#Override
public GpuHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.gpulist_layout, parent, false);
return new GpuHolder(view);
}
};
adapter.notifyDataSetChanged();
adapter.startListening();
gpuRecycler.setAdapter(adapter);
}
As you can tell, the minPrice and maxPrice does nothing yet, basically, I wanted to make something like "if object price is higher than minPrice and lower than maxPrice, put it to the RecyclerView, otherwise, go back and repeat".
By the way, this is how the app currently looks like, the filters/sorting here work:
I'm only stuck on filtering based on price, so far i've tried putting the if statement like this (minPrice is set to 70 and max to 600):
#Override
protected void onBindViewHolder(#NonNull GpuHolder holder, int position, #NonNull Gpu gpu) {
if (gpu.getPrice() > minPrice && gpu.getPrice() < maxPrice) {
holder.textViewModel.setText(gpu.getModel());
holder.textViewPrice.setText(String.valueOf(gpu.getPrice()));
holder.textViewBench.setText(String.valueOf(gpu.getBench()));
holder.textViewValue.setText(String.valueOf(gpu.getValue()));
holder.textViewType.setText(String.valueOf(gpu.getType()));
}
}
But I can't do an else {continue;} since this is not a for loop. So it does weird things like this:
for some reason, when I scroll down and up, these "TextView" objects disappear, and my "sort descending/ascending" does not work anymore.
if it may help, this is how i do the descend/ascend, i just reverse/unreverse the layout : LinearLayoutManager(getApplicationContext(), LinearLayoutManager.VERTICAL, true);
so yeah, anyone got any idea how to do this? if there's any info I should provide please tell me!
I would have when the different sorting selections are picked they call different methods and use the Collections.sort(options, new Comparator();
public void sortByPrice(){
Collections.sort(options, new Comparator<Gpu>() {
public int compare(Gpu gpu1, Gpu gpu2) {
// do your comparison of prices here
});
adapter.notifyDatasetChanged();
}
On the “onClickListener() or whatever event listener you use for when they select the filter they want is where you’re to put the method call
For sorting this will work, but for an actual filter what you will need to do is use a method like this
public void sortByPrice(){
ArrayList<Gpu> filteredList = new ArrayList();
for(Gpu gpu : options){
if(//whatever your requirements are){
filteredList.add(gpu);
}
}
adapter.setItems(filteredList);
adapter.notifyDatasetChanged();
}

What is notifyItemRangeChanged(0, this.data.size()); in this example and how does it work?

I understand how a ViewHolder's onBindViewHolder works, however I'm unclear about how notifyItemRangeChanged(0, this.data.size()); works in this example and what it does exactly.
The data that is supplied to this adapter is in Json format.
The adapter is below:
public class AdapterQuestion extends RecyclerView.Adapter<AdapterQuestion.ViewQuestion>{
private LayoutInflater mLayoutInflater;
//this is an arrayList of questionData objects
private ArrayList<QuestionData> data =new ArrayList<>();
//Created the layoutInflator
public AdapterQuestion(Context context){
//get from context
mLayoutInflater=LayoutInflater.from(context);
}
public void setBloglist(ArrayList<QuestionData> data){
this.data =data;
notifyItemRangeChanged(0, this.data.size());
}
#Override
public ViewQuestion onCreateViewHolder(ViewGroup parent, int viewType) {
//inflates the customQuestion view or converts it to java code
View view= mLayoutInflater.inflate(R.layout.customquestion, null);
//We now want to convert the View into a ViewQuestion, view Question takes
//a view so we pass the view into view question and then return it.
ViewQuestion holder=new ViewQuestion(view);
return holder;
}
//ViewGroup parent and ViewType are not being assigned.
#Override
public void onBindViewHolder(ViewQuestion holder, int position) {
//here we need to bind the data to our view, there is currently no Data!
//We need to get the data from our JSON
//Parameters is a ViewHolder and a Position
//This gives us the current information object from the whole arraylist
//data.get(position) data is the arraylist and we are getting the current position or index;
//That current obj is of Type QuestionData
QuestionData currentObj= data.get(position);
//we are accessing the Inflated view, or saved view with holder
//holder.answerText is the textView in holder. We are then taking that current object
//We are getting the text of the current object and setting it to the AnswerText view
holder.answerText.setText(currentObj.getMtext());
holder.answerId.setText(currentObj.getId());
holder.mVotes.setText(currentObj.getVotes());
holder.mLikeButton.setTag(currentObj);
}
#Override
public int getItemCount() {
return data.size();
}
public class ViewQuestion extends RecyclerView.ViewHolder{
//once we create it once the reclycer view will automatically recycle it
private TextView answerText;
private TextView answerId;
private TextView mVotes;
private LikeButton mLikeButton;
public ViewQuestion (View itemView){
super(itemView);
//here we are finding the views by their ID
answerText=(TextView)itemView.findViewById(R.id.answerText);
answerId=(TextView)itemView.findViewById(R.id.answerId);
mVotes=(TextView)itemView.findViewById(R.id.VoteTextView);
mLikeButton= (LikeButton)itemView.findViewById(R.id.heart_buttons);
mLikeButton.setOnLikeListener(new OnLikeListener() {
#Override
public void liked(LikeButton likeButton) {
Voting vote = new Voting();
vote.onUpVote(convertToString(),
getAdapterPosition(),ViewQuestion.this);
System.out.print("Adapter Position"+getAdapterPosition());
}
#Override
public void unLiked(LikeButton likeButton) {
Voting onDown=new Voting();
onDown.onDownVote(convertToString(),
getAdapterPosition(), ViewQuestion.this);
}
});
}
public String getVoteView(){
String voteView=mVotes.getText().toString();
return voteView;
}
public String convertToString(){
String converted=answerId.getText().toString();
return converted;
}
public int convertToInt(){
String converted=answerId.getText().toString();
int ConvertedInt=Integer.parseInt(converted);
return ConvertedInt;
}
}
}
When the data that is to be set in RecyclerView is changed, the Adapter needs to get notified of the data change so that it can change the data in recyclerview.
The method
notifyItemRangedChanged(fromIndex,toIndex);
is used to notify the adapter that some set of data is changed among the whole data and it tells the adapter that adapter should refresh the data and reload it into the recyclerView starting from fromIndex to toIndex as passed into the method .
use this method if you have multiple data changed but not all , those changed data also are in cluster so that you can say from 5th to 10th index data are changed .
If all data are changed call :
notifyDataSetChanged();
if only one dataItem is changed then call :
notifyItemChanged(dataPosition);
Using notifyItemRangeChanged(0, this.data.size()) it’s bad practice.
Best way is using notifyItemChanged or notifyItemRangeChanged with payload.
Payload - optional parameter (key). That give you opportunity to check what kind of update do you need.
public void onBindViewHolder(/*...*/, List payloads) {
if (payloads.isEmpty()) {
setText(holder, position);
downloadBitmap(holder, position);
} else if (payloads.contains(SET_ONLY_TEXT)){
setText(holder, position);
}
}
In this example payloads used for checking when adapter should update only the text.
in your case you are not doing it(notifyItemRangeChanged) right as you might as well can call notifyDataSetChanged(); because you are telling the adapter that the entire list has changed and not specific position.

Get to know device screen size programmatically in Android?

Please have a look at related Question. I got to know some hint from comments and now putting it in a new way.
I am using a RecyclerView with StaggeredGridView adapter to display the data. How do I get to know that how many items needed to be loaded for current devices to fit the whole screen?
process
Determine the screen size
Determine how many items needed to be loaded to fit full screen
Fetch data from Server with number of items
Display them
When user scroll down device fetch same amount of items and so on.
Question
I am not able to understand How to get done with first two points.
Code for Adapter
public class StaggeredGridAdapter extends RecyclerView.Adapter<StaggeredGridAdapter.StaggeredGridView> {
private Context context;
private String INTENT_VALUE = "warehouse";
private List<Warehouse> warehouses = new ArrayList<Warehouse>();
private AppDelegate app;
int size;
public StaggeredGridAdapter(Context context) {
this.context = context;
app = (AppDelegate) context;
}
public void addItems(List<Warehouse> response) {
size = response.size();
warehouses = response;
}
#Override
public StaggeredGridView onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.grid_item, parent, false);
StaggeredGridView staggeredGridView = new StaggeredGridView(layoutView);
return staggeredGridView;
}
#Override
public void onBindViewHolder(StaggeredGridView holder, int position) {
holder.textView.setText(warehouses.get(position).getFace());
}
#Override
public int getItemCount() {
return size;
}
class StaggeredGridView extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textView;
public StaggeredGridView(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.img_name);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
Intent intent = new Intent(context, WareHouseDetailActivity.class);
intent.putExtra(INTENT_VALUE, warehouses.get(getAdapterPosition()));
v.getContext().startActivity(intent);
}
}
}
code for filling data into adapter
mRecyclerView = (RecyclerView) mActivity.findViewById(R.id.staggering_grid);
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
mAdapter = new StaggeredGridAdapter(mContext);
mAdapter.addItems(response);
mRecyclerView.setAdapter(mAdapter);
As to find the Height and Width, you can use the DisplayMetrics.
final DisplayMetrics displayMetrics = this.getApplicationContext().getResources().getDisplayMetrics();
This displayMetrics will give Height, Width and Density. Use the height of the single View (or row) and the total height to calculate the number of items.
Why do you want the Exact number of items, if it's a server call you are making, you could just make a rough estimate on the number of items based on the device size. Also, I think it's better to keep the number of items sent by server a constant and not dependent on the device height. If you received more than the number of items you can fit in the screen, well, the user can scroll to see them anyways.
Rather than trying to work out how much you'll need to load, you could set it up to automatically load more when you're close to the end of the list.
Consider this method:
#Override
public void onBindViewHolder(StaggeredGridView holder, int position) {
holder.textView.setText(warehouses.get(position).getFace());
}
This is called when the item is ready to be displayed on the screen. By doing something like if(position > getItemCount() - 5{ // load more } then you could be automatically loading more as and when they're required.

Categories

Resources