I have chat fragment which is backed by recyclerview. When i enter chat screen manually to send or receive message its work fine. but when i closed app and enter app from message notification, Its sending message to other user but not updating own recyclerview.
Chat Fragment
public class Chat_Screen_Fragment extends Fragment implements View.OnClickListener, ChildEventListener{
public static final String TAG = "###CHAT SCREEN###";
List<Chat_Wrapper> message = new ArrayList<>();
Chat_Adapter adapter;
RecyclerView recyclerView;
LinearLayoutManager layoutManager;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View v=inflater.inflate(R.layout.chat_screen_main_fragment,container,false);
setRetainInstance(true);
// GET INTENT VALUES FROM USER PROFILE CLASS
UserName_Intent = getArguments().getString("Get_Name");
UserImage_Intent = getArguments().getString("Get_Image");
UserPhone_Intent = getArguments().getString("Get_Phone");
UserID_Intent = getArguments().getString("Get_ID");
FirebaseToken_Intent = getArguments().getString("Get_Token"); //Firebase Token of other person
Room_Name_Intent = getArguments().getString("Get_Other"); // Room Name of chat
UserLastSeen_Intent=getArguments().getString("LastSeen");
//Sender_FCMToken = Session.getFirebaseID();
// RECYCLER VIEW
recyclerView = v.findViewById(R.id.Chat_Screen_Message_List);
layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setStackFromEnd(true);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(layoutManager);
databaseReference = FirebaseDatabase.getInstance().getReference().child(Room_Name_Intent);
databaseReference.addChildEventListener(this);
adapter = new Chat_Adapter(getActivity(), message);
recyclerView.setAdapter(adapter);
// FETCH OLD MESSAGE FROM DATABASE
chatDatabase();
return v;
}
// FIREBASE REAL TIME DATABASE WHICH FETCH ALL MESSAGES (SYNC) FROM ONLINE DATABASE
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
append_chat_conversation(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
append_chat_conversation(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
//THIS METHOD WILL FETCH ADD ALL MESSAGES FROM FIREBASE DATABASE AND ALSO SEARCH RESULT
private synchronized void append_chat_conversation(DataSnapshot dataSnapshot) {
iterator = dataSnapshot.getChildren().iterator();
while (iterator.hasNext()) {
// NOW GET ALL DATA FROM FIREBASE DATABASE AND SAVE IT INTO STRINGS THEN CHECK EACH BY ITS MESSAGE TYPE
Chat_Msg = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_FROM = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_FCM_TO= (String) ((DataSnapshot) iterator.next()).getValue();
Chat_Database tempChatDatabase = new Chat_Database(getActivity());
boolean hasValue=tempChatDatabase.CheckValueExist(_ID);
if (!hasValue) {
Log.d(TAG,"Chat Message "+Chat_Msg);
long id=chat_database.Insert_Chat(Session.getUserID(),Room_Name_Intent, UserID_Intent, "Text", Chat_Msg, Chat_FROM, Chat_TO, Chat_TimeStamp, Chat_FCM_FROM, Chat_FCM_TO, Session.getPhoneNO(), UserPhone_Intent,Random_ID,UserImage_Intent,UserLastSeen_Intent,Chat_FROM_ID);
//Adding Chat Data Into Database
Log.d(TAG,"Database Entry ID "+id);
if (id==0){
Log.d(TAG,"Database Already Has Value Of This Random Id ");
return;
}
Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, User_Intent, UserImage_Intent, Chat_FROM, null,null,id);
message.add(chat_wrapper);
adapter.notifyDataSetChanged();
Log.d(TAG, "FIREBASE STORAGE PHOTO-3-MESSAGE ARRAY SIZE " + message.size());
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
}
}
Log.d(TAG, "MESSAGE ARRAY SIZE " + message.size());
tempChatDatabase.isDatabaseClose();
}
adapter.notifyDataSetChanged();
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(message.size()-1);
//recyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
private void chatDatabase(){
//Database Init and Filling Adapter
Log.d(TAG,"Chat Database Function");
chat_database=new Chat_Database(getActivity());
chatCursor=chat_database.getUserChat(UserID_Intent);
boolean checkDB_Exist=functions.DatabaseExist(getActivity(),"CHAT_DATABASE.DB");
boolean chatItemsCounts=chatCursor.getCount()>0;
chatCursor.moveToFirst();
Log.d(TAG,"Value At Chat Database "+ checkDB_Exist+" "+chatItemsCounts);
if (checkDB_Exist && chatCursor.getCount()>0 && chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID")).equals(UserID_Intent)){
Log.d(TAG,"Database Exist Chat Database");
message.clear();
chatCursor.moveToFirst();
do {
database_rowID=chatCursor.getInt(chatCursor.getColumnIndex("ID"));
database_userID=chatCursor.getString(chatCursor.getColumnIndex("USER_ID"));
database_RoomName =chatCursor.getString(chatCursor.getColumnIndex("ROOM_NAME"));
database_ReceiverID=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID"));
database_MessageType=chatCursor.getString(chatCursor.getColumnIndex("MESSAGE_TYPE"));
database_Message=chatCursor.getString(chatCursor.getColumnIndex("USER_MESSAGE"));
database_MsgFrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_NAME"));
database_MsgTo=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_NAME"));
database_TimeStamp=chatCursor.getString(chatCursor.getColumnIndex("TIME_STAMP"));
database_FCMfrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_TOKEN"));
database_FCMto=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_TOKEN"));
database_LocalPath=chatCursor.getString(chatCursor.getColumnIndex("DOWNLOADED_AT"));
database_PhoneFrom=chatCursor.getString(chatCursor.getColumnIndex("MY_PHONE"));
database_PhoneTo=chatCursor.getString(chatCursor.getColumnIndex("OTHER_PHONE"));
Log.d(TAG,"Value Of Database Message String = "+database_Message);
Log.d(TAG,"Row ID of Database "+database_rowID);
// Check Message Type
Log.d(TAG,"Message Type Is Text");
Chat_Wrapper text = new Chat_Wrapper(database_Message, null, null, null, null, null, null, database_TimeStamp, database_PhoneTo, UserImage_Intent, database_MsgFrom,null,null,database_rowID);
message.add(text);
}
while(chatCursor.moveToNext());
Room_Name_Intent = database_RoomName;
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
adapter.notifyDataSetChanged();
chatCursor.close();
boolean value = chat_database.isDatabaseClose();
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(message.size()-1);
}
});
Log.d(TAG,"Value Of Database Close or Not "+value);
}
}
}
Chat Adapter
public class Chat_Adapter extends RecyclerView.Adapter<Chat_Adapter.ViewHolder> {
public static final String TAG="###CHAT_ADAPTER###";
private Context context;
Chat_Database database;
Chat_Wrapper chat_wrapper;
public Chat_Adapter(Context context, List<Chat_Wrapper> message) {
this.context = context;
this.arrayList_message = message;
}
#Override
public Chat_Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View Layout;
Log.d(TAG,"On Create View Holder Calling ");
if (viewType==1){
Log.d(TAG,"View Tyoe Is "+viewType);
Layout=LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_screen_message_item,parent,false);
// ImagePath=Session.getUserImage();
}
else {
Log.d(TAG,"View Type Is "+viewType);
Layout=LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_screen_message_item_other,parent,false);
// ImagePath=chat_wrapper.getImageView();
}
return new ViewHolder(Layout);
}
#Override
public void onBindViewHolder(final Chat_Adapter.ViewHolder holder, final int position) {
chat_wrapper=arrayList_message.get(position);
database=new Chat_Database(context);
holder.Message.setVisibility(View.VISIBLE);
holder.TimeStamp.setVisibility(View.VISIBLE);
holder.User_Image.setVisibility(View.VISIBLE);
//CHECK SENDER IS SAME AS LOGGED IN USER
if ((Session.getUserFname()+" "+Session.getUserLname()).equals(chat_wrapper.getSender_UserName())){
ImagePath=Session.getUserImage();
Log.d(TAG,"Session.getUserImage() "+Session.getUserImage());
Log.d(TAG,"Value Of Message Running ImagePath "+ImagePath);
}
else {
//String filePath="/data/data/com.boysjoys.com.pro_working1/my_picture.jpg"
ImagePath=chat_wrapper.getImageView();
Log.d(TAG,"Value Of Message Running ImagePath "+ImagePath);
}
holder.Message.setText(chat_wrapper.getMessage());
holder.TimeStamp.setText(chat_wrapper.getTimestamp());
holder.TimeStamp.setVisibility(View.GONE);
Glide.with(MyApplication.getmContext())
.load(ImagePath)
.apply(new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.fitCenter().skipMemoryCache(false))
.into(holder.User_Image);
//Make TimeStamp Visible or Hidden
holder.Message.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d(TAG,"On Click Text View Message");
if (isShowing){
holder.TimeStamp.setVisibility(View.VISIBLE);
Log.d(TAG,"Is Showing Is True");
isShowing=false;
}
else {
holder.TimeStamp.setVisibility(View.GONE);
isShowing=true;
}
}
});
}
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemCount() {
Log.d(TAG,"GET ITEM COUNT--Array Message List Size "+arrayList_message.size());
return arrayList_message.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView Message;
TextView TimeStamp;
ImageView User_Image;
public ViewHolder(View itemView) {
super(itemView);
Log.d(TAG,"View Holder Constructor Calling. Now Inflating Layout Items");
Message = itemView.findViewById(R.id.Single_Item_Chat_Message);
TimeStamp = itemView.findViewById(R.id.Single_Item_Chat_TimeStamp);
User_Image = itemView.findViewById(R.id.Single_Item_Chat_ImageView);
}
}
Whenever i received message as notification after tapping on notification it open same chat screen (Load all previous message first) and when i click on send button to send message it send to other user but not updating my recyclerview.
UPDATE: I just found the issue which is fetching earlier messages from local database when i comment chatDatabase() function it works fine. but still not able to fix the issue.
When you are appending a new item to the list in append_chat_conversation method, you are not notifying the adapter about it. I don't know how that framework works but you should notify the adapter about any change in the dataset by using the notifyDataSetChaged on the adapter or better using the specific inserted/changed/deleted notify methods for better performance.
Why don't you call
adapter.notifyDataSetChanged();
after
Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, User_Intent, UserImage_Intent, Chat_FROM, null,null,id);
message.add(chat_wrapper);
as
notifyDataSetChanged only works if you use the add(), insert(), remove(), and clear() on the Adapter.
Related
I have a problem with Recyclerview item click. I fetch data to list() method and add via addItem() method in recyclerview custom adapter when scroll down in addOnScrollListener. I get item position with click interface on Fragment. First fetch data work perfectly but when fetch loadmore, can't retrive item positon to new data with onButtonLClick() method.
// in onBindViewHolder;
holder.lnl.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
try {
rcylviewItemNotify.onButtonLClick(position);
}catch (Throwable e){
//interface can be null
}
}
});
// addItem() method in adapter;
public void addItem(List<Image> img) {
for (Image im : img) {
arrayList.add(im);
}
notifyDataSetChanged();
}
// interface code;
public interface RcylviewItemNotify {
void onButtonLClick(int position);
}
// in Fragment code;
public void list() {
GetServices service = RetrofitInstance.getRetrofitInstance().create(GetServices.class);
Call<Images> call = service.getImages();
call.enqueue(new Callback<Images>() {
#Override
public void onResponse(Call<Images> call, Response<Images> response) {
Images body = response.body();
records = body.getImages();
adapter.addItem(records);
}
#Override
public void onFailure(Call<Images> call, Throwable t) {
Toast.makeText(getActivity(), "Network hatası onFailure", Toast.LENGTH_SHORT).show();
reflesh.setRefreshing(false);
}
});
}
#Override
public void onButtonLClick(int position) {
final String clickId = String.valueOf(records.get(position).getID());
Toast.makeText(getActivity(), "ID: " + clickId, Toast.LENGTH_SHORT).show();
}
// recycler settings;
public void loadView() {
layoutManager = new GridLayoutManager(getActivity(), 2);
recyclerView.setLayoutManager(layoutManager);
Collections.reverse(records);
adapter = new RecyclerViewAdapter(this,(ArrayList<Image>) records, getActivity());
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
reflesh.setRefreshing(false);
}
I'm not sure if this is your issue but you should be using the ViewHolder to get the position. Inside of your onBindViewHolder:
#Override
public void onClick(View view){
int itemPosition = holder.getAdapterPosition();
// Then do whatever you need to with the position
}
I have chat app with recyclerview, Its working fine until i come back to chat screen from notification, when i come to chat screen from notification i can send message to other user and other user can send message to me but the chat screen not updating at all whether i send message or i receive. There is function called chatDatabase() i notice if i comment that function then screen works perfectly without any issue even if i come on chat screen from notification. But i am not able to figure out why its not working with chatDatabase()
ChatScreenFragment
public class Chat_Screen_Fragment extends Fragment implements View.OnClickListener, ChildEventListener{
public static final String TAG = "###CHAT SCREEN###";
List<Chat_Wrapper> message = new ArrayList<>();
Chat_Adapter adapter;
RecyclerView recyclerView;
LinearLayoutManager layoutManager;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View v=inflater.inflate(R.layout.chat_screen_main_fragment,container,false);
setRetainInstance(true);
// GET INTENT VALUES FROM USER PROFILE CLASS
UserName_Intent = getArguments().getString("Get_Name");
UserImage_Intent = getArguments().getString("Get_Image");
UserPhone_Intent = getArguments().getString("Get_Phone");
UserID_Intent = getArguments().getString("Get_ID");
FirebaseToken_Intent = getArguments().getString("Get_Token"); //Firebase Token of other person
Room_Name_Intent = getArguments().getString("Get_Other"); // Room Name of chat
UserLastSeen_Intent=getArguments().getString("LastSeen");
//Sender_FCMToken = Session.getFirebaseID();
// RECYCLER VIEW
recyclerView = v.findViewById(R.id.Chat_Screen_Message_List);
layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setStackFromEnd(true);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(layoutManager);
databaseReference = FirebaseDatabase.getInstance().getReference().child(Room_Name_Intent);
databaseReference.addChildEventListener(this);
adapter = new Chat_Adapter(getActivity(), message);
recyclerView.setAdapter(adapter);
// FETCH OLD MESSAGE FROM DATABASE
chatDatabase();
return v;
}
// FIREBASE REAL TIME DATABASE WHICH FETCH ALL MESSAGES (SYNC) FROM ONLINE DATABASE
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
append_chat_conversation(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
append_chat_conversation(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
private synchronized void append_chat_conversation(DataSnapshot dataSnapshot) {
iterator = dataSnapshot.getChildren().iterator();
while (iterator.hasNext()) {
// NOW GET ALL DATA FROM FIREBASE DATABASE AND SAVE IT INTO STRINGS THEN CHECK EACH BY ITS MESSAGE TYPE
Chat_Msg = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_FROM = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_FCM_TO= (String) ((DataSnapshot) iterator.next()).getValue();
Chat_Database tempChatDatabase = new Chat_Database(getActivity());
boolean hasValue=tempChatDatabase.CheckValueExist(_ID);
if (!hasValue) {
Log.d(TAG,"Chat Message "+Chat_Msg);
long id=chat_database.Insert_Chat(Session.getUserID(),Room_Name_Intent, UserID_Intent, "Text", Chat_Msg, Chat_FROM, Chat_TO, Chat_TimeStamp, Chat_FCM_FROM, Chat_FCM_TO, Session.getPhoneNO(), UserPhone_Intent,Random_ID,UserImage_Intent,UserLastSeen_Intent,Chat_FROM_ID);
//Adding Chat Data Into Database
Log.d(TAG,"Database Entry ID "+id);
if (id==0){
Log.d(TAG,"Database Already Has Value Of This Random Id ");
return;
}
Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, User_Intent, UserImage_Intent, Chat_FROM, null,null,id);
message.add(chat_wrapper);
adapter.notifyDataSetChanged();
Log.d(TAG, "FIREBASE STORAGE PHOTO-3-MESSAGE ARRAY SIZE " + message.size());
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
}
}
Log.d(TAG, "MESSAGE ARRAY SIZE " + message.size());
tempChatDatabase.isDatabaseClose();
}
adapter.notifyDataSetChanged();
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(message.size()-1);
//recyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
private void chatDatabase(){
//Database Init and Filling Adapter
Log.d(TAG,"Chat Database Function");
chat_database=new Chat_Database(getActivity());
chatCursor=chat_database.getUserChat(UserID_Intent);
boolean checkDB_Exist=functions.DatabaseExist(getActivity(),"CHAT_DATABASE.DB");
boolean chatItemsCounts=chatCursor.getCount()>0;
chatCursor.moveToFirst();
Log.d(TAG,"Value At Chat Database "+ checkDB_Exist+" "+chatItemsCounts);
if (checkDB_Exist && chatCursor.getCount()>0 && chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID")).equals(UserID_Intent)){
Log.d(TAG,"Database Exist Chat Database");
message.clear();
chatCursor.moveToFirst();
do {
database_rowID=chatCursor.getInt(chatCursor.getColumnIndex("ID"));
database_userID=chatCursor.getString(chatCursor.getColumnIndex("USER_ID"));
database_RoomName =chatCursor.getString(chatCursor.getColumnIndex("ROOM_NAME"));
database_ReceiverID=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID"));
database_MessageType=chatCursor.getString(chatCursor.getColumnIndex("MESSAGE_TYPE"));
database_Message=chatCursor.getString(chatCursor.getColumnIndex("USER_MESSAGE"));
database_MsgFrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_NAME"));
database_MsgTo=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_NAME"));
database_TimeStamp=chatCursor.getString(chatCursor.getColumnIndex("TIME_STAMP"));
database_FCMfrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_TOKEN"));
database_FCMto=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_TOKEN"));
database_LocalPath=chatCursor.getString(chatCursor.getColumnIndex("DOWNLOADED_AT"));
database_PhoneFrom=chatCursor.getString(chatCursor.getColumnIndex("MY_PHONE"));
database_PhoneTo=chatCursor.getString(chatCursor.getColumnIndex("OTHER_PHONE"));
Log.d(TAG,"Value Of Database Message String = "+database_Message);
Log.d(TAG,"Row ID of Database "+database_rowID);
// Check Message Type
Log.d(TAG,"Message Type Is Text");
Chat_Wrapper text = new Chat_Wrapper(database_Message, null, null, null, null, null, null, database_TimeStamp, database_PhoneTo, UserImage_Intent, database_MsgFrom,null,null,database_rowID);
message.add(text);
}
while(chatCursor.moveToNext());
Room_Name_Intent = database_RoomName;
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
adapter.notifyDataSetChanged();
chatCursor.close();
boolean value = chat_database.isDatabaseClose();
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(message.size()-1);
}
});
Log.d(TAG,"Value Of Database Close or Not "+value);
}
}
}
UPDATE: i just logout and login again then entered into chat screen from notification then its working fine. Also if i remove app from recent app then it also work but i am still unable to understand, why its happening.
I looked through your chatDatabase function and noticed some suspicious behavior right before you call notifyDataSetChanged on your adapter. I tested in my own project and noticed my rendering also stopped if I tried to set the LayoutManager on the RecyclerView after it was already set.
You set up your RecyclerView fine with it at the beginning, so removing the new calls should solve the issue, as long as there isn't anything else wrong with the chatDatabase function. Let me know if removing the LayoutManager lines from the chatDatabase function solve the issue!
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
adapter.notifyDataSetChanged();
On my onBindViewHolder I have this to set the setImageResource
holder.card_image.setImageResource(image);
But my items can be purchased so, I have this to purchase on my holder.view.setOnClickListener()
bp.purchase((Activity) mContext,model.getProduct_id());
so, it goes to this method :
bp = new BillingProcessor() new BillingProcessor.IBillingHandler() {
#Override
public void onProductPurchased(#NonNull String productId, #Nullable TransactionDetails details) {
showToast("onProductPurchased: " + productId);
//Purchased OK
//WANT TO CHANGE THE IMAGE ONCE PURCHASE IS OK
}
#Override
public void onBillingError(int errorCode, #Nullable Throwable error) {
showToast("onBillingError: " + Integer.toString(errorCode));
}
#Override
public void onBillingInitialized() {
showToast("onBillingInitialized");
readyToPurchase = true;
}
#Override
public void onPurchaseHistoryRestored() {
showToast("onPurchaseHistoryRestored");
for(String sku : bp.listOwnedProducts())
Log.d("skuProducts", "Owned Managed Product: " + sku);
for(String sku : bp.listOwnedSubscriptions())
Log.d("skuProducts", "Owned Subscription: " + sku);
}
});
How do I change it if I'm not onBindViewHolder?
My adapter looks like :
FirebaseRecyclerAdapter adapter = new FirebaseRecyclerAdapter< CardPOJO, CardHolder>(options) {
#Override
public CardHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//inflate the single recycler view layout(item)
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_product, parent, false);
int width = parent.getMeasuredWidth() / 2;
width -= mContext.getResources().getDimensionPixelSize(R.dimen._8sdp);
final CardHolder cardViewHolder = new CardHolder(view,width);
return cardViewHolder;
}
#Override
public void onDataChanged() {
super.onDataChanged();
tv.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
#Override
protected void onBindViewHolder(CardHolder holder, int position, final CardPOJO model) {
holder.state.setText(model.getState());
holder.cardName.setText(model.getName());
switch (model.getState()){
case "free":
//Img free
break;
case "not_free":
//Img not free
break;
default:
break;
}
holder.view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(model.getState().equals("free")){
//stuff
}
else{
//stuff
}
root_ref.child("PurchasedProducts").child(currentuser).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
bp.purchase((Activity) mContext,model.getProduct_id()); //HERE I CALL THE PURCHASE SO IF IT'S OK I WANT TO DO SOMETHING LIKE holder.card_image.setImageResource(image);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
});
}
};
adapter.startListening();
products_recycler.setAdapter(adapter);
If I assume correctly you want to change the view appearance or some image change if some payment is done successful or failed.
for that, you can have a callback which will give you the item position in activity or fragment back from there you can make a server call to make the purchase happen and if everything goes well.
when you make your adapter constructor pass the callback
final SomeAdapter obj = new SomeAdapter(this,new Callback(){
#Override
onPaymentRequested(int position, View view){
//this will get called when you press click on image in bindviewholder
bp = new BillingProcessor() new BillingProcessor.IBillingHandler() {
#Override
public void onProductPurchased(#NonNull String productId, #Nullable TransactionDetails details) {
showToast("onProductPurchased: " + productId);
//Purchased OK
adapterModelList.get(position).setPayment(true);
obj.notifyDataSetChanged();
}
#Override
public void onBillingError(int errorCode, #Nullable Throwable error) {
showToast("onBillingError: " + Integer.toString(errorCode));
}
#Override
public void onBillingInitialized() {
showToast("onBillingInitialized");
readyToPurchase = true;
}
#Override
public void onPurchaseHistoryRestored() {
showToast("onPurchaseHistoryRestored");
for(String sku : bp.listOwnedProducts())
Log.d("skuProducts", "Owned Managed Product: " + sku);
for(String sku : bp.listOwnedSubscriptions())
Log.d("skuProducts", "Owned Subscription: " + sku);
}
});
}
});
recyclerView.setAdapter(obj);
so when you call your obj.notifyDataSetChanged(); it will make the adapter to draw all views again where you can set some flag according to int position recieved for click callback and make it change accordingly.
Edit=>07/12/2018: Tried the Firebase Adapter and made few changes since the code was not enough to replicate the scenario but I have made a sample class made few changes but the basic idea is like below.
1: When user click on view in onBindViewHolder we receive a callback which gives a position parameter in fragment or activity from where we are calling
2: Now we process the payment and when we are done we make a change in Database firebase also by updating the CardPojo to server for that particular user item.
3: while we update the CardPojo on server we also set a flag in card pojo which is a boolean for paymentSuccess which will be true when payment is done.
4: since our payment is done and is synced with server with new flag data now we can just call firebaseRecycler.notifyItemChanged(position); which will get the lates update from the server for that particular position which we have received on callback.
5: Now populateViewHolder() gives you a cardpojo object you can check if payment is done then you can change the image
so here is the sample code involved I have tried to match the scenario at best, hope you understand what I am trying to do here.
so first create a listener or a callback
public interface CallBackInterface {
void onClick(int position,CardPOJO cardPOJO);
}
now instead of initializing the FirebaseRecyclerAdapter in activity or fragment just create a class and extend it this separates your ui logic and gives us the extensibility of doing extra things like adding callback.
public class FirebaseRecycler extends FirebaseRecyclerAdapter<CardPOJO,CardHolder> {
CallBackInterface callBackInterface;
public FirebaseRecycler(Class<CardPOJO> modelClass, int modelLayout, Class<CardHolder> viewHolderClass, DatabaseReference ref) {
super(modelClass, modelLayout, viewHolderClass, ref);
this.callBackInterface = callBackInterface;
}
public FirebaseRecycler(Class<CardPOJO> modelClass, int modelLayout, Class<CardHolder> viewHolderClass, Query ref) {
super(modelClass, modelLayout, viewHolderClass, ref);
this.callBackInterface = callBackInterface;
}
#Override
public CardHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//your inflater logic goes here
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_product, parent, false);
CardHolder cardHolder = new CardHolder(view);
return cardHolder;
}
#Override
protected void populateViewHolder(CardHolder viewHolder, final CardPOJO model, final int position) {
//your populate logic
//your existing code here
if (model.isPaymentDone){
//set payment success image holder.card_image.setImageResource(image);
}else{
//set payment failure image
}
//setting the card click listener
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//we have the card click listener, we will start the payment processing in activity
callBackInterface.onClick(position,model);
}
});
}
public void setCallBackInterface(CallBackInterface callBackInterface) {
this.callBackInterface = callBackInterface;
}
}
now almost everything is done we need to call this Custom Firebase adapter and pass the required things and it will do its job.
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final DatabaseReference mDatabaseRef = FirebaseDatabase.getInstance().getReference();
/*
if you have any other database child then you can refer to it using
DatabaseReference child = mDatabaseRef.child("yourchilddatabase");
and pass this to the last argument
*/
final FirebaseRecycler firebaseRecycler = new FirebaseRecycler(CardPOJO.class, R.layout.card_product, CardHolder.class, mDatabaseRef);
firebaseRecycler.setCallBackInterface(new CallBackInterface() {
#Override
public void onClick(final int position, final CardPOJO cardPOJO) {
//start processing the payment
bp = new BillingProcessor() new BillingProcessor.IBillingHandler() {
#Override
public void onProductPurchased(#NonNull String productId, #Nullable TransactionDetails details) {
/**
*when you have processed the payment just enable the flag on server database by having a extra boolean flag for this
* and check in onBindViewHolder if this is enabled if so then replace your image
* updating the values on server, you can handle it according to your user case
*/
cardPOJO.setPaymentDone(true);
mDatabaseRef.push().setValue(cardPOJO);
firebaseRecycler.notifyItemChanged(position);
}
#Override
public void onBillingError(int errorCode, #Nullable Throwable error) {
//existing logic
}
#Override
public void onBillingInitialized() {
//existing logic
}
#Override
public void onPurchaseHistoryRestored() {
//existing logic
}
};
}
});
}
this demonstrates the basic logic you can patch it according to your requirement.
Get your item from RecyclerView's adapter and edit it. Then just call Adapter.onItemChanged(int position), this will cause to call onBindViewholder to be called specifically for that position.
I am making an app and has a recyclerview for showing user feed. I am using a toggle button for liking posts. When the user opens the app it enables the like button if the status is already liked by the user who is using the app and the user can also like the posts by scrolling down like other socials apps out there.
The problem is if a toggle button is checked when scrolling down
other toggle buttons belongs to other posts are also getting checked.
Adapter class
public class topNewsAdapter extends RecyclerView.Adapter<topNewsRowHolder> {
private ArrayList<topData> topDataList;
private Context context;
private Activity activity;
private RecyclerView recyclerView;
private View v;
protected String liked = "success";
protected String expired = "Expired";
public topNewsAdapter( ArrayList<topData> listItemList , Activity activity , RecyclerView view) {
this.topDataList = listItemList;
this.activity = activity;
this.recyclerView = view;
}
#Override
public topNewsRowHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.top_news_row, parent, false);
context = parent.getContext();
return new topNewsRowHolder(v , activity); //passed activity
}
#Override
public void onBindViewHolder(final topNewsRowHolder holder, final int position) {
topData item = topDataList.get(position);
if(!(item == null)) {
holder.userName.setText(item.getUserName().toString());
holder.timer.setText(item.getCreatedTime().toString());
holder.status.setText(item.getStatus().toString());
holder.fameCount.setText(item.getLikeCount().toString());
holder.dislike_Count.setText(item.getDislike_Count().toString());
holder.statusID.setText(item.getStatusID().toString());
if(item.getLiked().equals(1)){
holder.fameButton.setChecked(true);
}else if(item.getLiked().equals(0)){
holder.fameButton.setChecked(false);
}
if(item.getDisliked().equals(1)){
holder.disLikeButton.setChecked(true);
}else if(item.getDisliked().equals(0)){
holder.disLikeButton.setChecked(false);
}
holder.fameButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked){
lRequester(holder.statusID.getText().toString() , "1" , holder.fameCount );
dRequester(holder.statusID.getText().toString() , "0" , holder.dislike_Count);
holder.fameButton.setChecked(true);
}else if(!isChecked){
lRequester(holder.statusID.getText().toString() , "0" , holder.fameCount);
}
}
});
holder.disLikeButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked){
dRequester(holder.statusID.getText().toString(), "1" ,holder.dislike_Count);
lRequester(holder.statusID.getText().toString() , "0" , holder.fameCount);
holder.fameButton.setChecked(false);
}else if(!isChecked){
dRequester(holder.statusID.getText().toString() , "0" , holder.dislike_Count);
}
}
});
}
}
#Override
public int getItemCount() {
return topDataList.size();
}
#Override
public long getItemId(int position) {
return position;
}
public void lRequester(String id , String type, final TextView textview){
Call<responseFD> request = handler.handlerClass.fame(data("u") , data("t") , id , type );
request.enqueue(new Callback<responseFD>() {
#Override
public void onResponse(Call<responseFD> call, Response<responseFD> response) {
if(!response.body().getResponse().isEmpty() && (response.body().getResponse() != null)) {
if (response.body().getResponse().equals(expired)) {
vlData.getInstance().terminateRunnable();
reset();
}else if (response.body().getResponse().equals(liked)) {
textview.setText(response.body().getLike_count().toString());
}
}
}
#Override
public void onFailure(Call<responseFD> call, Throwable t) {
Toast.makeText(v.getContext(), "Something Gone Wrong", Toast.LENGTH_SHORT).show();
}
});
}
public void dRequester(String id , String type , final TextView textView){
Call<responseFD> request = handler.handlerClass.disfame(data("u") , data("t") , id , type );
request.enqueue(new Callback<responseFD>() {
#Override
public void onResponse(Call<responseFD> call, Response<responseFD> response) {
if(!response.body().getResponse().isEmpty() && response.body().getResponse() != null) {
if (response.body().getResponse().equals(expired)) {
vlData.getInstance().terminateRunnable();
reset();
}else if (response.body().getResponse().equals(liked)) {
textView.setText(response.body().getDislike_Count().toString());
}
}
}
#Override
public void onFailure(Call<responseFD> call, Throwable t) {
Log.d("data", "onFailure: " + t.getMessage());
}
});
}
public void reset(){
Intent i = new Intent(activity , login.class);
activity.startActivity(i);
activity.finish();
}
}
Rowholder class
public class topNewsRowHolder extends RecyclerView.ViewHolder {
protected TextView userName;
protected TextView timer;
protected TextView status;
protected ImageView profilePicHolder;
protected TextView fameCount;
protected TextView dislike_Count;
protected ToggleButton fameButton;
protected ToggleButton disLikeButton;
protected View v;
protected TextView statusID;
protected Activity activity;
protected RelativeLayout holderlayout;
public topNewsRowHolder(View view , Activity activity ){
super(view);
v = view;
this.activity = activity;
this.userName = (TextView)view.findViewById(R.id.usernameHolder);
this.timer = (TextView)view.findViewById(R.id.timeHolder);
this.status = (TextView)view.findViewById(R.id.status_user);
this.profilePicHolder = (ImageView)view.findViewById(R.id.profile_pic_holder);
this.fameCount = (TextView)view.findViewById(R.id.like_count);
this.dislike_Count = (TextView)view.findViewById(R.id.dislike_count);
this.fameButton = (ToggleButton)view.findViewById(R.id.fameButton);
this.disLikeButton = (ToggleButton)view.findViewById(R.id.dislikeButton);
this.statusID = (TextView)view.findViewById(R.id.statusID);
this.holderlayout = (RelativeLayout) view.findViewById(R.id.text);
}
}
I know this is because of the recycling. How to fix this issue ?. Thanks :)
Try this : RecyclerView needs an external variable to keep track of which items are checked, while SparseBooleanArray will work, its tedious.
holder.fameButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean IsChecked) {
isChecked ? item.setIsLiked(1) : item.setIsLiked(0);
if(isChecked){
lRequester(holder.statusID.getText().toString() , "1" , holder.fameCount );
dRequester(holder.statusID.getText().toString() , "0" , holder.dislike_Count);
} else if(!isChecked){
lRequester(holder.statusID.getText().toString() , "0" , holder.fameCount);
}
}
});
Create a Broker pattern for managind likes within the content feed.
This singleton broken would keep track of each Content feed item id and map it to a state (social or just likes) in a hashmap. And every time there is a reaction (like/unlike/dislike/undislike) happenning to a Content feed item, you trigger the actian using this Broker that also propagates and keeps track of the changes locally. You ahould implement a Broadcaster or a pub-sub pattern to ibform the UI about the changes.
Preferably use a Content Provider with a database for storing the json you are getting from the webserver.
Let me know if you need further clarifications.
SocialBroker.java
public class SocialBroker extends ISocialBroker {
//region <!-- Private properties -->
private static SocialBroker _instance;
private HashMap<UUID, SocialStatus> _map = new HashMap<>();
//endregion
//region <!-- Singleton initializer -->
public static SocialBroker newInstance() {
if (_instance == null) {
_instance = new SocialBroker();
}
return _instance;
}
private SocialBroker() {};
//endregion
// The interface method implemented here
}
ISocialBroker.java
public interface ISocialBroker {
public synchronized SocialStatus save(String expiresAt, UUID id, boolean hasLiked, int numberOfLikes, int numberOfComments);
public SocialStatus lookup(UUID id);
public synchronized SocialStatus like(UUID id);
public synchronized SocialStatus unlike(UUID id);
public HashMap<UUID, SocialStatus> getMap();
public void loadState(HashMap<UUID, SocialStatus> restoredSocialState);
}
OnReactionObserver.java
public interface OnReactionObserver {
void onLikeReaction();
}
OnReactionPublisher.java
public interface OnReactionPublisher {
void attachObserver(OnReactionObserver... observer);
void notifyListeners();
}
When you bind the results to your views in your adapter, you save the entries using the broker, then in your like button view, you summon the Broker again to execute the like/unlike/dislike/undislike.
SocialStatus is just a model to keep track of information, that can be serialized, saved, synchronized with an online service.
Hope this points you to the right direction.
I created a Checkbox, however, whenever I click a few Items random checkboxes become clicked. I have a recycler view, and I have an adapter class as well. Here is my CheckBox method can anyone tell me the problem?
public void CheckBox(View view) {
final CheckBox checkBox = (CheckBox)view;
if (checkBox.isChecked()) {
System.out.println("SET TO CHECKED");
//Input instance of selected course(CHECKED)
// TODO: 2/5/16 Need to pass in Persisted ID
mVolleySingleton = VolleySingleton.getInstance();
mRequestQueue = mVolleySingleton.getRequestQueue();
CharSequence ID_PUT_COURSES = ((TextView) ((RelativeLayout) view.getParent()).getChildAt(1)).getText();
System.out.println(PUT);
String URL_PUT_COURSES = "URL"+ID_PUT+"\n";
System.out.print(PUT);
StringRequest UrlPut = new StringRequest(Request.Method.PUT, URL_PUT, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
System.out.println(response + "reponse");
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
System.out.println("************Answer" + error + "error");
}
});
mRequestQueue.add(UrlPutCourses);
System.out.println("Added");
}
else{
System.out.println("SET TO UNCHECKED");
//delete instance of selected course(UNCHECKED)
mVolleySingleton = VolleySingleton.getInstance();
mRequestQueue = mVolleySingleton.getRequestQueue();
// TODO: 2/5/16 Need to pass in Persisted ID
CharSequence DELETE = ((TextView) ((RelativeLayout) view.getParent()).getChildAt(1)).getText();
System.out.print(ID_PUT_COURSES);
String UR_DELETE = "URL"+ DELETE;
StringRequest UrlDeleteCourses = new StringRequest(Request.Method.DELETE, UR_DELETE, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
System.out.println(response + "reponse");
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
System.out.println("************Answer" + error + "error");
}
});
mRequestQueue.add(UR_DELETE);
System.out.println("Deleted");
}
}
ublic class AdapterSearch extends RecyclerView.Adapter<AdapterSearch.ViewSearch>{
private LayoutInflater mLayoutInflater;
private ArrayList<Search> ListSearch=new ArrayList<>();
public AdapterSearch(Context context){
mLayoutInflater=LayoutInflater.from(context);
}
public void setListSearch(ArrayList<Search> ListSearch){
this.ListSearch=ListSearch;
notifyItemRangeChanged(0,ListSearch.size());
}
#Override
public ViewSearch onCreateViewHolder(ViewGroup parent, int viewType) {
View view= mLayoutInflater.inflate(R.layout.custom_search,(ViewGroup)null);
ViewSearch holder=new ViewSearch(view);
return holder;
}
#Override
public void onBindViewHolder(ViewSearch holder, int position) {
Search currentSearch=ListSearch.get(position);
holder.mSearchText.setText(currentSearch.getMtitle());
holder.mAnswerPointsSearch.setText(currentSearch.getMkey());
holder.mSearchId.setText(currentSearch.getMid());
holder.mCourseId.setText(currentSearch.getCourseId());
}
#Override
public int getItemCount() {
return ListSearch.size();
}
public void setFilter(ArrayList<Search> Search) {
ListSearch = new ArrayList<>();
ListSearch.addAll(Search);
notifyDataSetChanged();
}
static class ViewSearch extends RecyclerView.ViewHolder{
private TextView mSearchText;
private TextView mAnswerPointsSearch;
private TextView mSearchId;
private TextView mCourseId;
public ViewSearch (View view){
super(view);
mSearchText=(TextView)itemView.findViewById(R.id.SearchText);
mAnswerPointsSearch=(TextView)itemView.findViewById(R.id.AnswerPointsSearch);
mSearchId=(TextView)itemView.findViewById(R.id.SearchId);
mCourseId=(TextView)itemView.findViewById(R.id.CourseTextView);
}
}
}
Better late then never I'd say, it might help other people stumbling upon this issue later, so here goes.
This might not be the best solution ever, but it should work.
In your Search class, you have some variables like title and key. Add the boolean isChecked to it, and make it false by default. Also create the matching getter and setter. (I'm using isChecked() & setChecked() in this example)
In your onBindViewHolder() method, add the following lines:
CheckBox checkBox = (CheckBox) itemView.findViewById(R.id.CheckBoxID);
// Instantiating the checkbox
holder.checkBox.setChecked(currentSearch.isChecked());
// Every time the holder (one list item) is shown, this gets called.
// It sets the checked state based on what's stored in currentSearch.
checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (checkBox.isChecked()) {
currentSearch.setChecked(true)
} else {
currentSearch.setChecked(false);
}
});
}
// This listens for clicks on the checkbox, and changes the checked state of
// the boolean in currentSearch to the correct state based on
// what it already was on.
Please be aware that I haven't worked with checkboxes before, so some things (like setting the checked state) might be done slightly differently from what I've described above, but this should be a working base.
You also probably want the checkboxes to only persevere until you click a button or something like that, and then have them reset. You might want to add a simple for loop for that, something like:
public void resetAllCheckBoxes() {
for (Search s : ListSearch) {
s.setChecked(false);
}
}