Merging Local Data With Firebase Database and showing it in recyclerview - java

I have a chatting application which is made using firebase. It works fine. But some users send slangs or stuffs like that. I use Firebase Recyclerview. My connection is direct to database. So I can't verify if the message is good or bad before data gets added.
What I want to do is, I want to send that data to some other location. Then after verification using cloud functions, I want to add it to original chat room database.
Till that step I have no problems. But when user sends a message. I dont want the user to wait until data gets updates. Cloud Functions takes 2-3 seconds for me. I want to add that data locally with a sending status and then update it when it gets updated on the chat room database.
So What's the best way to do that..
There is nothing wrong with my code. It's just basic firebase adapter..
Query query = FirebaseDatabase.getInstance()
.getReference()
.child("rooms").child("Off-Topic").child("chat")
.limitToLast(500);
firebaseOptions = new FirebaseRecyclerOptions.Builder<ChatData>()
.setQuery(query, ChatData.class)
.build();
chatAdapter = new FirebaseRecyclerAdapter<ChatData, ChatHolder>(firebaseOptions) {
#Override
protected void onBindViewHolder(ChatHolder holder, int position, ChatData model) {
}
#Override
public ChatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.sender_text_message, parent, false);
return new ChatHolder(v);
}
};

The simplest way for you to solve this would be to add a boolean variable to your ChatData class. This variable (let's call it isGood) would be false when the message is sent and would become true when it has been updated by your Cloud Function.
So your Adapter's onBindViewHolder would be:
#Override
protected void onBindViewHolder(ChatHolder holder, int position, ChatData model) {
if(model.isGood())
holder.textView.setText(model.getMessage()); //edit this line as needed
else
holder.textView.setText("sending..."); //replace textView with the name of your actual TextView
}

Related

How to get document id in In FirestorePagingAdapter?

I am trying to use the FirestorePagingAdapter to display a list of all users in my firestore database. I am using FirestorePagingAdapter instead of FirestoreRecyclerAdapter to minimize the number of reads as FirestorePagingAdapterdoesn't read the whole list of documents while FirestoreRecyclerAdapter does. I am able to display the paginated list successfully but I need to implement onClickListener onto it and on click of every item, I need to open another activity which shows a detailed description of the particular user which was clicked. For this, I need to pass the documentId of the clicked user to the next activity.
But unfortunately, FirestorePagingAdapter doesn't have getSnapshots() method so that I use getSnapshots().getSnapshot(position).getId().
On the other hand, FirestoreRecyclerAdapter has this method which makes fetching the document id a very easy task. Something like this: How to get document id or name in Android in Firestore db for passing on to another activity?
// Query to fetch documents from user collection ordered by name
Query query = FirebaseFirestore.getInstance().collection("users")
.orderBy("name");
// Setting the pagination configuration
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(10)
.setPageSize(20)
.build();
FirestorePagingOptions<User> firestorePagingOptions = new FirestorePagingOptions.Builder<User>()
.setLifecycleOwner(this)
.setQuery(query, config, User.class)
.build();
firestorePagingAdapter =
new FirestorePagingAdapter<User, UserViewHolder>(firestorePagingOptions){
#NonNull
#Override
public UserViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.single_user_layout, parent, false);
return new UserViewHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull UserViewHolder holder, int position, #NonNull User user) {
holder.setUserName(user.name);
holder.setStatus(user.status);
holder.setThumbImage(user.thumb_image, UsersActivity.this);
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent userProfileIntent = new Intent(UsersActivity.this, UserProfileActivity.class);
// Need to fetch the user_id to pass it as intent extra
// String user_id = getSnapshots().getSnapshot(position).getId();
// userProfileIntent.putExtra("user_id", user_id);
startActivity(userProfileIntent);
}
});
}
};
I was able to do this by using SnapshotParser in setQuery method. Through this, I was able to modify the object which I got from the firestore. The documentSnapshot.getId() method returns the document id.
FirestorePagingOptions<User> firestorePagingOptions = new FirestorePagingOptions.Builder<User>()
.setLifecycleOwner(this)
.setQuery(query, config, new SnapshotParser<User>() {
#NonNull
#Override
public User parseSnapshot(#NonNull DocumentSnapshot snapshot) {
User user = snapshot.toObject(User.class);
user.userId = snapshot.getId();
return user;
}
})
.build();
In the User class, I just added another field "String userId" in the User class. The userId field doesn't exist in my firestore document.
In the onClickListener, I can then directly use user.userId to get the document id and send it to other activity.
While trying to access the document snapshot from itemView, I found out this
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int pos = getAdapterPosition();
if (pos != RecyclerView.NO_POSITION && listener != null) {
String docId = getItem(pos).getId();
Toast.makeText(context, "doc Id: "+docId, Toast.LENGTH_SHORT).show();
//listener.onItemClick(getSnapshots().getSnapshot(pos), pos, docId);
listener.onItemClick(getItem(pos), pos, docId);
}
}
});
As mentioned here, the getItem() returns the item's data object.
As you already noticed:
String id = getSnapshots().getSnapshot(position).getId();
Doesn't work, it works only when using FirestoreRecyclerAdapter. So to solve this, you need to store the id of the document as property of your document. If the id of the document is the id of the user that comes from the Firebase autentication, then simply store that uid. If you are not using the uid, get the id of the document and pass it to the User constructor when creating a new object like this:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
CollectionReference usersRef = rootRef.collection("users");
String id = eventsRef.document().getId();
User user = new User(id, name, status, thumb_image);
usersRef.document(id).set(user);
I spent a day trying to get the id of the document as I am now using the FirestorePagingAdapter. For Kotlin this is what worked for me
override fun onBindViewHolder(viewHolder: LyricViewHolder, position: Int, song: Lyric) {
// Bind to ViewHolder
viewHolder.bind(song)
viewHolder.itemView.setOnClickListener { view ->
val id = getItem(position)?.id
var bundle = bundleOf("id" to id)
view.findNavController().navigate(R.id.songDetailFragment, bundle)
}
}
Hope this helps someone else in the near future. Can post full code and fully explain if anyone is confused. Happy coding!
Try using this
getSnapshots().getSnapshot(position).getId()

