I was trying to search through the Firebase database from my material search dialog. I was successful in doing that but the problem is that I can search only through titles(my database contains title and descriptions too), although I tried quite a many things nothing worked for me.
At some point, I succeeded by making another search function for searching descriptions but then it made the searched results for titles to misbehave.
I am attaching the code below can, please have a look at it and let me know if there is anything that I can do to search through both titles and descriptions.
Declarations and code(I have skipped the unrequired parts from the activity code)
materialSearchBar = homeView.findViewById(R.id.searchBar);
materialSearchBar.setHint("Search Ad");
loadSuggest();
materialSearchBar.setLastSuggestions(suggestList);
materialSearchBar.setCardViewElevation(10);
materialSearchBar.addTextChangeListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
List<String> sugest = new ArrayList<String>();
for (String search : suggestList) {
if (search.toLowerCase().contains(materialSearchBar.getText().toLowerCase()))
sugest.add(search);
}
materialSearchBar.setLastSuggestions(sugest);
}
#Override
public void afterTextChanged(Editable editable) {
}
});
materialSearchBar.setOnSearchActionListener(new MaterialSearchBar.OnSearchActionListener() {
#Override
public void onSearchStateChanged(boolean enabled) {
if (!enabled)
mallUsers.setAdapter(firebaseRecyclerAdapter);
}
#Override
public void onSearchConfirmed(CharSequence text) {
startSearch(text);
}
#Override
public void onButtonClicked(int buttonCode) {
}
});
// start search and load suggestions methods
private void startSearch(CharSequence text) {
String searchText = text.toString();
Query query1 = mDatabase1.orderByChild("title").startAt(searchText).endAt(searchText + "\uf8ff");
Query query2 = mDatabase1.orderByChild("description").startAt(searchText).endAt(searchText + "\uf8ff");
FirebaseRecyclerOptions<Ad> firebaseRecyclerOptions2 = new FirebaseRecyclerOptions
.Builder<Ad>()
.setQuery(query1, Ad.class)
.build();
searchAdapter = new FirebaseRecyclerAdapter<Ad, UsersViewHolder1>(firebaseRecyclerOptions2) {
#Override
protected void onBindViewHolder(#NonNull UsersViewHolder1 holder, int position, #NonNull Ad ad1) {
holder.setTitle(ad1.getTitle());
holder.setPrice(ad1.getPrice());
holder.setCategory(ad1.getCategory());
holder.setImage(ad1.getImage(), getContext());
holder.setTime(ad1.getTime());
String user_id = getRef(position).getKey();
final String kk = user_id.toString();
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent mew = new Intent(getActivity(), ExpandActivity.class);
mew.putExtra("user_id", kk);
startActivity(mew);
}
});
}
#NonNull
#Override
public UsersViewHolder1 onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view1 = LayoutInflater.from(parent.getContext()).inflate(R.layout
.user_ad_layout, parent,
false);
return new UsersViewHolder1(view1);
}
};
searchAdapter.startListening();
mallUsers.setAdapter(searchAdapter);
}
private void loadSuggest() {
mDatabase1.orderByKey().addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot postdataSnapshot : dataSnapshot.getChildren()) {
Ad item = postdataSnapshot.getValue(Ad.class);
if (item != null) {
suggestList.add(item.getTitle());
suggestList.add(item.getDescription());
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
According to this post, there is no way in which you can pass two queries to a single adapter. So you can pass either query1 or query2.
Unfortunately, Firebase Realtime database does not support queries on multiple properties (some people say "multiple where clauses" in SQL terms), supports only queries on a single child property. So you'll need to create an extra field to keep both fields. So to achieve this, you need to create a new field which in your database should look like this:
Firebase-root
|
--- itemId
|
--- title: "valueOfTitle"
|
--- description: "valueOfDescription"
|
--- title_description: "valueOfTitle_valueOfDescription"
So as you see, the title_description property combines the values that you want to filter on. But Firebase real-time database doesn't support native indexing or search for text fields in objects. Additionally, downloading an entire mode to search for fields client-side isn't practical. However, you can do it for small data sets but as I said before is not practical for large data sets. To enable full text search of your Firebase real-tme database, I recommend you to use a third-party search service like Algolia.
Unlike Firebase Realtime database, Cloud Firestore allows compound queries. You should take a look at this. So a query as the one below is allowed in Cloud Firestore without creating a combined property.
itemIdRef.whereEqualTo("title", "valueOfTitle").whereEqualTo("description", "valueOfDescription");
If you want to use Algolia in Cloud Firestore, I recommend you see my answer from this post. For more information, I also recommend you see this video.
You have already experienced the lowest feature of Firebase, querying. However you can query multiple values if you for example upload on firebase both title and description like this:
user:
post:
title: "cake"
description: "yummy"
titledescription: "cake_yummy"
something like this.
Another thing you can do is
private boolean hasValue = false;
private boolean hasValue1 = false;
set value is true inside onBindViewHolder and then using handler delay query values one at a time:
Handler myHandler1 = new Handler();
Runnable myRunnable1 = new Runnable() {
#Override
public void run() {
// query1 here
}
};
Handler myHandler2 = new Handler();
Runnable myRunnable2 = new Runnable() {
#Override
public void run() {
// query2 here
}
};
then you check them one at a time until it finds a value
if(!hasValue){
myHandler1.postDelayed(myRunnable1, 1000);
if(!hasValue1){
myHandler2.postDelayed(myRunnable2, 1500);
} else {
myHandler1.removeCallbacks(myRunnable1);
hasValue1 = false;
} else {
myHandler2.removeCallbacks(myRunnable2);
hasValue2 = false;
}
}
Hope it helps.
Related
I am trying to filter matches on the basis of team Name using a Spinner. But the problem is it is only filtering with the name of team1 only not with team2.
In the above Image MI and CSK both are different teams. Here is the code I am using for filtering.
filter.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (parent.getItemAtPosition(position).equals("Sort by Teams")) {
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
matchInfoList.clear();
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
MatchInfo info = dataSnapshot.getValue(MatchInfo.class);
matchInfoList.add(info);
Log.d("TAG", "onDataChange: " + matchInfoList.size());
bar.setVisibility(View.INVISIBLE);
}
adapter = new ScheduleAdapter(ScheduleActivity.this, matchInfoList);
recyclerView.setAdapter(adapter);
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
} else {
Query query = databaseReference.orderByChild("bothTeams").startAt(filter.getSelectedItem().toString())
.endAt(filter.getSelectedItem().toString() + "\uf8ff");
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
matchInfoList.clear();
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
MatchInfo info = dataSnapshot.getValue(MatchInfo.class);
matchInfoList.add(info);
bar.setVisibility(View.INVISIBLE);
}
adapter = new ScheduleAdapter(ScheduleActivity.this, matchInfoList);
recyclerView.setAdapter(adapter);
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
My Spinner has
List<String> teams = new ArrayList<>();
teams.add("Sort by Teams");
teams.add("MI");
teams.add("CSK");
Now If I filter it with MI it works fine but on CSK being selected, it shows nothing.
SO, what changes should I make in code? Any help will be much appreciated.
Firebase Realtime Database can only filter on prefix matches, so on strings that start with a certain value. There is no way to filter for strings that end with (or contain) a certain values. Since this has been asked quite a few times before, I recommend you check out some of these answers:
Firebase query - Find item with child that contains string
How to perform sql "LIKE" operation on firebase?
Firebase string match at end of field
To accomplish your use-case, you will need to either keep two properties (team1, team2) and perform a separate query for each property, or keep a separate data structure that maps each team to its matches. Something like:
"teamMatches": {
"MI": {
"-MHSZ...": true
},
"CSK": {
"-MHSZ...": true
},
}
You can then use this structure (often called an inverted index) to find the game IDs for the team, and then load those games. This type of lookup is not nearly as slow as you may initially think, as Firebase pipelines the requests over a single connection.
Basically my problem is that I had stored in firebase db a list of users with multiple attributes, some of them were private informations so I wanted to deny access to them. Rules aren't the answer because they can't be used as filters.
My solution was to create two new attributes for "users": "pvt" (private) and "pb" (public) and to store inside of them the correct attributes.
To create new users I used first:
mDatabase.child("users").child(prefs.getString("id firebase", " ")).child("public").setValue(newUserPublic);
mDatabase.child("users").child(prefs.getString("id firebase", " ")).child("private").setValue(newUserPrivate);
where newUserPublic and newUserPrivate are objects of simple custom classes that offers getters and setters for user's attributes (one for public and the other for private informations).
My final goal was to create a leaderboard that uses only public attributes of each user but I wadn't able to create a proper ListAdapter with this configuration.
My final try was to create a new class called User
public class User {
public UserDataPrivate getPvt() {
return pvt;
}
public void setPvt(UserDataPrivate pvt) {
this.pvt = pvt;
}
public UserDataPublic getPb() {
return pb;
}
public void setPb(UserDataPublic pb) {
this.pb = pb;
}
private UserDataPrivate pvt;
private UserDataPublic pb;
public void setId(String id) {
this.id = id;
}
private String id;
public User(String id, UserDataPublic pb, UserDataPrivate pvt){
this.pvt=pvt;
this.pb=pb;
this.id=id;
}
}
to create new user with:
User user = new User(newUserPublic, newUserPrivate);
mDatabase.child("users").child(prefs.getString("id firebase", " ")).setValue(user);
and the current adapter is
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference("users");
Query query = mDatabase.child("pb").orderByChild("points");
ListAdapter adapter = new CustomFirebaseAdapter<User>(this, User.class, R.layout.list_item, query) {
#Override
protected void populateView(View v, User model, int position) {
//code that uses model.getPb()
}
};
but it doesn't work (//code is never executed).
Do you have any idea how I can solve this?
This is a test user in Firebase:
EDIT:
Tried to user pb/score inside the query but it crashes.
I think the problem is that firebase can't handle complex objects or I'm missing something. I have other code portions wich retrive single user data so I use
final DatabaseReference userDatabase = mDatabase.child("users").child(prefs.getString("id firebase", " "));
userDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
//...
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
and it crashes when setting user. I thought that if I save users in my db using user objects I could retrive them in the same way but I'm probably wrong.
When you call orderByChild() on a location, Firebase takes each node directly under that location and orders it on the child you specify. Since you call orderByChild("points") on /users, it takes the nodes under /users and orders them on their (non-existing) points property.
To order each user on their pb/points property, use that path in the call to orderByChild(). So:
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference("users");
Query query = mDatabase.orderByChild("pb/points");
ListAdapter adapter = new CustomFirebaseAdapter<User>(this, User.class, R.layout.list_item, query) {
#Override
protected void populateView(View v, User model, int position) {
//code that uses model.getPb()
}
};
This code:
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference("users");
Query query = mDatabase.child("pb").orderByChild("points");
will point to /users/pb and order by users/pb/??/points. If you seek for query that point to /users/?? order by users/??/pb/points, then I think Firebase can't do that for now. Check this post
You can use a firebase listview adapter and that does a lot of the work for you. But you might want to change your user objects.
Query query = FirebaseDatabase.getInstance().getReference("users").orderByChild("pb/points");
FirebaseListAdapter adapter = new FirebaseListAdapter(activity, User.class, R.id.modelLayout, query) {
#Override
protected void populateView(View view, User user, int i) {
//view is current view
//user is current user
//i is position
TextView points = view.findViewById(R.id.pointsTextView);
String userPoints = user.getPb().getPoints();
points.setText(userPoints);
}
};
Then apply the adapter on a view
listview.setAdapter(adapter);
I have wandered YouTube and Stack Overflow in search of way to retrieve the teacherName value from the following database:
I have not yet found a solution for my problem, whether I use a Value or ChildEventListener. This is the code I'm using:
public class ViewTeachersActivity extends AppCompatActivity {
// Define the Teacher Firebase DatabaseReference
private DatabaseReference databaseTeachers;
// Define a String ArrayList for the teachers
private ArrayList<String> teachersList = new ArrayList<>();
// Define a ListView to display the data
private ListView listViewTeachers;
// Define an ArrayAdapter for the list
private ArrayAdapter<String> arrayAdapter;
/**
* onCreate method
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_teachers);
// Associate the Teacher Firebase Database Reference with the database's teacher object
databaseTeachers = FirebaseDatabase.getInstance().getReference();
databaseTeachers = databaseTeachers.child("teachers");
// Associate the teachers' list with the corresponding ListView
listViewTeachers = (ListView) findViewById(R.id.list_teachers);
// Set the ArrayAdapter to the ListView
arrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, teachersList);
listViewTeachers.setAdapter(arrayAdapter);
// Attach a ChildEventListener to the teacher database, so we can retrieve the teacher entries
databaseTeachers.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
// Get the value from the DataSnapshot and add it to the teachers' list
String teacher = dataSnapshot.getValue(String.class);
teachersList.add(teacher);
// Notify the ArrayAdapter that there was a change
arrayAdapter.notifyDataSetChanged();
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
I have also tried using a for loop inside the ChildEventListener, but that also didn't work. Can anyone point me to a solution?
String teacher = dataSnapshot.getValue(String.class);
You don't have a String there. You have an object.
So, you need a Java class
public class Teacher {
String teacherID, teacherEmail; // etc. all fieldnames in the database
public Teacher() {
}
// getters
// setters
#Override
public String toString() {
return this.teacherID + ": " + this.teacherEmail;
}
}
Then from Firebase, you can map the snapshot to that class and add to an adapter.
Teacher teacher = (Teacher) dataSnapshot.getValue(Teacher.class);
String teacherString = String.valueOf(teacher);
arrayAdapter.add(teacherString);
Refer: Firebase-UI | Using Firebase to populate ListView
Since your question is unclear if you want to listen for all teacherNames or just one.
This is an easy way to get all 'teacherName' for every entry (uuid key) under /teachers and adds a listener to this query:
FirebaseDatabase.getInstance()
.getReference()
.child("teachers")
.orderByChild("teacherName")
.addValueEventListener(listener); //listener example below
ValueEventListener listener = new new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
dataSnapshot.getChildren().forEach(
// assuming you are using RetroLambda, if not, implement Consumer<? super T> action
// update change ListView here
);
}
#Override
public void onCancelled(FirebaseError firebaseError)
{
//handle errors here
}
});
If you are looking for getting a specific teacher name, comment and I'll change it. Also, I haven't tested syntax as I'm on mobile, so treat this as pseudocode but it should get you going in the right direction
Check out the Firebase Query documentation, it is really straightforward and as of now can do really powerful stuff in a few lines of code. Be careful of outdated tutorials when searching, Firebase has added allot of its functionality in the last ~1 year
Simple thing I would like to do (see in the picture)
Display a view with info coming from 2 different places in Firebase so that it behaves in a professional way scrolling UP and DOWN
I have a list of movies and on each of them I would like the user to specify a rating and see it
In DB I created 2 structures to have the list of movies on one side and the ratings per user on the other
Problem using FirebaseRecyclerAdapter
My problem is that scrolling fast up and down the list, the visualization of the information coming from the second reference (the rating) is loaded on a different time (asynchronous call) and this is not acceptable to see this (little) delay building the view. Is this a limitation of FirebaseRecyclerView?
Because viewHolders are reused in the recycleView I reset and reload each time in populateView() the rating values and this doesn't help. Once retrieved I'm oblidged to get them again if the user scroll the view (see the setOnlistener in populateView()
Setting a listener in populateView cause also to have as many listener as the number of times populateView() is executed (if you scroll UP and DOWN it's many times).
Solutions / Workaround ?
Is there a correct way to do it preventing the problem? Or is it a limitation?
What about performance with my implementation where the listener is inside populateView() and there are MANY listener created?
Below some things I'm thinking on:
Prevent viewHolders to be recycled and just load once?
Override some other methods of RecyclerView? I tried with parseSnapshot() but it's the same problem...
Change the DB structure to have all the info in one list (I don't think it's the good one because it means adding rating information of each user to movie list)
Add a loading spinner on the rating part so that the rating is displayed only when the asyncrhonous call to firebase is completed (don't like it) without the today effect of: "changing star color in front of the user".
My Implementation
From FirebaseRecyclerAdapter
#Override
protected void populateViewHolder(final MovieViewHolder viewHolder, final Movie movie, final int position) {
String movieId = this.getRef(position).getKey();
// Oblidged to show no rating at the beginning because otherwise
// if a viewHolder is reused it has the values from another movie
viewHolder.showNoRating();
//---------------------------------------------
// Set values in the viewHolder from the model
//---------------------------------------------
viewHolder.movieTitle.setText(movie.getTitle());
viewHolder.movieDescription.setText(movie.getDescription());
//-----------------------------------------------------
// Ratings info are in another DB location... get them
// but call is asynchronous so PROBLEM when SCROLLING!
//-----------------------------------------------------
DatabaseReference ratingMovieRef = mDbRef.child(Constants.FIREBASE_LOCATION_RATINGS).child(currentUserId).child(movieId);
ratingQuoteRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
RatingMovie ratingMovie = dataSnapshot.getValue(RatingMovie.class);
Rating rating = Rating.NO_RATING;
if (ratingMovie != null) {
rating = Rating.valueOf(ratingMovie.getRating());
}
// Set the rating in the viewholder (through anhelper method)
viewHolder.showActiveRating(rating);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
from MovieViewHolder
public class QuoteViewHolder extends RecyclerView.ViewHolder {
public CardView cardView;
public TextView movieTitle;
public TextView movieDescription;
public ImageView ratingOneStar;
public ImageView ratingTwoStar;
public ImageView ratingThreeStar;
public QuoteViewHolder(View itemView) {
super(itemView);
movieTitle = (TextView)itemView.findViewById(R.id.movie_title);
movieDescription = (TextView)itemView.findViewById(R.id.movie_descr);
// rating
ratingOneStar = (ImageView)itemView.findViewById(R.id.rating_one);
ratingTwoStar = (ImageView)itemView.findViewById(R.id.rating_two);
ratingThreeStar = (ImageView)itemView.findViewById(R.id.rating_three);
}
/**
* Helper to show the color on stars depending on rating value
*/
public void showActiveRating(Rating rating){
if (rating.equals(Rating.ONE)) {
// just set the good color on ratingOneStar and the others
...
}
else if (rating.equals(Rating.TWO)) {
// just set the good color
...
} else if (rating.equals(Rating.THREE)) {
// just set the good color
...
}
/**
* Initialize the rating icons to unselected.
* Important because the view holder can be reused and if not initalised values from other moviecan be seen
*/
public void initialiseNoRating(){
ratingOneStar.setColorFilter(ContextCompat.getColor(itemView.getContext(), R.color.light_grey));
ratingTwoStar.setColorFilter(....
ratingThreeStar.SetColorFilter(...
}
You can sort of cache the ratings using a ChildEventListener. Basically just create a separat one just for the Ratings node, and have it store the ratings in a Map. Then using the RecyclerAdapter you will retrieve from the Map if the rating is available, if it is not, have the rating listener update the recyclerview as soon as is has downloaded the rating. This is one strategy you could go about, doing it, you will have to manually copy/paste some classes from the FirebaseUI library and set some fields public for this to work.
Usage would be something like this
private MovieRatingConnection ratingConnection;
// inside onCreate
ratingConnection = new MovieRatingConnection(userId, new MovieRatingConnection.RatingChangeListener() {
#Override
public void onRatingChanged(DataSnapshot dataSnapshot) {
if (recyclerAdapter != null) {
if (dataSnapshot != null) {
int index = recyclerAdapter.snapshots.getIndexForKey(dataSnapshot.getKey());
recyclerAdapter.notifyItemChanged(index);
}
}
}
});
Query movieQuery = FirebaseDatabase.getInstance().getReference().child("Movies");
recyclerAdapter = new FirebaseRecyclerAdapter(movieQuery...) {
#Override
public void populateViewHolder(RecyclerView.ViewHolder viewHolder, Object model, int position) {
//...
final String key = getRef(position).getKey();
viewHolder.showActiveRating(ratingConnection.getRating(key));
}
};
and MovieRatingConnection would be a class like this
public class MovieRatingConnection {
private MovieRatingListener listener;
public MovieRatingConnection(String userId, RatingChangeListener changeListener) {
Query query = FirebaseDatabase.getInstance().getReference().child("MovieRatings").child(userId);
listener = new MovieRatingListener(query, changeListener);
}
public Rating getRating(String key) {
return listener.getRating(key);
}
public void cleanup() {
if (listener != null) {
listener.unregister();
}
}
public static class MovieRatingListener implements ChildEventListener {
public interface RatingChangeListener {
public void onRatingChanged(DataSnapshot snapshot);
}
private Query query;
private HashMap<String, Rating> ratingMap = new HashMap<>();
private RatingChangeListener changeListener;
public MovieRatingListener(Query query, RatingChangeListener changeListener) {
this.query = query;
this.changeListener = changeListener;
query.addChildEventListener(this);
}
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
if (dataSnapshot != null) {
ratingMap.put(dataSnapshot.getKey(), dataSnapshot.getValue(Rating.class));
changeListener.onRatingChanged(dataSnapshot);
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
if (dataSnapshot != null) {
ratingMap.put(dataSnapshot.getKey(), dataSnapshot.getValue(Rating.class));
changeListener.onRatingChanged(dataSnapshot);
}
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
ratingMap.remove(dataSnapshot.getKey());
changeListener.onRatingChanged(null);
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
public Rating getRating(String key) {
if (ratingMap.get(key) != null) {
return ratingMap.get(key);
} else {
return new Rating(); // default value/null object
}
}
public void unregister() {
query.removeEventListener(this);
}
}
}
I'd like to have a RecyclerView showing random questions to the users. Once the question is answered (wright or wrong), it should never appear for this user again, even if he closes the app. After that, the activity is reloaded and a new random question appears.
When the users see all the questions in database, it should show a message saying that there is no more question at that time.
My code right now is putting in Firebase database if the user saw the the question after the click.
I tried to put an if statement (but it seems a stupid idea) and it's working for a short time. Basically it says, if the child exists, reload the activity. When database has thousands of questions it'll work for a period, but will crash one day.
My problems:
First, I don't want to reload the activity n times to "find" a question which hasn't appear yet.
Second, I can't determine when the users see all the questions to show a message.
Here is the code:
mDatabase = FirebaseDatabase.getInstance().getReference().child("Category").child(category_key).child("Questions");
query = mDatabase.orderByChild("randomId").startAt(randomValue).limitToFirst(1);
#Override
protected void onStart() {
super.onStart();
FirebaseRecyclerAdapter<Questions, QuestionViewHolder>
firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<Photos, QuestionViewHolder>(
Question.class,
R.layout.question_grid,
QuestionViewHolder.class,
query
) {
#Override
protected void populateViewHolder(final QuestionViewHolder viewHolder, final Question model, int position) {
final String question_key = getRef(position).getKey();
final String category_key = getIntent().getExtras().getString("category_id");
mDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.child(question_key).child("ViwedBy").hasChild(mAuth.getCurrentUser().getUid())) {
finish();
startActivity(getIntent());
} else {
viewHolder.setQuestion(model.getQuestion());{
#Override
public void onClick (View v){
mDatabaseLike.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
mDatabaseTest = FirebaseDatabase.getInstance().getReference().child("Category").child(category_key).child("Questions").child(question_key).child("ViewedBy").child(mAuth.getCurrentUser().getUid());
mDatabaseTest.setValue(true);
finish();
startActivity(getIntent());
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
}
}
#Override
public void onCancelled (DatabaseError databaseError){
}
});
mQuestionsList.setAdapter(firebaseRecyclerAdapter);
}
Is there any smarter way to do it?
Extra question: Do I need to reload the entire activity or there is a way to reload only the RecyclerView?
This would be a straightforward approach to solving your problem. Your activity will no longer have to refresh itself, as that will be handled by your Adapter.
Structure your Database as such
rootNode
L ...category/questions...
L answeredQuestions
L userId
L randomQuestionId:true
L questionsForUser
L userId
L randomQuestionId:true
When your users answers a question :
add the questionId to answeredQuestions
remove the questionId from questionsForUser
call generateNewRandomQuestionForUser()
Replace your FirebaseRecyclerAdapter with a IndexedFirebaseRecyclerAdapter alike this:
new FirebaseIndexRecyclerAdapter<Question, QuestionHolder>(Question.class,
R.layout.question,
QuestionHolder.class,
keyRef,
dataRef);
where keyRef and dataRef are both
Query keyRef = FirebaseDatabase.getInstance().getReference().child("questionsForUser").child(userId);
Query DataRef = FirebaseDatabase.getInstance().getReference().child("Category").child(category_key).child("Questions");
and generateNewRandomQuestionForUser() is something like this:
public static void generateNewRandomQuestionForUser(final String userId, final String category_key) {
FirebaseDatabase.getInstance().getReference().child("answeredQuestions").child(userId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
ArrayList<String> answeredQuestions = new ArrayList<String>();
for (DataSnapshot questionId : dataSnapshot.getChildren()) {
answeredQuestions.add(questionId.getKey());
}
FirebaseDatabase.getInstance().getReference().child("Category").child(category_key).child("Questions").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
ArrayList<String> availableQuestionKeys = new ArrayList<String>();
for (DataSnapshot questionSnapshot : dataSnapshot.getChildren()) {
availableQuestionKeys.add(questionSnapshot.getKey());
}
// Using answeredQuestions, availableQuestionKeys, you can user your own algorithm
// to select one at random that the user has not seen yet.
// Here you can also determine whether a user has seen all possible questions and update UI accordingly.
// Once you have your random question as keyOfRandomQuestion:
FirebaseDatabase.getInstance().getReference().child("questionsForUser").child(userId).child(keyOfRandomQuestion).setValue(true);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}