This question already has answers here:
How to return DataSnapshot value as a result of a method?
(6 answers)
Closed 4 years ago.
I'm working on a simple Android project which can insert and retrieve data from Firebase. The inserting function works well, which means the project has successfully connected to the Firebase database. However, the retrieving part doesn't work. I did many tests and think the problem is in the FirebaseHelper because when I tried to print the result of "firebasehelper.retrieveMajor()" in the Activity, it shows nothing. But it did show the data when printing data in FirebaseHelper. You can see the codes as followings.
Model:
#IgnoreExtraProperties
public class Major {
public String major_id;
public String major_name;
public Major() {
}
public Major(String major_id, String major_name) {
this.major_id = major_id;
this.major_name = major_name;
}
public String getMajor_id() {
return major_id;
}
public String getMajor_name() {
return major_name;
}
}
Adapter:
public class MajorListAdapter extends BaseAdapter {
Context context;
ArrayList<Major> majors;
public MajorListAdapter(Context context, ArrayList<Major> majors) {
this.context = context;
this.majors = majors;
}
#Override
public int getCount() {
return majors.size();
}
#Override
public Object getItem(int pos) {
return majors.get(pos);
}
#Override
public long getItemId(int pos) {
return pos;
}
#Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
if(convertView==null)
{
convertView= LayoutInflater.from(context).inflate(R.layout.model,viewGroup,false);
}
TextView tv_majorid= (TextView) convertView.findViewById(R.id.tx_majorid);
TextView tv_majorname= (TextView) convertView.findViewById(R.id.tx_majorname);
final Major major= (Major) this.getItem(position);
tv_majorid.setText(major.getMajor_id());
tv_majorname.setText(major.getMajor_name());
return convertView;
}
}
FirebaseHelper:
public class FirebaseHelper {
DatabaseReference db;
Boolean saved=null;
ArrayList<Major> majors = new ArrayList<>();
public FirebaseHelper(DatabaseReference db) {
this.db = db;
}
//Save the Major info. into db
public Boolean saveMajor(Major major)
{
if(major==null)
{
saved=false;
}else
{
try
{
db.child("Major").push().setValue(major);
saved=true;
}catch (DatabaseException e)
{
e.printStackTrace();
saved=false;
}
}
return saved;
}
private void fetchDataFromMajor(DataSnapshot dataSnapshot) {
majors.clear();
for (DataSnapshot ds : dataSnapshot.getChildren()) {
Major major = ds.getValue(Major.class);
majors.add(major);
}
}
public ArrayList<Major> retrieveMajor() {
db.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
fetchDataFromMajor(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
fetchDataFromMajor(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return majors;
}
}
The Activity that retrieves data and binds the data with the ListView:
public class MajorListActivity extends AppCompatActivity {
DatabaseReference db;
FirebaseHelper firebasehelper;
MajorListAdapter adapter;
ListView lv_MajorList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_major_list);
lv_MajorList = (ListView) findViewById(R.id.lv_MajorList);
db= FirebaseDatabase.getInstance().getReference();
firebasehelper=new FirebaseHelper(db);
//ADAPTER
adapter = new MajorListAdapter(getApplicationContext(),firebasehelper.retrieveMajor());
lv_MajorList.setAdapter(adapter);
}
}
Firebase APIs are asynchronous, meaning that they return immediately, before the results of your query are complete. Some time later, your callback will be invoked with the results. This means that your retrieveMajor function is returning whatever is in the array majors, before the query finishes.
You'll need to correctly handle the asynchronous nature of Firebase APIs, which means that you can't write methods that directly return data that you fetch from the database. You'll have to use the callbacks to wait for results, then update your UI as needed.
To learn more about why Firebase APIs are asynchronous and what to expect from them, read this article.
It prints the data because your ArrayList is being populated, but asynchronously, after you pass it to your Adapter.
The adapter holds a reference to the same ArrayList that is later getting populated via your Firebase callbacks, but the adapter itself needs to be notified using notifyDataSetChanged() when there is new data in the array, otherwise it won't check for it.
Basically, you need to find a way to notify your adapter of the dataset changing every time you get new data from Firebase. A simple mechanism to use would be a callback.
Related
This is my first time posting here so correct me if I am doing things the wrong way.
I am trying to learn Android development by trying to create a simple chatting application with Firebase Database.
Right now I am stuck on a proper implementation of loading smaller chunks of chats data. Here is what I have done so far.
Repository
public MutableLiveData<ArrayList<Chats>> loadPrivateChats(String withUid) {
roomId = generateUniqueRoomId(myId, withUid);
privateChatQuery =
firebaseDatabase.getReference().child(Constants.PRIVATE_CHAT_NODE)
.child(roomId).orderByChild("timeStamp").limitToLast(limit);
listenerPrivateChatQuery = new ChildEventListener() {
#Override
public void onChildAdded(#NonNull #NotNull DataSnapshot snapshot,
#Nullable #org.jetbrains.annotations.Nullable
String previousChildName) {
Chats mCurrentChats = snapshot.getValue(Chats.class);
privateChatsArrayList.add(mCurrentChats);
privateChatsLiveData.setValue(privateChatsArrayList);
loadMore.setValue(true);
}
....
privateChatQuery.addChildEventListener(listenerPrivateChatQuery);
return privateChatsLiveData;
}
public void loadOldChats(){
privateChatsArrayList.clear();
limit = limit+3;
loadMore.setValue(false);
}
Viewmodel
public MutableLiveData<ArrayList<Chats>> loadPrivateChats(String withUid) {
MediatorLiveData<ArrayList<Chats>> chatsMediatorLiveData = new MediatorLiveData<>();
LiveData<Boolean> loadMore = chatRepository.loadMore;
chatsMediatorLiveData.addSource(chatRepository.loadPrivateChats(withUid), new Observer<ArrayList<Chats>>() {
#Override
public void onChanged(ArrayList<Chats> chats) {
chatsMediatorLiveData.setValue(chats);
}
});
chatsMediatorLiveData.addSource(loadMore, new Observer<Boolean>() {
#Override
public void onChanged(Boolean aBoolean) {
if (!aBoolean) {
chatsArrayList.clear();
chatsArrayList = chatRepository.loadPrivateChats(withUid).getValue();
chatsMediatorLiveData.setValue(chatsArrayList);
}
}
});
return chatsMediatorLiveData;
}
public void loadMoreChats() {
chatRepository.loadOldChats();
}
Fragment
chatViewModel.loadPrivateChats(withUid).observe(getViewLifecycleOwner(),
new Observer<ArrayList<Chats>>() {
#Override
public void onChanged(ArrayList<Chats> chats) {
if (chats != null) {
privateChatAdapter.submitList(new ArrayList<>(chats));
int pos = chats.size() -1;
binding.recyclerViewPublicChat.smoothScrollToPosition(pos);
}
}
});
private void loadMoreChats() {
chatViewModel.loadMoreChats();
binding.swipeRefreshLayout.setRefreshing(false);
}
Now my problem is whenever a message is sent to another device few behavior arises intermittently (duplicate messages, messages showing in random order).
My recyclerview uses a List adapter
private static final DiffUtil.ItemCallback<Chats> DIFF_CALLBACK = new DiffUtil.ItemCallback<Chats>() {
#Override
public boolean areItemsTheSame(#NonNull Chats oldItem, #NonNull Chats newItem) {
return oldItem.timeStamp.equals(newItem.timeStamp);
}
#Override
public boolean areContentsTheSame(#NonNull Chats oldItem, #NonNull Chats newItem) {
return oldItem.message.equals(newItem.message) && oldItem.senderID.equals(newItem.senderID);
}
I would be very much grateful for any leads on a correct approach of loading data in smaller chunks.
thanks
My Firebase database is like that -
users -
first user ID
- name - "abc"
- image - "url"
- one_word - "abc"
following -
first user ID -
second User ID - "0"
Following node shows that First user is following second user.
Here is my code -
#Override
protected void onStart() {
super.onStart();
imageView.setVisibility(View.GONE);
FirebaseRecyclerAdapter<followers_following_class,following_Adapter>firebaseRecyclerAdapter =
new FirebaseRecyclerAdapter<followers_following_class, following_Adapter>
(
followers_following_class.class,
R.layout.find_friend_card,
following_Adapter.class,
databaseReference
) {
#Override
protected void populateViewHolder(final following_Adapter viewHolder, final followers_following_class model, int position) {
final String user_id = getRef(position).getKey();
users.child(user_id).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final String name = dataSnapshot.child("name").getValue().toString();
final String image = dataSnapshot.child("image").getValue().toString();
final String line = dataSnapshot.child("line").getValue().toString();
final String wins = dataSnapshot.child("one_word").getValue().toString();
viewHolder.setName(name);
viewHolder.setImage(following.this,image);
viewHolder.setLine(line);
viewHolder.setOne_word(wins);
if(getItemCount() == 0){
imageView.setVisibility(View.VISIBLE);
}
viewHolder.vieww.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(!user_id.equals(my_id)){
Intent intent = new Intent(following.this,Friend_profile_view.class);
intent.putExtra("user_id",user_id);
intent.putExtra("image",image);
intent.putExtra("one_word",wins);
intent.putExtra("name",name);
startActivity(intent);
}
}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
};
list.setAdapter(firebaseRecyclerAdapter);
}
public static class following_Adapter extends RecyclerView.ViewHolder {
View vieww;
public following_Adapter(View itemView) {
super(itemView);
this.vieww = itemView;
}
public void setImage( final following following, final String image) {
final CircleImageView circleImageView = (CircleImageView)vieww.findViewById(R.id.find_friend_profile_image_card);
if(!image.equals("default_image")) {
Picasso.with(following).load(image).networkPolicy(NetworkPolicy.OFFLINE).into(circleImageView, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError() {
Picasso.with(following).load(image).into(circleImageView);
}
});
}
}
public void setName(String name) {
TextView textView = (TextView)vieww.findViewById(R.id.find_friends_name_card);
textView.setText(name);
}
public void setLine(String line) {
ImageView imageView = (ImageView)vieww.findViewById(R.id.online_or_not);
if(line.equals("offline")){
imageView.setVisibility(View.INVISIBLE);
}
}
public void setOne_word(String wins) {
TextView textView = (TextView)vieww.findViewById(R.id.user_level);
textView.setText(wins);
}
}
Is there any way where i can apply firebase recycler adapter for one node but retrieve data form another node with same key without using addValueEventListener ?
And also most of my app uses firebase recyclerview in all activities so when i observed my android profiler , my RAM usage is increasing while switching between activities i have also used finish(); ended the addValuelistener in onDistroy method but it is still not working.
There are 3 eventListeners that you can use according to your needs, namely valueEventListener, childEventListener and singleValueEventListener.
This will be a good read for this, Firebase Docs.
When working with lists, your application should listen for child events rather than the value events used for single objects.
Child events are triggered in response to specific operations that happen to the children of a node from an operation such as a new child added through the push() method or a child being updated through the updateChildren() method. Each of these together can be useful for listening to changes to a specific node in a database.
In code, childEventListener looks like this:
ChildEventListener childEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());
// A new comment has been added, add it to the displayed list
Comment comment = dataSnapshot.getValue(Comment.class);
// ...
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());
// A comment has changed, use the key to determine if we are displaying this
// comment and if so displayed the changed comment.
Comment newComment = dataSnapshot.getValue(Comment.class);
String commentKey = dataSnapshot.getKey();
// ...
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());
// A comment has changed, use the key to determine if we are displaying this
// comment and if so remove it.
String commentKey = dataSnapshot.getKey();
// ...
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey());
// A comment has changed position, use the key to determine if we are
// displaying this comment and if so move it.
Comment movedComment = dataSnapshot.getValue(Comment.class);
String commentKey = dataSnapshot.getKey();
// ...
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "postComments:onCancelled", databaseError.toException());
Toast.makeText(mContext, "Failed to load comments.",
Toast.LENGTH_SHORT).show();
}
};
ref.addChildEventListener(childEventListener);
Also, retrieving data without the use of eventListeners is not possible. And if you want to listen to children of your one node, simultaneously, then childEventListener will be a great tool.
I am just having a problem when populating my list view. I think I know what's the problem since I tested it multiple times and it seems like the path for retrieving my data is actually the user store and not the bets but I don't seem to get where does the path come from. I was looking back at the methods and it seems that the path would be the DatabaseReference although my reference in the class is just standard
mDatabase = FirebaseDatabase.getInstance().getReference();
Here is my Adapter
public class CustomAdapter extends BaseAdapter{
Context c;
ArrayList<Bets> bets;
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); //Getting currently logged user
DatabaseReference mDatabase;
String hours;
String samount;
public CustomAdapter(Context c, ArrayList<Bets> bets) {
this.c = c;
this.bets = bets;
}
#Override
public int getCount() {
return bets.size();
}
#Override
public Object getItem(int pos) {
return bets.get(pos);
}
#Override
public long getItemId(int itemid) {
return itemid;
}
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
if(view == null)
{
view = LayoutInflater.from(c).inflate(R.layout.item_layout,viewGroup,false);
}
TextView condition = (TextView) view.findViewById(R.id.conditionList);
TextView place = (TextView) view.findViewById(R.id.placeList);
TextView amount = (TextView) view.findViewById(R.id.amountList);
final Bets bet = (Bets) this.getItem(position);
condition.setText(bet.getCondition());
place.setText(bet.getPlace());
String setamount = String.valueOf(bet.getAmount());
amount.setText(setamount);
hours = String.valueOf(bet.getHours());
samount = String.valueOf(bet.getAmount());
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
openDetailList(bet.getCreator(),bet.getCondition(),bet.getPlace(),hours,samount,bet.getJoined(),bet.getCreatorUid(),bet.getJoinedUid());
}
});
return view;
}
public void openDetailList(String...details)
{
Intent i = new Intent(c, ListDetail.class);
i.putExtra("CREATOR_KEY",details[0]);
i.putExtra("CONDITION_KEY",details[1]);
i.putExtra("PLACE_KEY",details[2]);
i.putExtra("HOURS_KEY",details[3]);
i.putExtra("AMOUNT_KEY",details[4]);
i.putExtra("JOINED_KEY",details[5]);
i.putExtra("CREATORUID_KEY",details[6]);
i.putExtra("JOINEDUID_KEY",details[7]);
c.startActivity(i);
}
}
and here is my FirebaseHelper
public class FirebaseHelper {
DatabaseReference db;
Boolean saved = null;
ArrayList<Bets> bets = new ArrayList<>();
public FirebaseHelper(DatabaseReference db) {
this.db = db;
}
public Boolean save(Bets bets)
{
if(bets == null)
{
saved = false;
}
else {
try {
db.child("bets").push().setValue(bets);
saved = true;
} catch (DatabaseException e) {
e.printStackTrace();
saved = false;
}
}
return saved;
}
private void fetchData(DataSnapshot dataSnapshot)
{
bets.clear();
for(DataSnapshot snapshot: dataSnapshot.getChildren())
{
Bets bet = snapshot.getValue(Bets.class);
bets.add(bet);
}
}
public ArrayList<Bets> retrieve()
{
db.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, "onComplete: Failed=" + databaseError);
}
});
return bets;
}
}
Saving/Inserting data works perfectly fine since I even have a sample data to show you after I added a bet but it doesn't populate the data.
{
"bets" :
{
"-LAzYM9SA-8Sg0YgGqnv" :
{
"amount" : 10,
"condition" : "It will be sunny",
"creator" : "Ginart",
"creatorUid" : "MxQPvCjUIkahVSZk1y2stdCxeY32",
"hours" : 2,
"joined" : "Free",
"joinedUid" : "",
"place" : "New York"
}
For some reason I think it reads the users data which is here
"users" : {
"EbYtfLUPs7Vu2rvnExaOaqJ4J883" : {
"balance" : 5,
"bets" : 1
},
Since every time I add a new user, the list view adds an Item to my list but with no data at all just my template and Android Studio shows me this
W/ClassMapper: No setter/field for balance found on class Models.Bets
W/ClassMapper: No setter/field for bets found on class Models.Bets
So it must be something wrong with the paths in my opinion. My Bets Model has no balance and bets variables.
Here is my OnCreate method where I'm using the retrieve() method in firebase
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_logged_in);
Money = (EditText) findViewById(R.id.Balance);
User = (EditText) findViewById(R.id.Player);
betList = (ListView) findViewById(R.id.BetList);
addbet = (FloatingActionButton) findViewById(R.id.AddBet);
addcreds = (TextView) findViewById(R.id.addCredits);
Money.setInputType(0);
User.setInputType(0);
betting = FirebaseDatabase.getInstance().getReference();
mDatabase = FirebaseDatabase.getInstance().getReference();
helper = new FirebaseHelper(betting);
mDatabase.child("users").child(current.getUid().toString()).child("balance").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
balance = dataSnapshot.getValue(Long.class);
String b = String.valueOf(balance);
Money.setText(b);
}
#Override
public void onCancelled(DatabaseError error) {
Money.setText("Error");
}
});
mDatabase.child("users").child(current.getUid().toString()).child("bets").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
bets = dataSnapshot.getValue(Long.class);
}
#Override
public void onCancelled(DatabaseError error) {
}
});
User.setText(current.getDisplayName().toString());
//ADAPTER
adapter = new CustomAdapter(this,helper.retrieve());
betList.setAdapter(adapter);
addbet.setOnClickListener(this);
addcreds.setOnClickListener(this);
}
Also, whenever I'm trying to change my reference to
betting = FirebaseDatabase.getInstance().getReference().child("bets");
I get this error
E/AndroidRuntime: FATAL EXCEPTION: main
Process: app.betme.betme, PID: 22814
com.google.firebase.database.DatabaseException: Can't convert object of type java.lang.Long to type Models.Bets
at com.google.android.gms.internal.zg.zzb(Unknown Source)
at com.google.android.gms.internal.zg.zza(Unknown Source)
at com.google.firebase.database.DataSnapshot.getValue(Unknown Source)
at Utils.FirebaseHelper.fetchData(FirebaseHelper.java:67)
at Utils.FirebaseHelper.access$000(FirebaseHelper.java:21)
at Utils.FirebaseHelper$1.onChildAdded(FirebaseHelper.java:81)
at com.google.android.gms.internal.px.zza(Unknown Source)
at com.google.android.gms.internal.vj.zzHX(Unknown Source)
at com.google.android.gms.internal.vp.run(Unknown Source)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
It's not error, It's just a waring from Firebase that you should have methods to set data for balance and bets
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'm making a social media app, where user will be able to comment on different posts, I'm successfully uploading and receiveing comments from firebase when 1 user comments only one time. If one user comments 2 or more times the comment row just overrides the previous comment. Below is my database structure
Here is my code to send data to firebase:
private void sendComment() {
final String message=myComment.getText().toString();
if (!TextUtils.isEmpty(message))
{
DatabaseComment.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
DatabaseComment.child(mPostKey).child(mAuth.getCurrentUser().getUid()).child("comments").setValue(message);
hDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
DatabaseComment.child(mPostKey).child(mAuth.getCurrentUser().getUid()).child("othersName")
.setValue(dataSnapshot.child(mAuth.getCurrentUser().getUid()).child("name").getValue());
DatabaseComment.child(mPostKey).child(mAuth.getCurrentUser().getUid()).child("othersDP")
.setValue(dataSnapshot.child(mAuth.getCurrentUser().getUid()).child("DP").getValue());
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
This is my Firebase Recycler Adapter in onStart method:
FirebaseRecyclerAdapter<Profile,ProfileViewHolder> firebaseRecyclerAdapter=new FirebaseRecyclerAdapter<Profile, ProfileViewHolder>(
Profile.class,
R.layout.commentttt_row,
ProfileViewHolder.class,
DatabaseCommenttttt
) {
#Override
protected void populateViewHolder(final ProfileViewHolder viewHolder, final Profile model, int position) {
viewHolder.setComments(model.getComments());
viewHolder.setOthersName(model.getOthersName());
viewHolder.setOthersDP(model.getOthersDP());
}
};
mProfileList.setLayoutManager(mLayoutManager);
mProfileList.setAdapter(firebaseRecyclerAdapter);
and this my ViewHolder Class:
public static class ProfileViewHolder extends RecyclerView.ViewHolder
{
View mView;
LinearLayout mLinearName;
TextView userName;
TextView mComment;
public ProfileViewHolder(View itemView) {
super(itemView);
mView=itemView;
userName=(TextView) mView.findViewById(R.id.post_name);
mLinearName=(LinearLayout) mView.findViewById(R.id.layout_name);
mComment=(TextView) mView.findViewById(R.id.comment);
}
public void setComments(String name)
{
TextView post_phone=(TextView) mView.findViewById(R.id.others_comments);
post_phone.setText(name);
}
public void setOthersName(String othersName) {
TextView post_name=(TextView) mView.findViewById(R.id.others_name);
post_name.setText(othersName);
}
public void setOthersDP(String othersDP) {
CircleImageView post_dp=(CircleImageView) mView.findViewById(R.id.othersaccountImageButton);
if (!othersDP.equals("default"))
Picasso.with(mView.getContext()).load(othersDP).into(post_dp);
}
}
This is happening because you are using setvalue() method in stead of using updateChildren() method. Because Firebase is a JSON database and is structured as paris of key and values, every node is a Map. So, in the case of Map, it replaces the old value with the new one.
To solve this, just use updateChildren() method in stead of setvalue() method. This is an example using a HashMap.
Map<String, Object> map = new HashMap<>();
map.put("/one-node/", oneStringValue);
map.put("/another-node/", anotherStringValue);
mDatabase.updateChildren(map);
Hope it helps.
You will have to set a listener to your database.
Arraylist<Comment> commentArray = new ArrayList<Comment>();
for (DataSnapshot postSnapshot: dataSnapshot.child("Comments").child(Userid).getChildren()) {
Comment individualComment = postSnapshot.getValue(Comment.class);
commentArray.add(individualComment);
}
This code will loop is supposed to loop through each comment and will save each comment node in the Comment class and save them into ArrayList
You should make another node inside of each user that commented. So the structure will be user key and then separate unique key per comment. Create a class that holds the comment and push that class inside of each individual user.
public class Comment() {
String name;
String othersDp;
String dP;
Comment(String n, String o, String d) {
name = n;
othersDp = o;
dP = d;
}
}
Initialize those variables and push this class inside your database:
Comment newComment = new Comments("Hello","BLA","BLA");
DatabaseComment
.child(mPostKey)
.child(mAuth.getCurrentUser().getUid())
.child("othersName")
.setValue(dataSnapshot.child(mAuth.getCurrentUser().getUid())
.push()
.setValue(newComments);