The question is about this code (in an Android application):
private Task<QuerySnapshot> getVotesFromDB() {
res = new int[]{0, 0, 0}; // a private class-member
return FirebaseFirestore.getInstance().collection("Votes")
.whereEqualTo("proposition_key", curr_proposition.getKey())
.get().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
String userChoice = (String) document.get("user_choice");
int choice;
switch (userChoice) {
case "against":
choice = 0;
break;
case "impossible":
choice = 1;
break;
case "agreement":
choice = 2;
break;
default:
throw new IllegalStateException("Unexpected value: " userChoice);
}
res[choice] ;
}
}
});
}
In general, the code reads some lines from a Firestore collection, and applies some "business logic" to them (counting the number of strings from each type). The business logic can, in the future, become much more complicated than just counting. So I am looking for a way to refactor the code, so that the business logic can be written and tested separately from the database. What I would like is to have some function of the form:
int[] countVotes(Generator<String> strings) {
res = new int[3];
(for String userChoice: strings) {
// Update res as above
}
return res;
}
that can be unit-tested without any need for a database connection. Then, the above function can be refactored as follows:
private Generator<String> getVotesFromDB() {
return FirebaseFirestore.getInstance().collection("Votes")
.whereEqualTo("proposition_key", curr_proposition.getKey())
.get().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
userChoice = (String) document.get("user_choice");
yield userChoice;
}
}
});
}
and run something like:
countVotes(getVotesFromDB())
The problem is, I do not know how to do this with the asynchronous function call. Is there a way to refactor the code in a similar or better way?
CodePudding user response:
You could collect the results into an array and then process that with a separate method. Then if the processing method is complex it can be unit tested in isolation for various different lists of results.
private void getVotesFromDB() {
FirebaseFirestore.getInstance().collection("Votes")
.whereEqualTo("proposition_key", curr_proposition.getKey())
.get().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
ArrayList<String> results = new ArrayList();
for (QueryDocumentSnapshot document : task.getResult()) {
String userChoice = (String) document.get("user_choice");
results.add(userChoice);
}
// assign the result to a class member
res = countVotes(results);
// do something to signal to the rest of the code
// that results have been processed (e.g. post to
// LiveData or call another "showResults" method)
}
});
}
Then any complicated counting logic can live separate from the firebase calls.
int[] countVotes(ArrayList<String> choices) {
int[] res = new int[]{0,0,0};
// count the votes
return res;
}