I have an app that uses a recyclerView to show results from Google Books API.
In every onBindViewHolder, I ask the client to give me data which leads me eventually to exceeding the rate limit since every scroll calls data.
Let say I got data for position 1-5 and I scroll down to position 6-10 and then go back to 1-5. How can I make sure that it won't call the client again for positions 1-5 since it has already loaded them?
I just want whatever it already called to stay there.
My adapter looks like this ( I deleted some parts like more views so it wont confuse):
public class MyBooksAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<DiscoverBooks> discoverBooks;
private FirebaseFirestore db;
private FirebaseAuth auth;
private String UID, BookID2;
private int count, gbsSize, counter;
interface OnDiscoverBookClickListener {
void onClick(DiscoverBooks discoverBooks);
}
private OnDiscoverBookClickListener listener;
public MyBooksAdapter(int counter, List<DiscoverBooks> discoverBooks, int gbsSize, OnDiscoverBookClickListener listener) {
this.counter = counter;
this.discoverBooks = discoverBooks;
this.listener = listener;
this.gbsSize = gbsSize;
}
#Override
public int getItemViewType(int position) {
return AppConstants.IS_NOT_ADS_POSITION;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int viewType) {
View view;
view = LayoutInflater.from( viewGroup.getContext() ).inflate( R.layout.item_book_mybook, viewGroup, false );
return new DiscoverBooksViewHolder( view );
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
holder.setIsRecyclable( false );
if (holder instanceof DiscoverBooksViewHolder) {
((MyBooksAdapter.DiscoverBooksViewHolder) holder).bind( (discoverBooks.get( position )), holder );
}
}
#Override
public int getItemCount() {
return discoverBooks.size();
}
class DiscoverBooksViewHolder extends RecyclerView.ViewHolder {
private Button tv_Link;
private CardView cardView;
private ImageView Iv_BookCover;
private TextView tv_Author, tv_BorrowedTo, tv_BorrowedTill, tv_DateAdded, tv_Title;
private ImageButton ib_Options;
private ToggleButton tb_Status;
private DiscoverBooks discoverBooks;
private DiscoverBooksViewHolder(View itemView) {
super( itemView );
cardView = itemView.findViewById( R.id.imagecard );
Iv_BookCover = itemView.findViewById( R.id.iv_BookCover );
tv_Title = itemView.findViewById( R.id.tv_Title );
tv_Author = itemView.findViewById( R.id.tv_Author );
tv_DateAdded = itemView.findViewById( R.id.tv_DateAdded );
tv_BorrowedTo = itemView.findViewById( R.id.tv_BorrowedTo );
tv_BorrowedTill = itemView.findViewById( R.id.tv_BorrowedTill );
tv_Link = itemView.findViewById( R.id.tv_Link );
ib_Options = itemView.findViewById( R.id.ib_Options );
tb_Status = itemView.findViewById( R.id.tb_Status );
}
private void bind(DiscoverBooks discoverBooks, RecyclerView.ViewHolder viewHolder) {
this.discoverBooks = discoverBooks;
db = FirebaseFirestore.getInstance();
auth = FirebaseAuth.getInstance();
String BookID = discoverBooks.getBookID();
if (BookID.length() > AppConstants.UPLOADED_BOOK_LENGTH) {
db.collection( "Books" ).document( BookID ).get()
.addOnCompleteListener( task -> {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
DO SOMETHING
}
}
} );
} else {
//HERE I CALL GOOGLE BOOKS API
MyBookClient.getInstance().getBooks( BookID, new JsonHttpResponseHandler() {
#Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
if (response != null) {
final MyBook books = MyBook.fromJson( response );
//DO SOMETHING
}
}
} );
}
}
}
}
Ok, I understood your problem. For every bookID, you fetch its data from Google Books, and now you want to not hit the api for already loaded items.
Simply, create a global variable for ArrayList as
private ArrayList<MyBook> mybooks = new ArrayList();
Call your onBind() in onBindViewHolder() as
((MyBooksAdapter.DiscoverBooksViewHolder) holder).bind(position, discoverBooks.get( position), holder);
Replace your onBind() with this:
private void bind(int position, UserData discoverBooks, RecyclerView.ViewHolder viewHolder) {
this.discoverBooks = discoverBooks;
db = FirebaseFirestore.getInstance();
auth = FirebaseAuth.getInstance();
String BookID = discoverBooks.getBookID();
if (BookID.length() > AppConstants.UPLOADED_BOOK_LENGTH) {
db.collection("Books").document(BookID).get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
DO SOMETHING
}
}
});
} else {
if (position >= myBooks.size() || myBooks.get(position) == null) {
//If the value doesn't exist in the ArrayList,
//it will hit the Google Books API
MyBookClient.getInstance().getBooks(BookID, new JsonHttpResponseHandler() {
#Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
if (response != null) {
final MyBook books = MyBook.fromJson(response);
myBooks.add(position, books);
//DO SOMETHING
}
}
});
} else {
//If the value has already been loaded in the list,
//directly use it without hitting the API
final MyBook books = myBooks.get(position);
//DO SOMETHING
}
}
}
This will solve your issue, it will store the already accessed books from the API and will not hit the API again and will use the List to access them.
Apart from this, I'll suggest to go with ViewBinding which will replace your whole DiscoverBooksViewHolder into a single line(in Kotlin) or 2-3 lines(in Java), with viewBinding, you'll not have to declare and initialize views and the views can be accessed directly using Binding, you can then extract your onBind() out of the ViewHolder.
Related
I have an Android Fragment (in Java) that displays recyclerview. Now I would like to read the data for the items in the recyclerview from a firebase database. They should be stored into an array list orderList. For this purpose, I would like to use LiveData and a ViewModel because I read several times that this is the recommended way of implementing it. Further, I would like the Fragment to update the recyclerview automatically whenever new data is stored in the firebase database.
I tried to follow the steps that are described in the offical Firebase Blog (https://firebase.googleblog.com/2017/12/using-android-architecture-components.html) but unfortunately the result is always an empty list. No elements are being displayed altough there are some relevant entries in the database that should be returned and displayed. The implementation of the recyclerview itself is correct (I checked that by manually adding items into the recylcerview).
Here is my Fragment that holds the recyclerview:
public class FR_Orders extends Fragment {
private FragmentOrdersBinding binding;
//Define variables for the RecyclerView
private RecyclerView recyclerView_Order;
private RV_Adapter_Orders adapter_Order;
private RecyclerView.LayoutManager layoutManager_Order;
private ArrayList<RV_Item_Order> orderList;
public FR_Orders() {
// Required empty public constructor
}
public static FR_Orders newInstance(String param1, String param2) {
FR_Orders fragment = new FR_Orders();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
orderList = new ArrayList<RV_Item_Order>();
// Obtain a new or prior instance of ViewModel_FR_Orders from the ViewModelProviders utility class.
ViewModel_FR_Orders viewModel = new ViewModelProvider(this).get(ViewModel_FR_Orders.class);
LiveData<DataSnapshot> liveData = viewModel.getDataSnapshotLiveData();
liveData.observe(this, new Observer<DataSnapshot>() {
#Override
public void onChanged(#Nullable DataSnapshot dataSnapshot) {
for(DataSnapshot ds: dataSnapshot.getChildren()) {
if (dataSnapshot != null) {
String drinkName = "";
String drinkSize = "";
String orderDate = "";
String orderStatus = "";
int orderDateInMilliseconds = 0;
int orderID = 0;
int quantity = 0;
int tableNumber = 0;
// update the UI here with values in the snapshot
if( dataSnapshot.child("drinkName").getValue(String.class)!=null) {
drinkName = dataSnapshot.child("drinkName").getValue(String.class);
}
if(dataSnapshot.child("drinkSize").getValue(String.class)!=null) {
drinkSize = dataSnapshot.child("drinkSize").getValue(String.class);
}
if(dataSnapshot.child("orderDate").getValue(String.class)!=null) {
orderDate = dataSnapshot.child("orderDate").getValue(String.class);
}
if(dataSnapshot.child("orderStatus").getValue(String.class)!=null) {
orderStatus = dataSnapshot.child("orderStatus").getValue(String.class);
}
if(dataSnapshot.child("orderDateInMilliseconds").getValue(Integer.class)!=null) {
orderDateInMilliseconds = dataSnapshot.child("orderDateInMilliseconds").getValue(Integer.class);
}
if(dataSnapshot.child("quantity").getValue(Integer.class)!=null) {
quantity = dataSnapshot.child("quantity").getValue(Integer.class);
}
if(dataSnapshot.child("orderID").getValue(Integer.class)!=null) {
orderID = dataSnapshot.child("orderID").getValue(Integer.class);
}
if(dataSnapshot.child("tableNumber").getValue(Integer.class)!=null) {
tableNumber = dataSnapshot.child("tableNumber").getValue(Integer.class);
}
orderList.add(new RV_Item_Order(drinkName, drinkSize, orderDateInMilliseconds, orderDate, tableNumber, orderStatus, quantity, orderID));
;
}
}
}
});
}// end onCreate
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentOrdersBinding.inflate(inflater, container, false);
buildRecyclerView();
return binding.getRoot();
}
public void buildRecyclerView() {
recyclerView_Order = binding.rvDrinksToBeDisplayed;
recyclerView_Order.setHasFixedSize(true);
layoutManager_Order = new LinearLayoutManager(this.getContext());
adapter_Order = new RV_Adapter_Orders(orderList);
recyclerView_Order.setLayoutManager(layoutManager_Order);
recyclerView_Order.setAdapter(adapter_Order);
adapter_Order.setOnItemClickListener(new RV_Adapter_Orders.OnItemClickListener() {
/*
Define what happens when clicking on an item in the RecyclerView
*/
#Override
public void onItemClick(int position) {
}
});
}//end build recyclerView
}//End class
Here is the LiveData class
public class LiveData_FirebaseOrder extends LiveData<DataSnapshot> {
private static final String LOG_TAG = "LiveData_FirebaseOrder";
private final Query query;
private final MyValueEventListener listener = new MyValueEventListener();
public LiveData_FirebaseOrder(Query query) {
this.query = query;
}
public LiveData_FirebaseOrder(DatabaseReference ref) {
this.query = ref;
}
#Override
protected void onActive() {
Log.d(LOG_TAG, "onActive");
query.addValueEventListener(listener);
}
#Override
protected void onInactive() {
Log.d(LOG_TAG, "onInactive");
query.removeEventListener(listener);
}
private class MyValueEventListener implements ValueEventListener {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
setValue(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(LOG_TAG, "Can't listen to query " + query, databaseError.toException());
}
}
}
Here is the ViewModel class (the full name of the database is not given because of privacy issues).
public class ViewModel_FR_Orders extends ViewModel {
private static final DatabaseReference ORDERS_REF =
FirebaseDatabase.getInstance("https://....firebasedatabase.app").getReference("/Orders");
private final LiveData_FirebaseOrder liveData = new LiveData_FirebaseOrder(ORDERS_REF);
#NonNull
public LiveData<DataSnapshot> getDataSnapshotLiveData() {
return liveData;
}
}
And here is a screenshot of the Firebase Database that shows that there are some entries in the node Orders that should be returned
Does anyone have an idea what I am making wrong? I tried to stick exactly to the instructions of the offical Firebase Blog.
Update: I found out that the query itself returns the correct datasnapshots but the recyclerview is not built and updated.
I am in the process of changing someone else's code from listView to recyclerView, changing code in my activity and my adapter where required - the adapter is a big change, with OnCreateView, onBindViewHolder and so on...
As the question indicates, category, name, phone, address, comment, public_or_private are posting successfully to mySql db, but nothing for checkedContacts. I'd be very grateful if you could tell me how to fix the problem.
I am quite sure it has to do with something around this line:
//get the other data related to the selected contact - name and number
SelectPhoneContact contact = (SelectPhoneContact) checkbox.getTag();
I am not getting any errors but when I run in debug mode it gets to here and then jumps to:
} catch (Exception e) {
System.out.println("there's a problem here unfortunately");
e.printStackTrace();
}
I think it has something to do with setTag but I find it confusing and I'm not sure where it should be set (if, even, that is the problem).
I'm posting the relevant code for my activity, NewContact and the RecyclerViewAdapter, PopulistoCOntactsAdapter. Thanks for any help.
NewContact.java
//for the SAVE button
private void saveContactButton() {
save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
System.out.println("you clicked it, save");
try {
System.out.println("we're in the try part");
int count = MatchingContactsAsArrayList.size();
for (int i = 0; i < count; i++) {
//for each Matching Contacts row in the listview
LinearLayout itemLayout = (LinearLayout)recyclerView.getChildAt(i); // Find by under LinearLayout
//for each Matching Contacts checkbox in the listview
CheckBox checkbox = (CheckBox)itemLayout.findViewById(R.id.checkBoxContact);
//get the other data related to the selected contact - name and number
SelectPhoneContact contact = (SelectPhoneContact) checkbox.getTag();
//if that checkbox is checked, then get the phone number
if(checkbox.isChecked()) {
Log.d("Item " + String.valueOf(i), checkbox.getTag().toString());
Toast.makeText(NewContact.this, contact.getPhone(), Toast.LENGTH_LONG).show();
// make each checked contact in selectPhoneContacts
// into an individual
// JSON object called checkedContact
JSONObject checkedContact = new JSONObject();
// checkedContact will be of the form {"checkedContact":"+353123456"}
checkedContact.put("checkedContact", contact.getPhone());
// Add checkedContact JSON Object to checkedContacts jsonArray
//The JSON Array will be of the form
// [{"checkedContact":"+3531234567"},{"checkedContact":"+353868132813"}]
//we will be posting this JSON Array to Php, further down below
checkedContacts.put(checkedContact);
System.out.println("NewContact: checkedcontact JSONObject :" + checkedContact);
}
}
} catch (Exception e) {
System.out.println("there's a problem here unfortunately");
e.printStackTrace();
}
//When the user clicks save
//post phoneNoofUserCheck to NewContact.php and from that
//get the user_id in the user table, then post category, name, phone etc...
//to the review table
StringRequest stringRequest = new StringRequest(Request.Method.POST, NewContact_URL,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
//response, this will show the checked numbers being posted
Toast.makeText(NewContact.this, response, Toast.LENGTH_LONG).show();
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
}) {
//post these details to the NewContact.php file and do
//stuff with it
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<String, String>();
//post the phone number to php to get the user_id in the user table
params.put("phonenumberofuser", phoneNoofUserCheck);
//the second value, categoryname.getText().toString() etc...
// is the value we get from Android.
//the key is "category", "name" etc.
// When we see these in our php, $_POST["category"],
//put in the value from Android
params.put("category", categoryname.getText().toString());
params.put("name", namename.getText().toString());
params.put("phone", phonename.getText().toString());
params.put("address", addressname.getText().toString());
params.put("comment", commentname.getText().toString());
params.put("public_or_private", String.valueOf(public_or_private));
System.out.println("public_or_private is " + String.valueOf(public_or_private));
//this is the JSON Array of checked contacts
//it will be of the form
//[{"checkedContact":"+3531234567"},{"checkedContact":"+353868132813"}]
params.put("checkedContacts", checkedContacts.toString());
return params;
}
};
// Adding request to request queue
AppController.getInstance().addToRequestQueue(stringRequest);
//when saved, go back to the PopulistoListView class and update with
//the new entry
Intent j = new Intent(NewContact.this, PopulistoListView.class);
j.putExtra("phonenumberofuser", phoneNoofUserCheck);
NewContact.this.startActivity(j);
finish();
}
});
}
}
PopulistoContactsAdapter.java
public class PopulistoContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//make a List containing info about SelectPhoneContact objects
public List<SelectPhoneContact> theContactsList;
Context context_type;
public class MatchingContact extends RecyclerView.ViewHolder {
//In each recycler_blueprint show the items you want to have appearing
public TextView title, phone;
public CheckBox check;
public Button invite;
public MatchingContact(final View itemView) {
super(itemView);
//title is cast to the name id, in recycler_blueprint,
//phone is cast to the id called no etc
title = (TextView) itemView.findViewById(R.id.name);
phone = (TextView) itemView.findViewById(R.id.no);
invite = (Button) itemView.findViewById(R.id.btnInvite);
check = (CheckBox) itemView.findViewById(R.id.checkBoxContact);
}
}
public class nonMatchingContact extends RecyclerView.ViewHolder {
//In each recycler_blueprint show the items you want to have appearing
public TextView title, phone;
public CheckBox check;
public Button invite;
public nonMatchingContact(final View itemView) {
super(itemView);
//title is cast to the name id, in recycler_blueprint,
//phone is cast to the id called no etc
title = (TextView) itemView.findViewById(R.id.name);
phone = (TextView) itemView.findViewById(R.id.no);
invite = (Button) itemView.findViewById(R.id.btnInvite);
check = (CheckBox) itemView.findViewById(R.id.checkBoxContact);
}
}
#Override
public int getItemViewType(int position) {
//for each row in recyclerview, get the getType_row, set in NewContact.java
return Integer.parseInt(theContactsList.get(position).getType_row());
}
public PopulistoContactsAdapter(List<SelectPhoneContact> selectPhoneContacts, Context context) {
//selectPhoneContacts = new ArrayList<SelectPhoneContact>();
theContactsList = selectPhoneContacts;
// whichactivity = activity;
context_type = context;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView;
//if getType_row is 1...
if (viewType == 1)
{
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
itemView = inflater.inflate(R.layout.recycler_blueprint, parent, false);
//itemView.setTag();
return new MatchingContact(itemView);
} else {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
itemView = inflater.inflate(R.layout.recycler_blueprint_non_matching, parent, false);
return new nonMatchingContact(itemView);
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
//bind the views into the ViewHolder
//selectPhoneContact is an instance of the SelectPhoneContact class.
//We will assign each row of the recyclerview to contain details of selectPhoneContact:
//The number of rows will match the number of phone contacts
final SelectPhoneContact selectPhoneContact = theContactsList.get(position);
if (viewHolder.getItemViewType() == 1)
{
((MatchingContact) viewHolder).title.setText(selectPhoneContact.getName());
((MatchingContact) viewHolder).phone.setText(selectPhoneContact.getPhone());
CheckBox check = ((MatchingContact) viewHolder).check;
//get the number position of the checkbox in the recyclerview
//check.setTag(position);
}
else {
((nonMatchingContact) viewHolder).title.setText(selectPhoneContact.getName());
((nonMatchingContact) viewHolder).phone.setText(selectPhoneContact.getPhone());
}
}
#Override
public int getItemCount() {
return theContactsList.size();
}
}
And here is my SelectPhoneContact class:
public class SelectPhoneContact {
String phone;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
boolean isMatching;
public boolean isMatching(){return isMatching;}
public void setIsMatchingContact(boolean isMatching){
this.isMatching = isMatching;
}
//*****************************************
//this is for the checkbox
boolean selected = false;
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected){
this.selected = selected;
}
String type_row;
public String getType_row() {
return type_row;
}
public void setType_row(String type_row) {
this.type_row = type_row;
}
}
I figured it out. This tutorial was very helpful: https://demonuts.com/2017/07/03/recyclerview-checkbox-android/
Here is the code I used for NewContact.java:
//for the SAVE button
private void saveContactButton() {
save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
System.out.println("you clicked it, save");
try {
System.out.println("we're in the try part");
//loop through the matching contacts
int count = MatchingContactsAsArrayList.size();
for (int i = 0; i < count; i++) {
//for matching contacts that are checked...
if (PopulistoContactsAdapter.theContactsList.get(i).getSelected()) {
Toast.makeText(NewContact.this, PopulistoContactsAdapter.theContactsList.get(i).getPhone() + " clicked!", Toast.LENGTH_SHORT).show();
// make each checked contact in selectPhoneContacts
// into an individual
// JSON object called checkedContact
JSONObject checkedContact = new JSONObject();
// checkedContact will be of the form {"checkedContact":"+353123456"}
checkedContact.put("checkedContact", PopulistoContactsAdapter.theContactsList.get(i).getPhone());
// Add checkedContact JSON Object to checkedContacts jsonArray
//The JSON Array will be of the form
// [{"checkedContact":"+3531234567"},{"checkedContact":"+353868132813"}]
//we will be posting this JSON Array to Php, further down below
checkedContacts.put(checkedContact);
System.out.println("NewContact: checkedcontact JSONObject :" + checkedContact);
}
}
} catch (Exception e) {
System.out.println("there's a problem here unfortunately");
e.printStackTrace();
}
//When the user clicks save
//post phoneNoofUserCheck to NewContact.php and from that
//get the user_id in the user table, then post category, name, phone etc...
//to the review table
StringRequest stringRequest = new StringRequest(Request.Method.POST, NewContact_URL,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
//response, this will show the checked numbers being posted
Toast.makeText(NewContact.this, response, Toast.LENGTH_LONG).show();
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
}) {
//post these details to the NewContact.php file and do
//stuff with it
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<String, String>();
//post the phone number to php to get the user_id in the user table
params.put("phonenumberofuser", phoneNoofUserCheck);
//the second value, categoryname.getText().toString() etc...
// is the value we get from Android.
//the key is "category", "name" etc.
// When we see these in our php, $_POST["category"],
//put in the value from Android
params.put("category", categoryname.getText().toString());
params.put("name", namename.getText().toString());
params.put("phone", phonename.getText().toString());
params.put("address", addressname.getText().toString());
params.put("comment", commentname.getText().toString());
params.put("public_or_private", String.valueOf(public_or_private));
System.out.println("public_or_private is " + String.valueOf(public_or_private));
//this is the JSON Array of checked contacts
//it will be of the form
//[{"checkedContact":"+3531234567"},{"checkedContact":"+353868132813"}]
params.put("checkedContacts", checkedContacts.toString());
return params;
}
};
// Adding request to request queue
AppController.getInstance().addToRequestQueue(stringRequest);
//when saved, go back to the PopulistoListView class and update with
//the new entry
Intent j = new Intent(NewContact.this, PopulistoListView.class);
j.putExtra("phonenumberofuser", phoneNoofUserCheck);
NewContact.this.startActivity(j);
finish();
}
});
}
}
And my PopulistoContactsAdapter.java:
public class PopulistoContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//make a List containing info about SelectPhoneContact objects
public static List<SelectPhoneContact> theContactsList;
Context context_type;
public class MatchingContact extends RecyclerView.ViewHolder {
//In each recycler_blueprint show the items you want to have appearing
public TextView title, phone;
public CheckBox check;
public Button invite;
public MatchingContact(final View itemView) {
super(itemView);
//title is cast to the name id, in recycler_blueprint,
//phone is cast to the id called no etc
title = (TextView) itemView.findViewById(R.id.name);
phone = (TextView) itemView.findViewById(R.id.no);
invite = (Button) itemView.findViewById(R.id.btnInvite);
check = (CheckBox) itemView.findViewById(R.id.checkBoxContact);
}
}
public class nonMatchingContact extends RecyclerView.ViewHolder {
//In each recycler_blueprint show the items you want to have appearing
public TextView title, phone;
public CheckBox check;
public Button invite;
public nonMatchingContact(final View itemView) {
super(itemView);
//title is cast to the name id, in recycler_blueprint,
//phone is cast to the id called no etc
title = (TextView) itemView.findViewById(R.id.name);
phone = (TextView) itemView.findViewById(R.id.no);
invite = (Button) itemView.findViewById(R.id.btnInvite);
check = (CheckBox) itemView.findViewById(R.id.checkBoxContact);
}
}
#Override
public int getItemViewType(int position) {
//for each row in recyclerview, get the getType_row, set in NewContact.java
return Integer.parseInt(theContactsList.get(position).getType_row());
}
public PopulistoContactsAdapter(List<SelectPhoneContact> selectPhoneContacts, Context context) {
//selectPhoneContacts = new ArrayList<SelectPhoneContact>();
theContactsList = selectPhoneContacts;
// whichactivity = activity;
context_type = context;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView;
//if getType_row is 1...
if (viewType == 1)
{
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
itemView = inflater.inflate(R.layout.recycler_blueprint, parent, false);
//itemView.setTag();
return new MatchingContact(itemView);
} else {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
itemView = inflater.inflate(R.layout.recycler_blueprint_non_matching, parent, false);
return new nonMatchingContact(itemView);
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
//bind the views into the ViewHolder
//selectPhoneContact is an instance of the SelectPhoneContact class.
//We will assign each row of the recyclerview to contain details of selectPhoneContact:
//The number of rows will match the number of phone contacts
final SelectPhoneContact selectPhoneContact = theContactsList.get(position);
if (viewHolder.getItemViewType() == 1)
{
((MatchingContact) viewHolder).title.setText(selectPhoneContact.getName());
((MatchingContact) viewHolder).phone.setText(selectPhoneContact.getPhone());
((MatchingContact) viewHolder).check.setText("Cheeckbox" + position);
((MatchingContact) viewHolder).check.setChecked(theContactsList.get(position).getSelected());
((MatchingContact) viewHolder).check.setTag(position);
((MatchingContact) viewHolder).check.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Integer pos = (Integer) ((MatchingContact) viewHolder).check.getTag();
Toast.makeText(context_type, theContactsList.get(pos).getPhone() + " clicked!", Toast.LENGTH_SHORT).show();
if (theContactsList.get(pos).getSelected()) {
theContactsList.get(pos).setSelected(false);
} else {
theContactsList.get(pos).setSelected(true);
}
}
});
// CheckBox check = ((MatchingContact) viewHolder).check;
//get the number position of the checkbox in the recyclerview
//check.setTag(position);
}
else {
((nonMatchingContact) viewHolder).title.setText(selectPhoneContact.getName());
((nonMatchingContact) viewHolder).phone.setText(selectPhoneContact.getPhone());
}
}
#Override
public int getItemCount() {
return theContactsList.size();
}
}
Newbie here - As practice, I am using google places to search for nearby restaurants and get their details . I am also using google place details to get more info about the specific restaurant. I am using Retrofit to parse the data.
The problem:
I have three ArrayList, restaurantName, restaurantAddress, and restaurantPhoneNum. I cannot pass all three ArrayLists into my recyclerview adapter.
Get Nearby restaurants method:
private void getNearbySearchUrl(final double latitude, final double longitude, final String nearbyPlace){
MapInterface retrofit = new Retrofit.Builder()
.baseUrl("https://maps.googleapis.com/maps/api/place/nearbysearch/")
.addConverterFactory(GsonConverterFactory.create())
.build().create(MapInterface.class);
Call<Result> call = retrofit.getResults("65.9667,-18.5333", PROXIMITY_RADIUS, "restaurant", placesKey);
call.enqueue(new Callback<Result>() {
#Override
public void onResponse(Call<Result> call, Response<Result> response) {
ArrayList<String> restaurantName = new ArrayList<>();
ArrayList<String> restaurantAddress = new ArrayList<>();
GetNearbyPlaces b = new GetNearbyPlaces();
for(int i = 0; i <= response.body().getResults().size() - 1 ; i++) {
restaurantName.add(response.body().getResults().get(i).getName());
restaurantAddress.add(response.body().getResults().get(i).getVicinity());
//get telephone number through this method
getPlaceDetailsUrl(response.body().getResults().get(i).getPlaceId());
}
Collections.reverse(restaurantName);
Collections.reverse(restaurantAddress);
adapter = new RestaurantListAdapter(restaurantName, restaurantAddress,
response.body().getResults().size());
mRecyclerView.setAdapter(adapter);
}
#Override
public void onFailure(Call<Result> call, Throwable t) {
Log.d("Matt", "fail");
}
});
}
Get more info about the restaurant method:
public String getPlaceDetailsUrl(final String placeId) {
final String mPlace = placeId;
MapInterface retrofit = new Retrofit.Builder()
.baseUrl("https://maps.googleapis.com/maps/api/place/details/")
.addConverterFactory(GsonConverterFactory.create())
.build().create(MapInterface.class);
Call<Result> call = retrofit.getPlaceDetails(placeId, placesKey);
call.enqueue(new Callback<Result>() {
#Override
public void onResponse(Call<Result> response) {
ArrayList<String> restaurantPhoneNum = new ArrayList<>();
if (response.body().getResult().getFormattedPhoneNumber() == null ){
return;
}
restaurantPhoneNum.add(response.body().getResult().getFormattedPhoneNumber());
}
#Override
public void onFailure(Call<Result> call, Throwable t) {
return;
}
});
}
Adapter:
public class RestaurantListAdapter extends RecyclerView.Adapter<RestaurantListAdapter.RestaurantListHolder> {
ArrayList<String> restaurantName = new ArrayList<>();
ArrayList<String> restaurantAddress = new ArrayList<>();
ArrayList<String> restaurantPhoneNum = new ArrayList<>();
private int restaurantCount;
public RestaurantListAdapter(ArrayList<String> resName, ArrayList<String> resAddress,
int resCount){
restaurantName = resName;
restaurantAddress = resAddress;
restaurantCount = resCount;
}
public RestaurantListAdapter(ArrayList<String> resPhoneNum){
restaurantPhoneNum = resPhoneNum;
}
#Override
public RestaurantListHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.restaurant_list_item, parent, false);
RestaurantListHolder viewHolder = new RestaurantListHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(RestaurantListHolder holder, int position) {
Log.d("Matt1", String.valueOf(position) + " size:" + String.valueOf(restaurantName.size()) + restaurantName.toString());
holder.bindView(position);
}
#Override
public int getItemCount() {
return restaurantCount;
}
public class RestaurantListHolder extends RecyclerView.ViewHolder{
public TextView mRestaurantName;
public TextView mAddress;
public TextView mPhone;
public TextView mDistance;
public ImageView mRestaurantPic;
public RestaurantListHolder(View itemView) {
super(itemView);
mRestaurantName = (TextView) itemView.findViewById(R.id.tvRestaurantName);
mAddress = (TextView) itemView.findViewById(R.id.tvAddress);
mPhone = (TextView) itemView.findViewById(R.id.tvPhone);
}
public void bindView(int arrayNum){
mRestaurantName.setText(restaurantName.get(arrayNum));
mAddress.setText(restaurantAddress.get(arrayNum));
mPhone.setText("123 123 123 123 ");
mRestaurantPic.setImageResource(R.drawable.rzkibble_chinese);
}
}
}
Ok, first advice, try to think about it a little bit on your own. I know that you know how to solve this :).
Your question is left unclear:
you want to update adapter after both requests are executed
you will update adapter after each request
I suppose that you want to implement this in a first way.
You need to have the logic which executes requestOne, executes requestTwo and when both requests are successful, update adapter. You can do this with some global flags arePlacesLoaded and areDetailsLoaded and to have third method which receives request result and inspects these flags.
A manual way (pseudo)
boolean arePlacesLoaded;
boolean areDetailsLoaded;
void loadPlaces(){
if(success){
arePlacesLoaded = true;
savePlaces(places)
updateAdapter()
}
}
void loadDetails(){
if(success){
areDetailsLoaded = true;
saveDetails(details)
updateAdapter()
}
}
void updateAdaper(){
if(arePlacesLoaded && areDetailsLoaded){
adaper.onDataChange(places, details);
}
}
You can use some queues where you put requests and once the requests are executed and queue is empty -> update adapter.
Another, better way would be with usage of RxJava and its zipWith operator, where implementation takes few lines only (having in mind that retrofit is moved somewhere to core of the app)
Here is a marble diagram of this operator:
So, request one executes, and then request two. You will provide a function which is going to merge data into some better object called Hotel, and you will feed the adapter with the array of Hotel objects.
I'm trying to see what is making my listview jerk sometimes when scroll, at times it's bad especially when the application first launches.
All the conditions I have are necessary, unless there is something I don't know(highly likely).
I'm not running certain tasks on a seperate thread because they are dependent on the data I receive from the backend(I'm coding both, so backend suggestions are welcome as well). Product is in beta but really need to make this a slightly bit smoother. I'm compressing the images, and they are a bit long but it's not the problem because when I upload the images from the device, I also include the width and height of the image and send that along to the backend. These dimensions come back when loading the list.
One thing I wonder is if calculating/converting the dimensions for the specific device's screen is causing the slight lag. Not sure how resource intensive that task is, but without it(without knowing the dimensions, each row would start out flat and then expand to the actual picture size which would cause the list to jump, so I can't run that calculation on the background either.)
Basically the scrolling isn't bad, but I need to improve this somehow.
Here is my Adapter:
public class VListAdapter extends BaseAdapter {
ViewHolder viewHolder;
private boolean isItFromProfile;
/**
* fields For number formating, ex. 1000
* would return 1k in the format method
*/
private static final NavigableMap<Long, String> suffixes = new TreeMap<>();
static {
suffixes.put(1_000L, "k");
suffixes.put(1_000_000L, "M");
suffixes.put(1_000_000_000L, "G");
suffixes.put(1_000_000_000_000L, "T");
suffixes.put(1_000_000_000_000_000L, "P");
suffixes.put(1_000_000_000_000_000_000L, "E");
}
private Context mContext;
private LayoutInflater mInflater;
private ArrayList<Post> mDataSource;
private static double lat;
private static double lon;
public VListAdapter(Context context, ArrayList<Post> items) {
mContext = context;
mDataSource = items;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = false;
mInflater = LayoutInflater.from(context);
}
public VListAdapter() {
}
public VListAdapter(Context baseContext, ArrayList<Post> posts, boolean b) {
mContext = baseContext;
mDataSource = posts;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = b;
mInflater = LayoutInflater.from(baseContext);
}
public void addElement(Post post) {
mDataSource.add(0, post);
this.notifyDataSetChanged();
}
#Override
public int getCount() {
return mDataSource.size();
}
#Override
public Object getItem(int position) {
return mDataSource.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
int limit = Math.min(position + 4, getCount());
for (int i = position; i < limit; i++) {
Glide.with(mContext).load(((Post) getItem(i)).getFilename().toString()).preload();
}
// StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads()
// .detectDiskWrites().detectNetwork()
// .penaltyLog().build());
View rowView = convertView;
if (rowView == null) {
viewHolder = new ViewHolder();
rowView = mInflater.inflate(R.layout.mylist, parent, false);
viewHolder.titleTextView = (TextView) rowView.findViewById(R.id.usernameinlist);
viewHolder.timeago = (TextView) rowView.findViewById(R.id.timeago);
//viewHolder.sharebutton = (ImageView) rowView.findViewById(R.id.sharebutton);
viewHolder.likesTextView = (TextView) rowView.findViewById(R.id.likestext);
viewHolder.viewcount = (TextView) rowView.findViewById(R.id.viewcount);
viewHolder.distance = (TextView) rowView.findViewById(R.id.distance);
viewHolder.footprints = (TextView) rowView.findViewById(R.id.footprintcount);
viewHolder.postText = (TextView) rowView.findViewById(R.id.posttext);
viewHolder.profilePic = (ImageView) rowView.findViewById(R.id.profilethumb);
viewHolder.caption = (TextView) rowView.findViewById(R.id.captiontext);
viewHolder.moremenu = (ImageView) rowView.findViewById(R.id.dots);
viewHolder.likesPic = (ImageView) rowView.findViewById(R.id.likeimage);
viewHolder.mapitPic = (ImageView) rowView.findViewById(R.id.mapimage);
viewHolder.playbutton = (ImageView) rowView.findViewById(R.id.playbutton);
viewHolder.videoThumb = (ImageView) rowView.findViewById(R.id.videothumb);
viewHolder.listphoto = (ImageView) rowView.findViewById(R.id.listphoto);
viewHolder.rainbow = (ImageView) rowView.findViewById(R.id.rainbow);
rowView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) rowView.getTag();
}
final Post post = (Post) getItem(position);
int color = Color.parseColor("#dddddd");
viewHolder.likesPic.setColorFilter(color);
viewHolder.mapitPic.setColorFilter(color);
viewHolder.moremenu.setColorFilter(color);
if (Hawk.count() == 0)
initHawkWithDataFromServer();
if (isItFromProfile) {
viewHolder.profilePic.setVisibility(View.GONE);
viewHolder.titleTextView.setVisibility(View.GONE);
viewHolder.distance.setVisibility(View.GONE);
}
viewHolder.titleTextView.setText(post.getUsername());
PrettyTime prettyTime = new PrettyTime();
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size() - 1)));
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
if (post.getViews() != null)
viewHolder.viewcount.setText(format(post.getViews()) + (post.getViews() == 1 ? " View" : " Views"));
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername()
+ ".jpg";
String filename = post.getS3link();
final String videoThumbURL = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + filename;
Glide.with(mContext).load(profilePictureS3Url).asBitmap().centerCrop().into(new BitmapImageViewTarget(viewHolder.profilePic) {
#Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(mContext.getResources(), resource);
circularBitmapDrawable.setCircular(true);
viewHolder.profilePic.setImageDrawable(circularBitmapDrawable);
}
});
int height = ((Post) getItem(position)).getHeight();
int width = ((Post) getItem(position)).getWidth();
if (height != 0 && width != 0) {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
} else {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
}
if (post.getType() == null) {
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
//Glide.clear(viewHolder.listphoto);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
viewHolder.playbutton.setVisibility(View.VISIBLE);
}
if (post.getType() != null) {
if (post.getType().equals("video")) {
viewHolder.playbutton.setVisibility(View.VISIBLE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
}
if (post.getType().equals("image")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.listphoto.setVisibility(View.VISIBLE);
viewHolder.listphoto.setBottom(0);
Glide.with(mContext).load(post.getFilename().toString())
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate()
.into(viewHolder.listphoto);
}
if (post.getType().equals("text")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
viewHolder.postText.setVisibility(View.VISIBLE);
viewHolder.postText.setText(post.getText());
}
}
if (Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.clearColorFilter();
Glide.with(mContext).load(R.drawable.heartroundorange).into(viewHolder.likesPic);
((ImageView) viewHolder.likesPic).setColorFilter(Color.parseColor("#ff3a6f"));
} else {
Glide.with(mContext).load(R.drawable.heartroundgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.likesPic);
}
if (Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.clearColorFilter();
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
((ImageView) viewHolder.mapitPic).setColorFilter(Color.parseColor("#444444"));
} else {
Glide.with(mContext).load(R.drawable.dropdarkgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.mapitPic);
}
if (!Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Hawk.put("mapped" + post.getId().get$oid(), 1);
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
viewHolder.footprints.setText(String.valueOf(post.getLocation().size() + 1));
post.getLocation().add(new double[]{PostListFragment.lon, PostListFragment.lat});
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postMappedToServer(post.getId().get$oid());
}
});
t.start();
TastyToast.makeText(mContext, "Post dropped off here.", TastyToast.LENGTH_SHORT, TastyToast.CONFUSING);
}
});
} else {
viewHolder.mapitPic.setClickable(false);
}
if (!Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Hawk.put("liked" + post.getId().get$oid(), 1);
viewHolder.likesPic.setClickable(false);
((ImageView) viewHolder.likesPic).setImageResource(R.drawable.heartroundorange);
viewHolder.likesTextView.setText(String.valueOf(post.getLikes() + 1));
post.setLikes(post.getLikes() + 1);
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postLikeToServer(post);
}
});
t.start();
}
});
} else {
viewHolder.likesPic.setClickable(false);
}
if (post.getType() == null || post.getType().equals("video"))
viewHolder.videoThumb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (VListAdapter.this.mContext instanceof ProfileFeed) {
((ProfileFeed) VListAdapter.this.mContext).closeActivity();
}
Intent broadcast = new Intent();
broadcast.setAction("com.molehead.openout.POST");
broadcast.putExtra("postId", post.getFilename().toString());
broadcast.putExtra("hawkId", post.getId().get$oid());
broadcast.putExtra("s3link", post.getS3link());
broadcast.putExtra("username", post.getUsername());
if (Hawk.contains("liked" + post.getId().get$oid()))
broadcast.putExtra("liked", "yes");
else
broadcast.putExtra("liked", "no");
broadcast.putExtra("likecount", post.getLikes().toString());
App.post = post;
LocalBroadcastManager.getInstance(mContext.getApplicationContext()).sendBroadcast(broadcast);
}
});
viewHolder.moremenu.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
PopupMenu popup = new PopupMenu(mContext.getApplicationContext(), viewHolder.moremenu, Gravity.CENTER);
//Inflating the Popup using xml file
popup.getMenuInflater().inflate(R.menu.menu_main, popup.getMenu());
//registering popup with OnMenuItemClickListener
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_share:
String postId = post.getId().get$oid();
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
String shareBody = postId + ".jpg"; //https://openout.herokuapp.com/posts/" + postId;
String shareSub = "Shared via Molehead";
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, shareSub);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
sharingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent new_intent = Intent.createChooser(sharingIntent, "Share");
new_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.getApplicationContext().startActivity(new_intent);
break;
}
return true;
}
});
popup.show();
}
});
return rowView;
}
private void initHawkWithDataFromServer() {
SharedPreferences settings = mContext.getApplicationContext().getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
final Call<List<Post>> call = loginService.getLikes(username);
Log.i("lonlat", String.valueOf(lon) + " and " + String.valueOf(lat));
call.enqueue(new Callback<List<Post>>() {
#Override
public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {
ArrayList<Post> posts = new ArrayList<>();
posts = (ArrayList<Post>) response.body();
if (!posts.isEmpty())
for (Post p : posts) {
Hawk.put("liked" + p.getId().get$oid(), 1);
}
}
#Override
public void onFailure(Call<List<Post>> call, Throwable t) {
}
});
}
private void postMappedToServer(String oid) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Log.i("postlistfraglat", String.valueOf(PostListFragment.lat));
Call<ResponseBody> call = loginService.addLocation(oid, PostListFragment.lon, PostListFragment.lat);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful())
Log.i("mapped", "success");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
public void postLikeToServer(Post post) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Call<ResponseBody> call = loginService.like(post, 1, username);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
try {
Log.i("call", response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i("MFEED", "like request failed");
}
});
}
public static String format(long value) {
//Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
if (value < 0) return "-" + format(-value);
if (value < 1000) return Long.toString(value); //deal with easy case
Map.Entry<Long, String> e = suffixes.floorEntry(value);
Long divideBy = e.getKey();
String suffix = e.getValue();
long truncated = value / (divideBy / 10); //the number part of the output times 10
boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
}
static class ViewHolder {
private TextView titleTextView;
private TextView timeago;
private TextView likesTextView;
private TextView viewcount;
private TextView distance;
private TextView footprints;
private ImageView profilePic;
private ImageView moremenu;
private ImageView likesPic;
private ImageView mapitPic;
private ImageView rainbow;
//private ImageView sharebutton;
private TextView caption;
private ImageView listphoto;
private ImageView videoThumb;
private ImageView playbutton;
private TextView postText;
private Post post;
}
private float getHeight(float height, float width) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return (height * size.x / width);
}
}
It is impossible to point to a specific issue because there is so much code in your Adapter. One thing is sure, though - switching to RecyclerView won't help you in this case.
Adapters should not contain business logic - they should only "adapt" input objects to the underlying Views. In your case, it seems like the adapter performs calculations, spawns new threads, performs network requests, etc.
You need to refactor your code such that the adapter will be similar to this:
public class PostsListAdapter extends ArrayAdapter<Post> {
private Context mContext;
public PostsListAdapter(Context context, int resource) {
super(context, resource);
mContext = context;
}
public void bindPosts(List<Post> posts) {
clear();
addAll(posts);
notifyDataSetChanged();
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// assign new View to convertView
// create new ViewHolder
// set ViewHolder as tag of convertView
// set listeners
} else {
// get a reference to existing ViewHolder
}
// populate ViewHolder's elements with data from getItem(position)
// kick off asynchronous loading of images
// NOTE: no calculations allowed here - just simple bidding of data to Views
return convertView;
}
}
Your code needs to be structured in such a way, that business logic that involves calculations and transformation of data executed before you bind a new data to ListView, and Post objects that you pass to bindPosts() method already contain the results of the aforementioned calculations and transformations.
Adapter just "adapts" the final data from Posts to Views - nothing more.
If you're short on time now, and just need to "make it work", then I would start by removing the logic that spawns new threads and makes network requests. See if this improves performance.
Change your implementation to RecyclerView which is more efficient in terms of scrapping views or recycling.
We can also enable optimizations if the items are static and will not change for significantly smoother scrolling:
recyclerView.setHasFixedSize(true);
Create an intent service and register BroadcastReceiver as data return callback or error callback when api request, business rule, data modification completed. Use synchronous call to execute initHawkWithDataFromServer() in advance and after getting result from api continue modifying or applying business logic. After that create new adapter or update existing adapter data set.
Move all the below data calculation or data value formatting logic from adapter's getView() to above intent service.
You can add more getter and setter to existing Post pojo.
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size)) - 1)));
Post{
//Your existing property
#Expose(serialize = false, deserialize = false)
//equals neither serialize nor deserialize or
private DateTime uploadedDateTime;
//etc. prettyTime.format, String.valueOf
}
Removes unnecessary reflection:
GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
Gson gson = builder.create();
new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)).build();
and it to your retrofit Service creation class.
You can also use transient(private transient DateTime uploadedDateTime;)
Remove
public void addElement(Post post) { mDataSource.add(0, post);
this.notifyDataSetChanged();}
and whenever you need to notify if a single or more items inserted, deleted etc. Use the below:
notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)
We can use these from the activity or fragment:
//Add a new contact
items.add(0, new Post("Barney"));
//Notify the adapter that an item was inserted at position 0
adapter.notifyItemInserted(0);
Above methods are more efficient. Every time we want to add or remove items from the RecyclerView, we will need to explicitly inform to the adapter of the event. Unlike the ListView adapter, a RecyclerView adapter should not rely on notifyDataSetChanged() since the more granular actions should be used. See the API documentation for more details
Also, if you are intending to update an existing list, make sure to get the current count of items before making any changes. For instance, a getItemCount() on the adapter should be called to record the first index that will be changed.
// record this value before making any changes to the existing list
int curSize = adapter.getItemCount(); // replace this line with wherever you get new records
ArrayList<Post> newItems = Post.createPostsList(20);
// update the existing list
items.addAll(newItems);
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
adapter.notifyItemRangeInserted(curSize, newItems.size());
Diffing Larger Changes
A new DiffUtil class has been added in the v24.2.0 of the support library to help compute the difference between the old and new list. Details
Don't preload images via glide if your image sizes are different. Try creating your own. Also try looking at.
Create color as the class member
int color = Color.parseColor("#dddddd");
Write View.GONE or View.VISIBLE in Post pojo itself, which will be executed in background thread from Retrofit if IntentService. Try api return boolean in json instead "0" as String.
Move all below to IntentService
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
All String concatenation also in Post or IntnetService like:
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername() + ".jpg";
Also you can create color filter in advance and one time only. Remove scrollbar from listview as it calculates height to show scroll bar.
Too many things to improve here. Here is some examples.
I see this
if (Hawk.count() == 0)
initHawkWithDataFromServer();
I believe that the method initHawkWithDataFromServer will be called many times during the time the list appears.
This call can be done only once when the activity was created.
Glide.with(mContext).load(videoThumbURL).fitCenter()
But you should refactor your code first, moving the logic to another class. Try to remove some code like this (it should be done by using some layout attribites)
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
I have two spinners the first one for month and the second for years.I am trying to call a method send_date() if on Item Selected is called for any of the 2 spinners.
So I have two problems:- 1)send_date() gets called twice the first
time it gets the correct data as expected but the 2nd time it returns
a empty array. 2)When I select another month or year the old data does
not get removed that is the list does not refresh.
The following is my code for on Item Selected :-
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
int i = spinYear.getSelectedItemPosition();
selected_year = years.get(i);
Log.d("Selection Year",selected_year);
tv_year.setText(selected_year);
try {
send_date();
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
And for the month spinner:-
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
int j = spinMonths.getSelectedItemPosition();
selected_month = Months[j];
Date date = null;
try {
date = new SimpleDateFormat("MMMM").parse(selected_month);
} catch (ParseException e) {
e.printStackTrace();
}
Calendar cal = Calendar.getInstance();
cal.setTime(date);
tv_month.setText(String.valueOf(cal.get(Calendar.MONTH)+1));
Log.d("Selection Month",selected_month);
try {
send_date();
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
On response I call the following method for populating the list view with data:-
public void showBS(String response)
{
ParseBS_all pb = new ParseBS_all(response);
pb.parseBS();
bl = new BS_allList(getActivity(),ParseBS_all.doc_no,ParseBS_all.balance,ParseBS_all.total,ParseBS_all.vat,ParseBS_all.profit);
lv_bsall.setAdapter(bl);
}
This is the code for the send_date method:-
//This method is used to send month and year
private void send_date() throws JSONException {
final String year = tv_year.getText().toString();
final String month = tv_month.getText().toString();
StringRequest stringRequest = new StringRequest(Request.Method.POST, SEND_DATE,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
//display.setText("This is the Response : " + response);
String resp = response.toString().trim();
if (resp.equals("Nothing to display"))
{
Toast.makeText(getContext(), "Nothing to Display", Toast.LENGTH_SHORT).show();
// bl.clear();
lv_bsall.setAdapter(bl);
bl.notifyDataSetChanged();
}else
{
Toast.makeText(getContext(), "Response" + response, Toast.LENGTH_LONG).show();
Log.d("RESPONSE for date", response.toString().trim());
showBS(response);
}
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
}) {
#Override
protected Map<String, String> getParams() throws AuthFailureError {
GlobalClass gvar = (GlobalClass) getActivity().getApplicationContext();
String dbname = gvar.getDbname();
Map<String, String> params = new HashMap<>();
params.put(KEY_DBNAME, dbname);
params.put(KEY_MONTH, month);
params.put(KEY_YEAR,year);
return params;
}
};
RequestQueue requestQ = Volley.newRequestQueue(getContext());
requestQ.add(stringRequest);
}
Adapter code for list view.
public class BS_allList extends ArrayAdapter<String>
{
private String[] doc_no;
private String[] balance;
private String[] total;
private String[] vat;
private String[] profit;
private Activity context;
public BS_allList(Activity context, String[] doc_no, String[]balance, String[] total, String[] vat, String[] profit)
{
super(context, R.layout.bs_list_all, doc_no);
this.context =context;
this.doc_no= doc_no;
this.balance = balance;
this.total = total;
this.vat=vat;
this.profit = profit;
}
#Override
public View getView(int position, View listViewItem, ViewGroup parent)
{
if (null == listViewItem)
{
LayoutInflater inflater = context.getLayoutInflater();
listViewItem = inflater.inflate(R.layout.bs_list_all, null, true);
}
TextView tv_docNo = (TextView) listViewItem.findViewById(R.id.tvdoc_no);
TextView tv_balance = (TextView) listViewItem.findViewById(R.id.tv_balance);
TextView tv_tot = (TextView) listViewItem.findViewById(R.id.tv_total);
TextView tv_vat = (TextView) listViewItem.findViewById(R.id.tv_vat);
TextView tv_pf = (TextView) listViewItem.findViewById(R.id.tv_profit);
tv_docNo.setText(doc_no[position]);
tv_balance.setText(balance[position]);
tv_tot.setText(total[position]);
tv_vat.setText(vat[position]);
tv_pf.setText(profit[position]);
return listViewItem;
}
}
Also note that I have set the spinner to point to the current month and year so the first time it works properly.
I am new to programming so any help or suggestion is appreciated.Thank you.
Hi #AndroidNewBee,
As per our discussion made following changes in your code and you will get proper output and it will resolve your issues.
if (resp.equals("Nothing to display"))
{
Toast.makeText(getContext(), "Nothing to Display", Toast.LENGTH_SHORT).show();
bl = new BS_allList(getActivity(),{""},{""},{""},{""},{""});
lv_bsall.setAdapter(bl);
}
And second is check validation as below,
try {
if((selected_year != null & selected_year.length > 0 ) & (tv_month.getText().toString() != null & tv_month.getText().toString().length > 0))
{
send_date();
}
} catch (JSONException e) {
e.printStackTrace();
}
First you shouldn't be using so many String[], instead wrap them in a class
Class BSDataModel{
private String doc_no;
private String balance;
private String total;
private String vat;
private String profit;
//getters and setters
}
Now the reponse result should be added as in ,it returns List<BSDataModel>
List<BSDataModel> reponseList = new ArrayList<>();
//for example adding single response
for(int i=0;i<jsonArrayResponse.length();i++){
BSDataModel singleResponse = new BSDataModel();
singleResponse.setDocNo(jsonArrayResponse.get(i).getString("doc_no"));
singleResponse.setBalace(jsonArrayResponse.get(i).getString("balance"));
//etc..finall add that single response to responseList
reponseList.add(singleResponse);
}
BS_allList.java
public class BS_allList extends ArrayAdapter<BSDataModel>
{
private List<BSDataModel> bsList;
private Activity context;
public BS_allList(Activity context,List<BSDataModel> bsList)
{
super(context, R.layout.bs_list_all, bsList);
this.context =context;
this.bsList = bsList;
}
#Override
public View getView(int position, View listViewItem, ViewGroup parent)
{
if (null == listViewItem)
{
LayoutInflater inflater = context.getLayoutInflater();
listViewItem = inflater.inflate(R.layout.bs_list_all, null, true);
}
TextView tv_docNo = (TextView) listViewItem.findViewById(R.id.tvdoc_no);
TextView tv_balance = (TextView) listViewItem.findViewById(R.id.tv_balance);
TextView tv_tot = (TextView) listViewItem.findViewById(R.id.tv_total);
TextView tv_vat = (TextView) listViewItem.findViewById(R.id.tv_vat);
TextView tv_pf = (TextView) listViewItem.findViewById(R.id.tv_profit);
BSDataModel bsData = bsList.get(position);
tv_docNo.setText(bsData.getDoc());
tv_balance.setText(bsData.getBalance());
tv_tot.setText(bsData.getTot());
tv_vat.setText(bsData.getVat());
tv_pf.setText(bsData.getPF());
return listViewItem;
}
}
Now in your class
BS_allList bl = new BS_allList(getActivity(),responseList);//which you got above
After receiving new Response
// remove old data
responseList.clear(); // list items in the sense list of array used to populate listview
if(newresponseArray.size() > 0){
for(int i=0;i<newjsonArrayResponse.length();i++){
BSDataModel singleResponse = new BSDataModel();
singleResponse.setDocNo(newjsonArrayResponse.get(i).getString("doc_no"));
singleResponse.setBalace(newjsonArrayResponse.get(i).getString("balance"));
//etc..finall add that single response to responseList
reponseList.add(singleResponse);
}
}
//refresh listview
bl.notifyDataSetChanged();
Try this way,
1. adapter.clear();
2. Add/Remove your list Items.
3. listview.setAdapter(adapter);
4. adapter.notifyDatasetChanged();
this procedure should work.