I'm trying to create my first android app. I have created quiz, connected with Firebase. It works fine, but I need to show only 10 questions - from the total 30, randomly. Also, I don't want to repeat the question. How can I generate random question? Here is the code I made. Thanks
private void updateQuestion() {
mQuestionRef = new Firebase("https://ab-quiz.firebaseio.com/"+ mQuestionNumber +"/biq");
mQuestionRef.addValueEventListener(new com.firebase.client.ValueEventListener() {
#Override
public void onDataChange(com.firebase.client.DataSnapshot dataSnapshot) {
String question = dataSnapshot.getValue(String.class);
mQuestion.setText(question);
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
if(mQuestionNumber > 10){
quitFunction();
}
else {
mQuestionNumber++;
}
}
Try these steps:
Add all your questions in a List.
Maintain a count of how many questions have you displayed starting with 0.
Use the code below to get a random number within your list size.
Random rand = new Random();
int n = rand.nextInt(list.size);
Get the question from your list using list.get(n).
After your question is displayed remove the item from the list using list.remove(n).
Increment your count of how many questions have you displayed declared in step 2.
Start another process after you have reached your limit of displaying questions.
Hope this helps.
Thank you.
Here is a possible answer -
Firstly, you should create a QuizQuestion.java where you can set a questionId and the questionText, like this -
public class QuizQuestion {
private int questionId;
private String questionText;
public ChatMessage(int questionId, String questionText, ) {
this.messageText = messageText;
this.messageUser = messageUser;
}
public QuizQuestion(){
}
public int getQuestionId() {
return questionId;
}
public void setQuestionId(int questionId) {
this.questionId = questionId;
}
public String getQuestionText() {
return questionText;
}
public void setQuestionText(String questionText) {
this.questionText = questionText;
}
}
Then, call it in MainActivity.java -
public class MainActivity extends AppCompatActivity {
private TextView questionView;
private Button nextButton;
private int questionCount = 0;
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
questionView = findViewById(R.id.question_view);
nextButton = findViewById(R.id.next_button);
displayQuestion();
nextButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
displayQuestion();
}
});
}
public void displayQuestion() {
// This methods queries your database for a question with the Id of an random integer from 1 to 30 and sets the text of the TextView to the question text
final Query questionToDisplay = FirebaseDatabase.getInstance()
.getReference().child("questions")
.orderByChild("questionId")
.equalTo(new Random().nextInt(30)); // Generate a random integer from 1 to 30
questionToDisplay.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot question : dataSnapshot.getChildren()) {
if (questionCount is < 10) {
questionView.setText(question.getValue(QuizQuestion.class).getQuestionText);
questionCount += 1;
} else if (questionCount >= 10) {
finish(); // Close Activity
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.e("ItemDetail", "onCancelled", databaseError.toException());
}
});
}
If you want add a question, just add these lines wherever you want -
FirebaseDatabase.getInstance().getReference().child("questions").push
.setvalue(new QuizQuestion(1, /* Question Id */"What sound does a fox make?" /* Question Text */));
Hope this helps!!
EDIT:
Here is the asssumed database structure -
--quiz-app // Root node
--questions
--Lxjcksduso12m42i4m
--questionId: "1"
--questionText: "What sound does a fox make?"
--Lxjcksduso12m42i4m
--questionId: "2"
--questionText: "What is the capital of Italy?"
--Lxjcksduso12m42i4m
--questionId: "3"
--questionText: "When will the polar ice caps melt completely?"
Suppose if you have a questions like below image in firebase database.
database = FirebaseFirestore.getInstance();
ArrayList<Question> questions;
String categoryId = getIntent().getStringExtra("categoryId");
database.collection("categories")
.document(categoryId)
.collection("Questions")
.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for(DocumentSnapshot snapshot : queryDocumentSnapshots) {
Question question = snapshot.toObject(Question.class);
questions.add(question);
}
Collections.shuffle(questions);
}
});
In my case, I have a category (for example computer category, software category) and in each category we have a collection of questions Therefore we get a specific categoryId to display questions of specific category.
first you get all the question from database and add in a list like questions.add(question); if all the added in a list then you shuffle the questions in the list, Collections.shuffle(questions);
so each time you get different questions.
Related
I use Firebase Realtime Database and LiveData with ViewModel in Android and I would like to constantly update the query.
Here is the ViewModel class:
public class ViewModel_MainActivity extends ViewModel {
/*
LiveData with Database query for the Firebase node "Ratings"
*/
private static long currentTimeMillisRating = System.currentTimeMillis();
private static double pastMinuteForDisplayingRatings = 1;
private static long pastTimeMillisRatings = System.currentTimeMillis() - (long) (pastMinuteForDisplayingRatings * 60 * 60 * 1000);
private static final Query QUERY_RATINGS =
FirebaseDatabase.getInstance(DB_SQLite_Firebase_Entries.FIREBASE_URL).getReference().child(DB_SQLite_Firebase_Entries.FIREBASE_NODE_RATINGS).orderByChild(DB_SQLite_Firebase_Entries.FIREBASE_RATINGDATEINMILLISECONDS).endAt(pastTimeMillisRatings);
private final LiveData_FirebaseRating liveData_firebaseRating = new LiveData_FirebaseRating(QUERY_RATINGS);
#NonNull
public LiveData_FirebaseRating getDataSnapshotLiveData_FirebaseRating() {
Log.e("LogTag_ViMo", "pastTimeMillisRatings: " + pastTimeMillisRatings);
return liveData_firebaseRating;
}
}
Here is the LiveData class:
public class LiveData_FirebaseRating extends LiveData<DataSnapshot> {
private static final String LOG_TAG = "LiveData_FirebaseRating";
private Query query;
private final LiveData_FirebaseRating.MyValueEventListener listener = new LiveData_FirebaseRating.MyValueEventListener();
DataSnapshot currentDataSnapShotFromFirebase;
public LiveData_FirebaseRating(Query query) {
this.query = query;
}
public LiveData_FirebaseRating(DatabaseReference ref) {
this.query = ref;
}
public void changeQuery(Query newQuery) {
this.query = newQuery;
}
public DataSnapshot getCurrentDataSnapShotFromFirebase() {
return currentDataSnapShotFromFirebase;
}
public void setCurrentDataSnapShotFromFirebase(DataSnapshot currentDataSnapShotFromFirebase) {
this.currentDataSnapShotFromFirebase = currentDataSnapShotFromFirebase;
}
#Override
protected void onActive() {
Log.d(LOG_TAG, "onActive");
query.addValueEventListener(listener);
}
#Override
protected void onInactive() {
Log.d(LOG_TAG, "onInactive");
query.removeEventListener(listener);
}
private class MyValueEventListener implements ValueEventListener {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
setValue(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(LOG_TAG, "Can't listen to query " + query, databaseError.toException());
}
}
}
And here is the part of the main activity, where the live data and view model are created and observed:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
/*
Initiate View Model with LiveData for Firebase
*/
rootRef_Firebase = FirebaseDatabase.getInstance(DB_SQLite_Firebase_Entries.FIREBASE_URL).getReference();
viewModel = new ViewModelProvider(this).get(ViewModel_MainActivity.class);
liveData_firebaseRating = viewModel.getDataSnapshotLiveData_FirebaseRating();
liveData_firebaseRating.observe(this, new Observer<DataSnapshot>() {
#Override
public void onChanged(#Nullable DataSnapshot dataSnapshot) {
liveData_firebaseRating.setCurrentDataSnapShotFromFirebase(dataSnapshot);
if(liveData_firebaseRating.getCurrentDataSnapShotFromFirebase()!=null) {
//Do something with the dataSnapshot of the query
}
}// end onChange
}); //end observe
}
So my problem lies in the non-updating of the QUERY_RATINGS in the class ViewModel_MainActivity . What I want is to return all the nodes from the FIREBASE_NODE_RATINGS whose at attribute FIREBASE_RATINGDATEINMILLISECONDS is older than 1 Minute (meaning that the entry was created at least 1 Minute before the current time slot). How can I do that such that I always get these nodes?
Firebase Query objects are immutable, and their parameters are fixed values. So there is no way to update the existing query, nor is there a "one minute ago" value that automatically updates.
The options I can quickly think off:
Create a new query frequently to capture the new full range of relevant nodes.
Create a new query frequently to capture only the new nodes (by combining startAt and endAt) and merge them with the results you already have.
Remove the condition from the query, and perform the filtering in your application code.
Based on what I know, I'd probably start with the third option as it seems the simplest.
So I have been trying to implement a way to check if the user had already sent a friend a request to the profile visited, or if the user has received a friend request from the profile visited. Based on the results, the buttons will be set to sent request, accept request ,add friend or friends
However, in my Firebase function, the first 3 if statements aren't met, even if one of them was supposed to be met. The first else if statement should have worked because I already sent a friend request to the profile visited.
When I ran a debug, it shows something like value = {sentFriendRequests={jmarston=2}}. So Firebase knows that I added John Marston as a friend, but for some reason the else if statement wasn't working. Its the else statement that works instead
My code is down below:
checkFriendRequestStatus function
private void checkFriendRequestStatus(final ButtonStatus buttonsCallback, final String strSignedInUID, final String visitedUsername, final String strVisitedUID) {
final DatabaseReference checkFriendRequestsRef = database.getReference("friend_requests/test/" + strSignedInUID);
checkFriendRequestsRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
// choice is 1 to show buttons, then select which buttons to show with second params
if (dataSnapshot.child("friends/" + visitedUsername).getValue(String.class) == strVisitedUID) {
buttonsCallback.setButtonStatus(1, 1);
}
else if (dataSnapshot.child("sentFriendRequest/" + visitedUsername).getValue(String.class) == strVisitedUID) {
buttonsCallback.setButtonStatus(1, 2);
}
else if (dataSnapshot.child("receivedFriendRequests/" + visitedUsername).getValue(String.class) == strVisitedUID) {
buttonsCallback.setButtonStatus(1, 3);
}
else {
buttonsCallback.setButtonStatus(1, 4);;
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
onViewCreated function
#Override
public void onViewCreated(View view, Bundle savedInstanceState){
sRFullName = (TextView) view.findViewById(R.id.sRUFullNameET);
addFriendBtn = (Button) view.findViewById(R.id.sRUAddFriendBtn);
sentRequestBtn = (Button) view.findViewById(R.id.sRUFriendReqSentBtn);
acceptRequestBtn = (Button) view.findViewById(R.id.sRUAcceptRequestBtn);
wereFriendsBtn = (Button) view.findViewById(R.id.sRUWeFriendsBtn);
final String strVisitedUserID = getArguments().getString("sRUserID");
final String visitedUsername = getArguments().getString("sRUsername");
ShPreference = getActivity().getSharedPreferences(MyPREFERENCES, Context.MODE_PRIVATE);
// converts Stringed userID back to Int
final String strSignedInUID = ShPreference.getInt(currentUserID, 0) + "";
final String signedInUsername = ShPreference.getString(currentUsername, "");
// converts the userSignedIn id to string
//final String strSignedInUID = userSignedInID + "";
// checks if the current User visited has been sent a friend Request
checkFriendRequestStatus(new ButtonStatus() {
#Override
public void setButtonStatus(int choice, int button) {
/**
* The choice params is for the choose if to show or hide buttons.
* The buttons params selects which buttons are to show or hide
*/
addFriendBtn.setVisibility(View.GONE);
sentRequestBtn.setVisibility(View.GONE);
acceptRequestBtn.setVisibility(View.GONE);
wereFriendsBtn.setVisibility(View.GONE);
// if choosed to show buttons
if (choice == 1) {
// show buttons depending on the friendRequest status
if (button == 1) {
wereFriendsBtn.setVisibility(View.VISIBLE);
}
else if (button == 2) {
sentRequestBtn.setVisibility(View.VISIBLE);
}
else if (button == 3) {
acceptRequestBtn.setVisibility(View.VISIBLE);
}
else {
addFriendBtn.setVisibility(View.VISIBLE);
}
}
}
}, strSignedInUID, visitedUsername, strVisitedUserID);
// sets the name with the Full Name; called from SearchResultsAdapter
sRFullName.setText(getArguments().getString("sRFullName"));
}
To compare String object use equals instead of == (double equals). Double equals will compare reference not their values.
Update your code of comparison like below:
private void checkFriendRequestStatus(final ButtonStatus buttonsCallback, final String strSignedInUID, final String visitedUsername, final String strVisitedUID) {
final DatabaseReference checkFriendRequestsRef = database.getReference("friend_requests/test/" + strSignedInUID);
checkFriendRequestsRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
// choice is 1 to show buttons, then select which buttons to show with second params
if (dataSnapshot.child("friends/" + visitedUsername).getValue(String.class).equals(strVisitedUID)) {
buttonsCallback.setButtonStatus(1, 1);
}
else if (dataSnapshot.child("sentFriendRequest/" + visitedUsername).getValue(String.class).equals(strVisitedUID)) {
buttonsCallback.setButtonStatus(1, 2);
}
else if (dataSnapshot.child("receivedFriendRequests/" + visitedUsername).getValue(String.class).equals(strVisitedUID)) {
buttonsCallback.setButtonStatus(1, 3);
}
else {
buttonsCallback.setButtonStatus(1, 4);;
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
Currently a have a floating action bottom in my detailView in this activity I bring the information of the Object with an intent. I want that when the user enters the detailView activity I have a filled heart if that Automoviles objectId already exist in the Favorites Node.if not add the information into the Database. If the favorite already exist and it has the filled heart if the user clicks again it will remove it from the database.
This is my favorites class:
public class Favorites {
private String id;
private String automovilesId;
private String userId;
private String fecha;
public Favorites() {
}
public Favorites(String automovilesId, String userId, String fecha) {
this.automovilesId = automovilesId;
this.userId = userId;
this.fecha = fecha;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAutomovilesId() {
return automovilesId;
}
public void setAutomovilesId(String automovilesId) {
this.automovilesId = automovilesId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getFecha() {
return fecha;
}
public void setFecha(String fecha) {
this.fecha = fecha;
}
}
At the moment i have this code:
databaseReference = FirebaseDatabase.getInstance().getReference(AppModel.Favorites);
favoritesButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
final DatabaseReference usersRef = rootRef.child("Favorites");
favorites = new Favorites(automoviles.getObjectId(), user.getUid(), AppModel.GetDate());
databaseReference.push().setValue(favorites);
}
});
Which Leads this entry in the database:
Firebase Data
This is all good but i need to verify also if favorites exist or not in the database or else everytime i run the android emulator it will create another favorites node.
Now i added more code . But when i run it and i add this AddFavorite method it will create infinite loops of Favorite Nodes in the database
databaseReference = FirebaseDatabase.getInstance().getReference(AppModel.Favorites);
favoritesButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
final DatabaseReference usersRef = rootRef.child("Favorites");
favorites = new Favorites(automoviles.getObjectId(), user.getUid(), AppModel.GetDate());
databaseReference.push().setValue(favorites);
usersRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
String car = ds.child("automovilesId").getValue(String.class);
Favorites fav =ds.getValue(Favorites.class);
String carId =fav.getAutomovilesId();
Log.i("No","Este es el nombre------>:"+carId);
String id=ds.getKey();
AddFavoritesuser();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
});
This is the AddFavorite method and the delete method:
private void AddFavoritesuser() {
favoritesButton.setImageResource(R.drawable.ic_favorite);
favorites = new Favorites(automoviles.getObjectId(), user.getUid(), AppModel.GetDate());
databaseReference.push().setValue(favorites);
}
And the delete:
private void deleteFavoritesUser(String key) {
favoritesButton.setImageResource(R.drawable.ic_favorite_border);
databaseReference.child(key).getRef().removeValue();
}
In short i want it to verify if that object of Automoviles object exist . If it exist the user enters and sees the filled heart, if he wants to delete this favorite then he clicks again and it will run the deleteFavoritesUser method. But my question is what if Favorites is still no created in the database how can i also validate this?.
I runned the app again and I have the same vale created again:
As you can see I have the same automovilesId entry
basically you need to make your favorite button as a toggle button
favoritesButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// in place of YES/NO you can take any values to present true/false state
boolean status = v.getTag() != null ? v.getTag().equals("YES") : false;
v.setTag(status ? "NO" : "YES");
if (status) {
// Do favorite me
//AddFavoritesuser();
} else {
// Do un favorite me
//deleteFavoritesUser(key);
}
}
}
everytime i run the android emulator it will create another favorites node.
This is happening because everytime you are trying to add a new object of Favorites class to the database, you are using the push() method, which generates a new random key everytime is called. This is what this method does. So in order to check a property from the database, you need to use in your reference the same key, not to generate another one. Please see in my answer from this post, where I have explained how you can achieve this.
I'm a Android Studio coding beginner and currently building a nutrition app to get programming practice. I use Firebase for Authentication and as a database to save User data.
How it works:
My app has a survey built in which asks for body specifics and taste (age, height, liked/disliked ingredients etc.). I have a class GlobalUser with public static attributes to save the answers in the app. When the user registers, he is sent directly to the survey activity. There he answers the questions and the results are written to the Firebase database under his UID (I use a User class with the same attributes as GlobalUser to create an instance and use Firebase's setValue(Object) method). If he signs in (or still is signed in), the LoginRegistrationActivity directly sends him to the MainActivity. There, the GlobalUser class gets instantiated with the data saved under his UID. From the MainActivity, he can navigate to a ProfileActivity where the UI gets updated based on his data. This works quite well. After doing the survey I can find the results in a child node consisting of the UID of the user, the UI gets updated correctly and the sign in/registration process works as intended.
What is wrong:
However, as I was playing around with different designs and constantly restarting the app, it started to crash occasionally. After some testing it showed that the GlobalUser class wasn't updated and thus the ArrayLists were null and caused NullPointerExceptions when I used .size() on them. Since this issue only occurs rarely and seems to be related to restarting the app multiple times I thought it would have something to do with the Activity lifecycle so I also updated the GlobalUser in onStart and onResume but it didn't help. I also tried updating GlobalUser again in the ProfileActivity directly before the ArrayLists were set but it didn't work. I still guess it has something to do with the lifecycle but I have no idea where I should start. Here's the code of the relevant classes/actvitites:
LoginRegistrationActivity:
public class LoginRegistrationActivity extends AppCompatActivity {
private DatabaseReference mRef;
private FirebaseAuth mAuth;
private EditText emailAddress;
private EditText emailPassword;
private Button emailLogin;
private Button emailRegistration;
private TextView forgotPassword;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_registration);
mAuth = FirebaseAuth.getInstance();
if (mAuth.getCurrentUser()!=null){
Intent i = new Intent (LoginRegistrationActivity.this, MainActivity.class);
LoginRegistrationActivity.this.startActivity(i);
}
emailAddress = findViewById(R.id.address_edit);
emailPassword = findViewById(R.id.password_edit);
emailLogin = findViewById(R.id.mail_login_button);
emailRegistration = findViewById(R.id.mail_registration_button);
forgotPassword = findViewById(R.id.forgot_password);
emailRegistration.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String email = emailAddress.getText().toString().trim();
String password = emailPassword.getText().toString().trim();
if (TextUtils.isEmpty(email)){
Toast.makeText(LoginRegistrationActivity.this, "Bitte E-Mail Addresse eingeben!", Toast.LENGTH_LONG).show();
return;
}
if (TextUtils.isEmpty(password)){
Toast.makeText(LoginRegistrationActivity.this, "Bitte Passwort eingeben!", Toast.LENGTH_LONG).show();
return;
}
if (password.length()<6){
Toast.makeText(LoginRegistrationActivity.this, "Passwort muss mindestens sechs Zeichen lang sein!", Toast.LENGTH_LONG).show();
return;
}
mAuth.createUserWithEmailAndPassword(email, password).addOnCompleteListener(LoginRegistrationActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (!task.isSuccessful()){
Toast.makeText(LoginRegistrationActivity.this, "Unbekannter Fehler", Toast.LENGTH_LONG).show();
} else {
Intent i = new Intent (LoginRegistrationActivity.this, SurveyGreetingActivity.class);
LoginRegistrationActivity.this.startActivity(i);
finish();
}
}
});
}
});
emailLogin.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String password = emailPassword.getText().toString();
String email = emailAddress.getText().toString();
if (TextUtils.isEmpty(email)){
Toast.makeText(LoginRegistrationActivity.this, "Bitte E-Mail Addresse eingeben!", Toast.LENGTH_LONG).show();
return;
}
if (TextUtils.isEmpty(password)){
Toast.makeText(LoginRegistrationActivity.this, "Bitte Passwort eingeben!", Toast.LENGTH_LONG).show();
return;
}
if (password.length()<6){
Toast.makeText(LoginRegistrationActivity.this, "Passwort muss mindestens sechs Zeichen haben!", Toast.LENGTH_LONG).show();
return;
}
mAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(LoginRegistrationActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (!task.isSuccessful()){
Toast.makeText(LoginRegistrationActivity.this, "Unbekannter Fehler beim Einloggen", Toast.LENGTH_LONG).show();
} else {
Intent i = new Intent (LoginRegistrationActivity.this, MainActivity.class);
LoginRegistrationActivity.this.startActivity(i);
finish();
}
}
});
}
});
}
}
MainActivity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final FirebaseAuth mAuth = FirebaseAuth.getInstance();
FirebaseUser user = mAuth.getCurrentUser();
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference mRef = database.getReference().child("users").child("uid").child(mAuth.getCurrentUser().getUid());
//In case the user cancelled the app when filling out the survey for the first time
if (mRef == null){
MainActivity.this.startActivity(new Intent (MainActivity.this, SurveyGreetingActivity.class));
}
//sets GlobalUser to data saved in Firebase Database User object
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
if (user!=null){
GlobalUser.setToUser(user);
GlobalUser.setGlobalUid(mAuth.getCurrentUser().getUid());
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(getApplicationContext(), "Database Error", Toast.LENGTH_LONG).show();
}
});
}
}
GlobalUser:
package com.example.andre.valetto02;
import java.util.ArrayList;
public class GlobalUser {
public static String globalUid = null;
public static ArrayList<Ingredient> globalLikes;
public static ArrayList<Ingredient> globalDislikes;
public static int globalAge;
public static int globalWeight;
public static int globalHeight;
public static int globalTrainingGoal;
public static int globalDailyActive;
public static boolean globalIsMale;
public GlobalUser() {
}
public static String getGlobalUid() {
return globalUid;
}
public static void setGlobalUid(String globalUid) {
GlobalUser.globalUid = globalUid;
}
public static ArrayList<Ingredient> getGlobalLikes() {
return globalLikes;
}
public static void setGlobalLikes(ArrayList<Ingredient> globalLikes) {
GlobalUser.globalLikes = globalLikes;
}
public static ArrayList<Ingredient> getGlobalDislikes() {
return globalDislikes;
}
public static void setGlobalDislikes(ArrayList<Ingredient> globalDislikes) {
GlobalUser.globalDislikes = globalDislikes;
}
public static int getGlobalAge() {
return globalAge;
}
public static void setGlobalAge(int globalAge) {
GlobalUser.globalAge = globalAge;
}
public static int getGlobalWeight() {
return globalWeight;
}
public static void setGlobalWeight(int globalWeight) {
GlobalUser.globalWeight = globalWeight;
}
public static int getGlobalHeight() {
return globalHeight;
}
public static void setGlobalHeight(int globalHeight) {
GlobalUser.globalHeight = globalHeight;
}
public static int getGlobalTrainingGoal() {
return globalTrainingGoal;
}
public static void setGlobalTrainingGoal(int globalTrainingGoal) {
GlobalUser.globalTrainingGoal = globalTrainingGoal;
}
public static int getGlobalDailyActive() {
return globalDailyActive;
}
public static void setGlobalDailyActive(int globalDailyActive) {
GlobalUser.globalDailyActive = globalDailyActive;
}
public static boolean isGlobalIsMale() {
return globalIsMale;
}
public static void setGlobalIsMale(boolean globalIsMale) {
GlobalUser.globalIsMale = globalIsMale;
}
public static void setToUser(User user) {
GlobalUser.setGlobalAge(user.getAge());
GlobalUser.setGlobalWeight(user.getWeight());
GlobalUser.setGlobalHeight(user.getHeight());
GlobalUser.setGlobalDailyActive(user.getDailyActive());
GlobalUser.setGlobalTrainingGoal(user.getTrainingGoal());
GlobalUser.setGlobalIsMale(user.getIsMale());
GlobalUser.setGlobalLikes(user.getLikes());
GlobalUser.setGlobalDislikes(user.getDislikes());
}
public static void resetLikesAndDislikes(){
globalLikes = new ArrayList <>();
globalDislikes = new ArrayList<>();
}
public static User globalToUser () {
return new User (globalLikes, globalDislikes, globalWeight, globalHeight, globalAge, globalTrainingGoal, globalDailyActive, globalIsMale);
}
}
User:
package com.example.andre.valetto02;
import java.util.ArrayList;
public class User {
ArrayList<Ingredient> likes;
ArrayList<Ingredient> dislikes;
Boolean isMale;
public Boolean getIsMale(){return isMale;}
public void setIsMale(Boolean b){isMale = b;}
public void setDislikes(ArrayList<Ingredient> dislikes) {
this.dislikes = dislikes;
}
public User (){
likes = new ArrayList<>();
dislikes = new ArrayList<>();
weight = 0;
height = 0;
age = 0;
trainingGoal = 2;
dailyActive = 1;
isMale=true;
}
public User (ArrayList<Ingredient> l, ArrayList<Ingredient> d, int w, int h, int a, int tG, int dA, boolean iM) {
likes = l;
dislikes = d;
weight = w;
height = h;
age = a;
trainingGoal = tG;
dailyActive = dA;
isMale = iM;
}
int age;
public ArrayList<Ingredient> getDislikes() {
return dislikes;
}
public ArrayList<Ingredient> getLikes() {
return likes;
}
public void setLikes (ArrayList<Ingredient> list){
likes = list;
}
public void setDisikes (ArrayList<Ingredient> list){
dislikes = list;
}
public int getAge () {
return age;
}
public void setAge (int i) {
age = i;
}
int weight;
public int getWeight (){
return weight;
}
public void setWeight(int i) {
weight = i;
}
int height;
public int getHeight (){
return height;
}
public void setHeight(int i) {
height = i;
}
int trainingGoal; //trainingGoal = 0 means weight loss, 1 means muscle gain and 2 means healthy living
public void setTrainingGoal(int i) {
trainingGoal = i;
}
public int getTrainingGoal(){
return trainingGoal;
}
int dailyActive; //dailyActive = 0 means wenig, 1 means leicht, 2 means moderat, 3 means sehr and 4 means extrem
public int getDailyActive() {return dailyActive;}
public void setDailyActive(int i) {dailyActive = i;}
public double computeCalorieGoal(){
if (isMale) {
double RMR;
RMR = weight*10 + 6.25*height - 5*age + 5;
if (dailyActive==0) {RMR=RMR*1.2;}
else if (dailyActive==1) {RMR=RMR*1.375;}
else if (dailyActive==2) {RMR=RMR*1.55;}
else if (dailyActive==3) {RMR=RMR*1.725;}
else {RMR=RMR*1.9;}
if (trainingGoal == 0) {RMR = RMR - 400;}
else if (trainingGoal ==1){RMR = RMR + 400;}
return RMR;
} else {
double RMR;
RMR = weight*10 + 6.25*height - 5*age - 161;
if (dailyActive==0) {RMR=RMR*1.2;}
else if (dailyActive==1) {RMR=RMR*1.375;}
else if (dailyActive==2) {RMR=RMR*1.55;}
else if (dailyActive==3) {RMR=RMR*1.725;}
else {RMR=RMR*1.9;}
if (trainingGoal == 0) {RMR = RMR - 300;}
else if (trainingGoal ==1){RMR = RMR + 300;}
return RMR;
}
}
}
Thanks for the help!
I just found the mistake. It has nothing to do with the activity lifecycle and it only indirectly had something to do with restarting the app. The problem was that Firebase's Value Event Listeners are still AsyncTasks. When I started the app and immediately opened the ProfileActivity, the Activity was created before the Firebase AsyncTask could fetch the data from the Database. Thus the ProfileActivity would call the .size() method on the ArrayLists before they were instantiated. In essence, the error occurred when you clicked too quickly through the UI and were faster than the asynchronous data fetching task.
Therefore I moved the session management to the LoginRegistrationActivity like this:
if (mAuth.getCurrentUser()!=null){
FirebaseDatabase firebaseDatabase = FirebaseDatabase.getInstance();
DatabaseReference mRef = firebaseDatabase.getReference().child("users").child("uid").child(mAuth.getCurrentUser().getUid());
//In case the user cancelled the app when filling out the survey for the first time
if (mRef == null){
LoginRegistrationActivity.this.startActivity(new Intent (LoginRegistrationActivity.this, SurveyGreetingActivity.class));
}
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
if (user!=null) {
GlobalUser.setToUser(user);
GlobalUser.setGlobalUid(mAuth.getCurrentUser().getUid());
}
Intent i = new Intent (LoginRegistrationActivity.this, MainActivity.class);
LoginRegistrationActivity.this.startActivity(i);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
By moving LoginRegistrationActivity.this.startActivity(i) to the onDataChange method, I ensure that the GlobalUser variables get instantiated before the MainActivity is started. There are probably still more elegant ways to do this.
I am quite new to java and android so be patient with me. I have an xml layout containing two buttons. One containing text of "previous" and the other "next". I also have a class containing array of strings which loops in an ascending order in a textView when a "next" button is clicked.
What i want is that i want the array to loop backwards from its current position when the "previous" button is clicked. Any ideas?
Question Class
// This file contains questions from QuestionBank
class Question{
// array of questions
private String mQuestions [] = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
// method returns number of questions
int getLength(){
return mQuestions.length;
}
// method returns question from array textQuestions[] based on array index
String getQuestion(int a) {
return mQuestions[a];
}
}
Main Activity.java
public class MainActivityextends AppCompatActivity {
private QuestionLibraryBeginner mQuestionLibrary = new QuestionLibraryBeginner();
private int mQuestionNumber = 1; // current question number
//initialising navigation buttons
private Button mPrevious;
private Button mNext;
private TextView mQuestionText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_beginner_review);
mPrevious = (Button) findViewById(R.id.previous);
mNext = (Button) findViewById(R.id.next);
mQuestionText = (TextView) findViewById(R.id.txtQuestion);
// receive the current question number from last activity by Intent
Intent intent = getIntent();
currentQuestionNumber = intent.getIntExtra("quizNumber", 0); // receiving the number of questions the user has attempted from previous activity
mNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// checking against total number of questions the user has attempted instead of total number of questions from Question Class
if (mQuestionNumber < currentQuestionNumber) {
updateQuestion();
}
});
mPrevious.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// i want it to loop backwards from here
}
});
// logic to update question from array
private void updateQuestion() {
if (mQuestionNumber < mQuestionLibrary.getLength()) {
mQuestionText.setText(mQuestionLibrary.getQuestion(mQuestionNumber));
mQuestionNumber++;
}
}
}
I would suggest to do this:
1) Rename updateQuestion method to nextQuestion
2) Create a method to decrease the mQuestionNumber like this:
private void prevQuestion(){
if(mQuestionNumber > 0){
mQuestionText.setText(mQuestionLibrary.getQuestion(mQuestionNumber));
mQuestionNumber--;}
}
Here's a solution accounting for bounds
mNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View view) {
showNextQuestion();
}
});
mPrevious.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View view) {
showPreviousQuestion();
}
});
private void showNextQuestion() {
showQuestion(1);
}
private void showPreviousQuestion() {
showQuestion(-1);
}
private void showQuestion(int increment) {
int newQuestionNumber = mQuestionNumber + increment;
if (newQuestionNumber >= 0 && newQuestionNumber < mQuestionLibrary.getLength()) {
mQuestionNumber = newQuestionNumber;
mQuestionText.setText(mQuestionLibrary.getQuestion(mQuestionNumber));
}
}
It can be done by just adding a flag to mention the move,
mNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
updateQuestion(true);
});
mPrevious.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
updateQuestion(false);
}
});
And the method would look like:
private void updateQuestion(boolean forward) {
if(forward && mQuestionNumber < mQuestionLibrary.getLength())
mQuestionNumber++
else if (mQuestionNumber>1)
mQuestionNumber--;
mQuestionText.setText(mQuestionLibrary.getQuestion(mQuestionNumber));
}
I would change the following methodes:
would remove mQuestionNummer++; from update question.
You can increment mQuestions directly in the onClickMethode of NextButton.
So you can implement your solution simply by decrement mQuestion-- in onClick of previous Button.
Code would look like this:
public class MainActivityextends AppCompatActivity {
private QuestionLibraryBeginner mQuestionLibrary = new
QuestionLibraryBeginner();
private int mQuestionNumber = 1; // current question number
//initialising navigation buttons
private Button mPrevious;
private Button mNext;
private TextView mQuestionText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_beginner_review);
mPrevious = (Button) findViewById(R.id.previous);
mNext = (Button) findViewById(R.id.next);
// receive the current question number from last activity by Intent
Intent intent = getIntent();
mNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mQuestionNumber < mQuestionLibrary.getLength()) {
mQuestionNumber++;
updateQuestion();
}
});
mPrevious.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// i want it to loop backwards from here
if(mQuestionNumber > 0){
mQuestionNumber--;
updateQuestion();
}
else
{}//don't do anything to prevent IndexOutOfBounds
}
});
// logic to update question from array
private void updateQuestion() {
if (mQuestionNumber < mQuestionLibrary.getLength()) {
mQuestionText.setText(mQuestionLibrary.getQuestion(mQuestionNumber));
}
}
}
You need to don't mess logic of your application with view logic, decouple them.
Just make class Question able to provide previous and next questions. Also according to oop principles (solid, grasp) fetch information from class and make decision outside is wrong, make class to do it's work. Oop it's about telling classes to do things, not work instead of them.
class Questions {
private int index = 0;
private String[] mQuestions;
//better to don't hardcode and provide questions in constructor
public Question(String[] questions) {
this.questions = questions;
}
//we don't need this method
int getLength(){
return mQuestions.length;
}
//provide human readable information about current position in question list
// when you want to provide this information to user introduce label field in activity
public String currentPosition() {
int questionPosition = index + 1;
int questionsLength = mQuestions.length;
return String.format("current question number is %d from %d" , questionPosition, questionsLength);
}
//return next question when available, if next not available returns last question from array
public String next() {
int lastIndex = mQuestions.length - 1;
if(index < lastIndex) {
index++;
}
return mQuestions[index];
}
//return current question
public String current() {
return mQuestions[index];
}
//return previous question when available, if previous not available returns first question from array
public String previous() {
int firstIndex = 0;
if(index > firstIndex) {
index--;
}
return mQuestions[index];
}
}
And how to use it in Activity:
public class MainActivity extends AppCompatActivity {
//better to don't hardcode here, but provide this class from
//constructor of MainActivity just like questions array provide
// to constructor in Questions class
private Questions questions = new Questions(new String[]{"q1","q2"});
private Button mPrevious;
private Button mNext;
private TextView mQuestionText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_beginner_review);
mPrevious = (Button) findViewById(R.id.previous);
mNext = (Button) findViewById(R.id.next);
Intent intent = getIntent();
//when create Activity populate question field with first question
mQuestionText.setText(questions.current());
mNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mQuestionText.setText(questions.next());
}
});
mPrevious.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mQuestionText.setText(questions.previous());
}
});
}
}
p.s. you may improve this code further in way to introduce Observer pattern, Activity is a view, Questions is model.