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) {}
});
}
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.
As refer in this link. I want to try in my coding apps. But it didnt retrieve any data from my firebase database. I just cant figure out how to fix it. Can someone please help me. Is there any idea, where did I miss?
My firebase database as show image below:-
Spinner coding:-
mDatabase = FirebaseDatabase.getInstance().getReference();
mDatabase.keepSynced(true);
mDatabase.child("Advertisement").child(mAuth.getUid()).addValueEventListener(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
//final List<String> name = new ArrayList<String>();
ArrayList<String> identity = new ArrayList<>();
identity.add(0, "Choose Tuition Centre");
for (DataSnapshot nameSnapshot: dataSnapshot.getChildren())
{
String tuitionName = nameSnapshot.child("adstuitioname").getValue(String.class);
identity.add(tuitionName);
}
ArrayAdapter<String> nameAdapter = new ArrayAdapter<String>(RatingActivity.this, android.R.layout.simple_spinner_item, identity);
nameAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mName.setAdapter(nameAdapter);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
mName.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
if(position==0)
{
//Toast.makeText(getApplicationContext(),"No Item Selected",Toast.LENGTH_LONG).show();
}
else
{
Toast.makeText(getApplicationContext(),parent.getItemAtPosition(position) +" Selected",Toast.LENGTH_SHORT).show();
}
}
#Override
public void onNothingSelected(AdapterView<?> parent)
{
// TODO Auto-generated method stub
}
});
Add listener as below :
DatabaseReference mDatabaseRef =
FirebaseDatabase.getInstance().getReference();
ArrayList<String> list=new ArrayList<>();
mDatabaseRef.child("Advertisement")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot :dataSnapshot.getChildren())
{
Advertisementject advertisement=
snapshot.getValue(Advertisement.class);
//replace Object.class with your class name
//get your key value here from your "Custom class"
// which contains "adstutioname"
// add in list
list.add(advertisement.getAdstutuioname());
}
//set adapter
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
generate class like this
public class Advertisement{
#SerializedName("adstutuioname")
#Expose
private String adstutuioname;
public String getAdstutuioname() {
return adstutuioname;
}
public void setAdstutuioname(String adstutuioname) {
this.adstutuioname = adstutuioname;
}
}
Your class contains all your params and .
Replace Object with this class
I hope the problem is with your getValue() method. Firebase getValue method returns an Object, and you are expecting a String. Please modify this line to convert your Object to String -
String tuitionName = nameSnapshot.child("adstuitioname").getValue(String.class).toString();
It's not clear if you're trying to read once, or watch changes Difference between addValueEventListener() and addListenerForSingleValueEvent() of firebase
First things first, look if you are getting a database error
#Override
public void onCancelled(DatabaseError databaseError) {
// Toast or print the databaseError, don't ignore it
}
Then look where you're adding a listener - mDatabase.child("Advertisement").child(mAuth.getUid())., which from your picture is every element at the level of -LNZ6....
For that level, you have a list of object's, so you can need to use a loop, and as shown from some examples on Firebase site, it's recommended that you make an object, but you still can use child method for a certain field as you're trying to do
But if a field doesn't exist, you get null value, and Adapters cannot hold nulls, so you must ignore or set some default value.
ArrayList<String> identity = new ArrayList<>();
identity.add(0, "Choose Tuition Centre");
for (DataSnapshot nameSnapshot: dataSnapshot.getChildren()) {
String tuitionName = nameSnapshot.child("adstuitioname").getValue(String.class);
Log.d("DEBUG TUITION", String.valueOf(tuitionName); // look for this in your logs
identity.add(tuitionName == null ? "none" : tuitionName);
}
ArrayAdapter<String> nameAdapter =...
I am creating an app in which points increases on button click and those points should be saved to firebase. I managed to save those data to firebase. But when I destroy my app and open it again the points value is showing same but after clicking button. It again starts from 0.
For example: every time on button click the points value increases to 10 points. Now when I completely destroy the app and open it again, the points value shows same, but when button clicked it again starts from initial condition.
Here is my code
int amount = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
button_claim.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
amount = amount + 100;
textView_points.setText(String.valueOf(amount));
databaseReference.setValue(textView_points.getText().toString());
}
});
}
#Override
protected void onStart() {
super.onStart();
if (mAuth.getCurrentUser() == null) {
finish();
Intent main = new Intent(this,MainActivity.class);
main.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(main);
}
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
textView_points.setText(dataSnapshot.getValue(String.class));
databaseReference.setValue(textView_points.getText().toString());
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(Main2Activity.this,"error",Toast.LENGTH_LONG).show();
}
});
}
Before destroying the app
see the image please, points increasing on button click before destroying the app and uploading to database
please see image 1
Opened app again(after closing) it is showing the same updated point
please see image 2
Now when i click claim it returns back to 100
please see image number 3
please help me on this problem, and i am a newbie
Thanks
Edit: 29th, June 2020
Now it's also possible to solve this problem without the use of a transaction. We can simply increment a value using:
rootRef.child("score").setValue(ServerValue.increment(1));
And for decremenet, the following line of code is required:
rootRef.child("score").setValue(ServerValue.increment(-1));
This is how you set a value in your Firebase database:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.child("score").setValue(1);
Assuming that the your score field is of type Integer, to solve this, please use the following method:
public static void setScore(String operation) {
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference scoreRef = rootRef.child("score");
scoreRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Integer score = mutableData.getValue(Integer.class);
if (score == null) {
return Transaction.success(mutableData);
}
if (operation.equals("increaseScore")) {
mutableData.setValue(score + 1);
} else if (operation.equals("decreaseScore")){
mutableData.setValue(score - 1);
}
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {}
});
}
For this, I recommend you definitely use transactions. You will avoid wrong results if users are trying to increase/decrease the score in the same time. So as a conclusion, call this method accordingly to your increase/decrease operation.
This is how you can read it:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference scoreRef = rootRef.child("score");
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Integer score = ds.getValue(Integer.class);
Log.d("TAG", score + "");
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
scoreRef.addListenerForSingleValueEvent(eventListener);
You need to first get the value of previous score and then add it with the current score and then update the final value in the firebase database.
question_score.child("score").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String previousScore = dataSnapshot.getValue(String.class);
if (previousScore!=null){
int finalScore = Integer.parseInt(previousScore) + current_score;
question_score.child("score").setValue(String.valueOf(finalScore));
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
I think that previous score is a string in you code #DHANANJAY KUMAR and Strings cannot be added and also what is the meaning of String.class. I think It can be made
int previousScore = dataSnapshot.getValue(Integer.class);
if we can edit
String.class
to
Integer.class
or
int.class
Please Clarify
I am creating an app in which points increases on button click and those points should be saved to firebase. I managed to save those data to firebase. But when I destroy my app and open it again the points value is showing same but after clicking button. It again starts from 0.
For example: every time on button click the points value increases to 10 points. Now when I completely destroy the app and open it again, the points value shows same, but when button clicked it again starts from initial condition.
Here is my code
int amount = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
button_claim.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
amount = amount + 100;
textView_points.setText(String.valueOf(amount));
databaseReference.setValue(textView_points.getText().toString());
}
});
}
#Override
protected void onStart() {
super.onStart();
if (mAuth.getCurrentUser() == null) {
finish();
Intent main = new Intent(this,MainActivity.class);
main.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(main);
}
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
textView_points.setText(dataSnapshot.getValue(String.class));
databaseReference.setValue(textView_points.getText().toString());
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(Main2Activity.this,"error",Toast.LENGTH_LONG).show();
}
});
}
Before destroying the app
see the image please, points increasing on button click before destroying the app and uploading to database
please see image 1
Opened app again(after closing) it is showing the same updated point
please see image 2
Now when i click claim it returns back to 100
please see image number 3
please help me on this problem, and i am a newbie
Thanks
Edit: 29th, June 2020
Now it's also possible to solve this problem without the use of a transaction. We can simply increment a value using:
rootRef.child("score").setValue(ServerValue.increment(1));
And for decremenet, the following line of code is required:
rootRef.child("score").setValue(ServerValue.increment(-1));
This is how you set a value in your Firebase database:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
rootRef.child("score").setValue(1);
Assuming that the your score field is of type Integer, to solve this, please use the following method:
public static void setScore(String operation) {
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference scoreRef = rootRef.child("score");
scoreRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Integer score = mutableData.getValue(Integer.class);
if (score == null) {
return Transaction.success(mutableData);
}
if (operation.equals("increaseScore")) {
mutableData.setValue(score + 1);
} else if (operation.equals("decreaseScore")){
mutableData.setValue(score - 1);
}
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {}
});
}
For this, I recommend you definitely use transactions. You will avoid wrong results if users are trying to increase/decrease the score in the same time. So as a conclusion, call this method accordingly to your increase/decrease operation.
This is how you can read it:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference scoreRef = rootRef.child("score");
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Integer score = ds.getValue(Integer.class);
Log.d("TAG", score + "");
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
scoreRef.addListenerForSingleValueEvent(eventListener);
You need to first get the value of previous score and then add it with the current score and then update the final value in the firebase database.
question_score.child("score").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String previousScore = dataSnapshot.getValue(String.class);
if (previousScore!=null){
int finalScore = Integer.parseInt(previousScore) + current_score;
question_score.child("score").setValue(String.valueOf(finalScore));
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
I think that previous score is a string in you code #DHANANJAY KUMAR and Strings cannot be added and also what is the meaning of String.class. I think It can be made
int previousScore = dataSnapshot.getValue(Integer.class);
if we can edit
String.class
to
Integer.class
or
int.class
Please Clarify
I am trying to login a user as a guest which means he can only view whatever is there but not interact with anything. I had some problems doing this.
First of all I used anonymous login, so that is working perfectly to sign in the user.
Now I want the user to select from a list of areaUIDs to view details of only that particular area
My database looks like this,
Areas
-AreaUID1
-Admin
-Admin1UID
-admin_name:""
-admin_area:""
-admin_email:""
-Admin2UID
-admin_name:""
-admin_area:""
-admin_email:""
.
.
-Events
-EventUid1
-event_name:""
-event_timings:""
-EventUid2
-event_name:""
-event_timings:""
.
.
-Gallery
-ImageUid1
-image_link:""
-AreaUid2
-Admin
-Admin1UID
-admin_name:""
-admin_area:""
-admin_email:""
-Admin2UID
-admin_name:""
-admin_area:""
-admin_email:""
.
.
-Events
-EventUid1
-event_name:""
-event_timings:""
-EventUid2
-event_name:""
-event_timings:""
.
.
-Gallery
-ImageUid1
-image_link:""
and so on.
Now I want to show the user a list of the several admin_area located in different AreaUids. I am using FirebaseRecyclerAdapter and I need help implementing the above problem. I want the user to be able to view all the gallery images and events from a particular area that he/she chooses.
mDbRef = FirebaseDatabase.getInstance().getReference().child("Areas");
allAreaUids = new ArrayList<>();
ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
//This is where i want to populate my arraylist with all the AreaUids. How do i do it?
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
System.out.println(databaseError.toException());
// ...
}
};
mDbRef.addValueEventListener(listener);
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, android.R.id.text1, allAreaUids);
ListView listView = (ListView) findViewById(R.id.list_of_areas);
listView.setAdapter(adapter);
My Areas class (note the constructors and the getters and setters methods are also present
public class Areas {
private Events Events;
private GalleryPics Gallery;
private Admin Admin;
public Areas(){
}
.
.
.
}
To display the Events of a specific area, please use this code:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference areasRef = rootRef.child("Areas");
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
String areaUid = ds.getKey();
DatabaseReference eventsRef = rootRef.child("Areas").child(areaUid).child("Events");
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot child : dataSnapshot.getChildren()) {
String event_name = child.child("event_name").getValue(String.class);
Log.d("TAG", event_name);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
eventsRef.addListenerForSingleValueEvent(valueEventListener);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
areasRef.addListenerForSingleValueEvent(eventListener);
In the same way you can get all the Gallery of a specific area.