Unexpected Behavior When Query by .whereArrayContains()

I have a RecyclerView that utilizes the FireaseUI and orders all objects from the "Polls" node by the "timestamp" field (sequentially).
New Fragment - .onViewCreated()
Query queryStore = FirebaseFirestore.getInstance()
.collection(POLLS_LABEL)
.orderBy("timestamp", Query.Direction.ASCENDING);
//Cloud Firestore does not have any ordering; must implement a timestampe to order sequentially
FirestoreRecyclerOptions<Poll> storeOptions = new FirestoreRecyclerOptions.Builder<Poll>()
.setQuery(queryStore, Poll.class)
.build();
mFirestoreAdaper = new FirestoreRecyclerAdapter<Poll, PollHolder>(storeOptions) {
#Override
protected void onBindViewHolder(#NonNull final PollHolder holder, final int position, #NonNull Poll model) {
holder.mPollQuestion.setText(model.getQuestion());
String voteCount = String.valueOf(model.getVote_count());
//TODO: Investigate formatting of vote count for thousands
holder.mVoteCount.setText(voteCount);
Picasso.get()
.load(model.getImage_URL())
.fit()
.into(holder.mPollImage);
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent toClickedPoll = new Intent(getActivity(), PollHostActivity.class);
String recyclerPosition = getSnapshots().getSnapshot(position).getId();
Log.v("Firestore ID", recyclerPosition);
toClickedPoll.putExtra("POLL_ID", recyclerPosition);
startActivity(toClickedPoll);
}
});
}
I have another layout in my app that subscribes to this same node, but instead queries by "followers" and then by "timestamp.:
Following Fragment - .onViewCreated()
Query queryStore = FirebaseFirestore.getInstance()
.collection(POLLS_LABEL)
.whereArrayContains("followers", mUserId)
.orderBy("timestamp", Query.Direction.ASCENDING);
FirestoreRecyclerOptions<Poll> storeOptions = new FirestoreRecyclerOptions.Builder<Poll>()
.setQuery(queryStore, Poll.class)
.build();
mFirestoreAdaper = new FirestoreRecyclerAdapter<Poll, PollHolder>(storeOptions) {
#Override
protected void onBindViewHolder(#NonNull final PollHolder holder, final int position, #NonNull Poll model) {
holder.mPollQuestion.setText(model.getQuestion());
String voteCount = String.valueOf(model.getVote_count());
//TODO: Investigate formatting of vote count for thousands
holder.mVoteCount.setText(voteCount);
Picasso.get()
.load(model.getImage_URL())
.fit()
.into(holder.mPollImage);
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent toClickedPoll = new Intent(getActivity(), PollHostActivity.class);
String recyclerPosition = getSnapshots().getSnapshot(position).getId();
Log.v("Firestore ID", recyclerPosition);
toClickedPoll.putExtra("POLL_ID", recyclerPosition);
startActivity(toClickedPoll);
}
});
}
In the first scenario, UI items populate, in real-time, into my RecyclerView as they are added to Firebase. However, when I query by ".whereArrayContains," I do not get this same behavior, and I was curious as to why. The items only reappear when I restart the application:
Edit:
I commented out the below line:
// .whereArrayContains("followers", mUserId)
and the behavior performed as expected, therefore I can isolate the issue to the .whereArrayContains() query. It is the only difference between each Fragment.
This is happening because when you are using whereArrayContains() and orderBy() methods in the same query, an index is required. To use one, go to your Firebase Console and create it manually or if you are using Android Studio, you'll find in your logcat a message that sounds like this:
W/Firestore: (0.6.6-dev) [Firestore]: Listen for Query(products where array array_contains YourItem order by timestamp) failed: Status{code=FAILED_PRECONDITION, description=The query requires an index. You can create it here: ...
You can simply click on that link or copy and paste the url into a web broswer and you index will be created automatically.
Why is this index needed?
As you probably noticed, queries in Cloud Firestore are very fast and this is because Firestore automatically creates an indexes for any fields you have in your document. When you need to order your items, a particular index is required that should be created as explained above. However, if you intend to create the index manually, please also select from the dropdown the corresponding Array contains option, as in the below image:

How does Android Paging Library know to load more data?

Iʼm fairly new to developing Android apps and Iʼm trying to do everything “the right way.” So right now, Iʼm implementing the new Android Paging Library into my project, where I need to load a list of articles from a network server.
I have an ArticlesRepository class that returns an ArticleList class containing instances of ArticleListItem that I would like to display in a RecyclerView. The list of articles is paginated already on the server, so the repository sends a request for the first page and returns an ArticleList with the page property set to 1 and the articles property containing a List<ArticleListItem> of articles on the requested page. I donʼt know how many articles can be on one page.
Now, I was able to implement a PageKeyedDataSource<Integer, ArticleListItem>, but it only fetches the first page:
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, ArticleListItem> callback) {
ArticleList list = load(1);
if (list != null) {
callback.onResult(list.articles, null, next(list));
}
}
#Override
public void loadBefore(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, ArticleListItem> callback) {
ArticleList list = load(previous(params.key));
if (list != null) {
callback.onResult(list.articles, previous(list));
}
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, ArticleListItem> callback) {
ArticleList list = load(next(params.key));
if (list != null) {
callback.onResult(list.articles, next(list));
}
}
The previous/next functions return an Integer with the previous/next page number or null if there isnʼt one.
In my ViewModel, I configure the PagedList like this:
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(1)
.setPageSize(1)
.setPrefetchDistance(1)
.build();
This way Iʼm able to load the first page, but when I scroll to the bottom of the RecyclerView (that is inside a NestedScrollView), nothing happens. Debugging shows that the PageKeyedDataSource.loadAfter method is not invoked.
Do I have to somehow tell the PagedList that the next page has to be loaded, or is it the RecyclerView/DataSource/GodKnowsWhatElseʼs job and Iʼm just doing something wrong? Thanks for any advice.
The paging library should know automatically when to load new items. The problem in your implementation is that the paged RecyclerView is inside a NestedScrollView and according to this issue the libary doesn't have built in support for that.
when you put recyclerview inside an infinite scrolling parent, it will
layout all of its children because the parent provides infinite
dimensions.
You'll need to create your own implementation of Nested Scroll View, there is actually one here in this gist that might be able to help you.
It is also suggested to add fillViewPort to this custom nested scroll view:
android:fillViewport="true" to scrollable container

