제가 아는 법위에서 답을 드릴게요.
1. 로그2,로그3,로그1 순서대로 실행되는 이유가 무엇일까요?
db.collection(...).get().addOnCompleteListener(...)에 들어가는 오브젝트를 콜백(Callback) 이라고 합니다.Call back인 이유는 말 그대로 db의 동작이 비동기이기 때문에 언제 완료될지 모르므로, 완료되면 addOnCompleteListioner 전달된 인터페이스의 오브젝트를 통해 결과값을 받기 위해서입니다. 콜백을 세팅한 쪽에서는 db 의 동작이 완료될 때 결과를 받을 수 있게 되는 mechanism입니다. 따라서 이 콜백 밖에 위치한 "로그2", "로그3"는 먼저 출력이 되지만, 콜백 안에 있는 "로그1"은 db 의 동작이 완료되고 나서야 출력이 되는 것입니다.2
2. 로그1,로그2,로그3 순서대로 실행시킬수는 없을까요?
할 수 있습니다. 제일 쉬운 방법은 로그2, 로그3를 addCompletedListner 안으로 위치시키거나 콜백을 메소드 바깥에서 주입하는 겁니다. 하지만 이걸 현재 있는 순서대로 출력시킬려면 RxJava같은 라이브러리를 사용하거나 Thread.join같은 조금 복잡한 과정을 거쳐야만 합니다. 따라서 현재 로그2, 로그3는 별로 의미없는 로그 보입니다.
public void updateDiet(Calendar date) {
...
for (String s : array) {
db.collection("users/" + user.getUid() + "/" + "diet/" + getDate + "/" + s)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
...
for (int i = 0; i < 4; i++) {
Log.e("로그1", "" + nutr[i]);
}
Log.e("로그2",""+nutr[0]);
Log.e("로그3",""+nutr[0]);
}
});
}
}
3. for문안의 코드가 너무 노가다형식으로 지저분해 보이는데 이것을 깔끔하게 할수는 없을까요?
코드를 작은 단위로 쪼개시고 분리하세요. 예를 들자면, 가장 간단하게는 success 와 failure블록을 바깥으로 빼세요.
public void updateDiet(Calendar date) {
//날짜 받아오기 생략
float nutr[] = new float[5];
float tempData[] = new float[5];
Arrays.fill(nutr, (float) 0.0);
Arrays.fill(tempData, (float) 0.0);
for (String s : array) {
db.collection("users/" + user.getUid() + "/" + "diet/" + getDate + "/" + s)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
userDataFetched(task, tempData, nutr);
} else {
userDataFetchFailure(task.getException());
}
updateNutritionUi(nutr);
//생략
for (int i = 0; i < 4; i++) {
Log.e("로그1", "" + nutr[i]);
}
}
});
Log.e("로그2",""+nutr[0]);
}
Log.e("로그3",""+nutr[0]);
}
private int userDataFetchFailure(@NonNull Exception exception) {
return Log.d(TAG, "Error getting documents: ", exeption);
}
private void userDataFetched(@NonNull Task<QuerySnapshot> task, float[] tempData, float[] nutr) {
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG, document.getId() + " => " + document.getData().get("탄수화물"));
tempData[0] = Float.parseFloat(document.getData().get("에너지").toString());
//생략
for (int i = 0; i < 4; i++) {
nutr[i] = nutr[i] + tempData[i];
}
}
}
private void updateNutritionUi(float[] nutritios) {
dietView1.setText("열량 : " + String.format("%.2f", nutritios[0]));
}
userDataFetched의 내부도 동작 단위의 작은 단위로 쪼개면 더 읽기 쉬워집니다. (저는 안드로이 스튜디오의 리팩토링 메뉴를 주로 이용합니다. 위의 코드도 그렇게 한 거구요.) 여기서 중요한 것은 변수이름, 메소드명 등을 이름을 보고도 무슨 일을 하는지 알 수있게끔 이해하기 쉬운 이름을 주라는 것입니다. 이게 코드를 좋게 만들 때 정말 중요한 원칙입니다. 의외로 많은 초보분들이 이 부분을 중요하게 여기지 않더라구요. (암튼 이게 첫번째로 중요합니다.)
해당 코드를 빌드해 볼 수가 없어서 정확하지는 않은데, Firebase의 콜백이 백그라운드 쓰레드에서 돌아간다면, 화면 업데이트는 메인쓰레드에서만 허용이 되기 때문에 addOnCompleteListener 안에서는 바로 화면 업데이트가 되지 않습니다. runOnUiThread같은 메소드를 통해 먼저 메인쓰레드로 전환하신 다음, 화면 업데이트를 하셔야 할 거예요. 안그러면 앱 크래시가 날 겁니다.