Handling positions in FirebaseRecyclerAdapter

I have fiddled with the FirebaseRecyclerAdapter for quite some time now. It's really a great tool to populate a custom list/recycler view very fast with all of its features. However, one thing that I would like to ask is how to handle positions of items inside the adapter itself.
So for example, I want to mimic this small feature that WhatsApp has in their chats.
So, in a group chat setting, if a person sends more than one consecutive message in a row, the display name of that particular person will be invisible.
The logic behind it according to my understanding: if the person who sends the message is the same for (position - 1), then I will just make the EditText invisible for (position). This is, of course, to prevent a very long stream of text with minimum amounts of repetitive information.
Let's say the JSON tree from Firebase database is as follows.
{
"messages" : {
"pushed_id_1" : {
"message_sender" : "AppleJuice",
"message_text" : "Are you free?"
},
"pushed_id_2" : {
"message_sender" : "AppleJuice",
"message_text" : "On Saturday I mean..."
}
}
}
The FirebaseRecyclerAdapter would look like this.
FirebaseRecyclerAdapter<Message, MessageViewHolder> adapter = new FirebaseRecyclerAdapter<Message, MessageViewHolder>(Message.class, R.layout.message_item, MessageViewHolder.class, myRef) {
#Override
protected void populateViewHolder(MyBookingsViewHolder viewHolder, Booking model, int position) {
viewHolder.messageSender.setText(model.getMessage_sender());
viewHolder.messageText.setText(model.getMessage_text());
//put some code here to implement the feature that we need
}
};
messages_recycler_menu.setAdapter(adapter);
The furthest I have gone is to use getItemCount() method in the FirebaseRecyclerAdapter, but I am still unable to achieve the feature that mimics that of Whatsapp's that I was talking about previously.
Is there a method that can achieve this? Or am I missing something very important in this example?
String lastSender=null; //or some random string
FirebaseRecyclerAdapter<Message, MessageViewHolder> adapter =
new FirebaseRecyclerAdapter<Message, MessageViewHolder>
(Message.class, R.layout.message_item, MessageViewHolder.class, myRef) {
#Override
protected void populateViewHolder(MyBookingsViewHolder viewHolder, Booking model, int position) {
if (model.getMessage_sender().equals(lastSender){ //check if the current sender is same as the last sender
viewHolder.messageText.setText(model.getMessage_text()); //setting only message text
viewHolder.messageSender.setVisibility(View.GONE); //if required
}else{
lastSender=model.getMessage_sender();//updating the lastSender value
viewHolder.messageSender.setVisibility(View.VISIBLE); //if required
viewHolder.messageSender.setText(model.getMessage_sender());
viewHolder.messageText.setText(model.getMessage_text());
}
//put some code here to implement the feature that we need
}
};
messages_recycler_menu.setAdapter(adapter);
As discussed in comments:
Let's suppose I received message and stored sender's name in constant String that should be static constant in some class i.e. AppConstants so that It can be accessed everywhere therefore after that:
in populateViewHolder or in your message receiver do something like this:
if (TextUtils.isEqual(storedSender,model.getMessage_sender())){
viewHolder.messageSender.setVisiblity(View.GONE)
}
else{
// do your normal flow
viewHolder.messageSender.setVisiblity(View.VISIBLE);
storedSender = model.getMessage_sender();
}
In this way automatically the last message's sender's name will be updated , this is exactly what you were trying to achieve by adapter position!

How do you view data from Azure's easy tables and view it in a list view?

Hi i seem to be forever looking for an example of how to insert and retrieve data from Azure. I have managed to insert data into the azure easy table (happy days).I want to know how to retrieve that data and display it in a list view or even an alertDialog builder for all i care just need a way to view the data in my app.
using the code below i have managed to enter some data into the azure database.
public void saveToAzure(){
button_save_to_azure = (Button)findViewById(R.id.btnSaveDataToAzure);
button_save_to_azure.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
myAzuretbl.SEEDNAME = edittext_seed_name_for_azure.getText().toString();
mClient.getTable(Azuretbl.class).insert(myAzuretbl, new TableOperationCallback<Azuretbl>() {
#Override
public void onCompleted(Azuretbl entity, Exception exception, ServiceFilterResponse response) {
if (exception == null) {
// Insert succeeded
Toast myToast = Toast.makeText(getApplicationContext(),"Inserted", Toast.LENGTH_LONG);
myToast.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.CENTER_VERTICAL,0,0);
myToast.show();
edittext_seed_name_for_azure.setText("");
} else {
// Insert failed
Toast myFailToast = Toast.makeText(getApplicationContext(),"Not Inserted", Toast.LENGTH_LONG);
myFailToast.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.CENTER_VERTICAL,0,0);
myFailToast.show();
edittext_seed_name_for_azure.setText("");
}
}
});
}
}
);
}
At the moment i can enter 1 field into the database and i know how to enter more. I would like to now retrieve this data.
my local azure table looks like this at the moment:
package com.jonnyg.gardenapp;
public class Azuretbl {
public String Id;
public String SEEDNAME;
}
nothing special but it does the job.
I have looked at the documentation and none of it makes sense to me.looking at the new quick start guide and then looking at the documentation are completely different.
from the way i am doing it here is there a follow up in retriving the data and viewing it in either a list view or alertDialog builder?.
#JonnyG,
Could you please try to use the excute() method and refer to SDK document (https://github.com/Azure/azure-mobile-services/blob/master/sdk/android/src/sdk/src/main/java/com/microsoft/windowsazure/mobileservices/table/MobileServiceTable.java )?
Generally, we can retrieve the table rows as following:
MobileServiceList<Azuretbl> result =mClient.getTable(Azuretbl.class).execute().get();
for(Azuretbl item:result)
{
//your code
}
Also, you can check this official document sample(https://azure.microsoft.com/en-us/documentation/articles/mobile-services-android-how-to-use-client-library/#querying)
Hope this helps.

Categories

Resources