마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

함수 내부에서 원하는 순서대로 로그가 나오지 않는데 해결가능할까요?

0 추천

안녕하세요. 코드 실행순서...라고 해야되나? 이부분에 대해서 문제가 생겨 질문 올립니다.

 

현재 아래와 같은 파이어베이스에서 데이터를 들고와서 출력하는 코드를 짜고있습니다.

제가 원하는 것은 for문밖에서 nutr배열에서 각 인덱스 값의 합을 UI에 표시하고싶습니다. 그래서 처음에는 로그3자리에 dietView1.set~~~ 이런식으로 구현하였는데 앱을 실행시켜보니 로그2, 로그3, 로그1 순으로 실행되었습니다. 그래서 임시방편으로 아래 코드처럼 구성했습니다.

여기서 질문은 

1. 로그2,로그3,로그1 순서대로 실행되는 이유가 무엇일까요?

2. 로그1,로그2,로그3 순서대로 실행시킬수는 없을까요?

(onComplete가 끝나고 로그2,3 자리에 코드를 순서대로 짤수는 없을까요?)

3. for문안의 코드가 너무 노가다형식으로 지저분해 보이는데 이것을 깔끔하게 할수는 없을까요?

위 3가지가 질문입니다.

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()) {
                                                   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];
                                                       }
                                                   }
                                               } else {
                                                   Log.d(TAG, "Error getting documents: ", task.getException());
                                               }
                                               dietView1.setText("열량 : " + String.format("%.2f", nutr[0]));
                                               //생략
                                               for (int i = 0; i < 4; i++) {
                                                   Log.e("로그1", "" + nutr[i]);
                                               }
                                           }
                                       }
                );
        Log.e("로그2",""+nutr[0]);
    }
    Log.e("로그3",""+nutr[0]);
}
우혼 (260 포인트) 님이 2021년 11월 2일 질문

1개의 답변

0 추천
 
채택된 답변

제가 아는 법위에서 답을 드릴게요.

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같은 메소드를 통해 먼저 메인쓰레드로 전환하신 다음, 화면 업데이트를 하셔야 할 거예요. 안그러면 앱 크래시가 날 겁니다.

spark (224,800 포인트) 님이 2021년 11월 2일 답변
우혼님이 2021년 11월 2일 채택됨
그리고, 파이어베이스 구조를 보니 user별로 diet데이터가 위치해 있네요. 제가 알기로 이렇게 하면 파이어베이스는 JSON Tree라 데이터가 조금 많아지면 퍼포먼스가 확 떨어질 수 있습니다. user와 diet 를 같은 레벨이 되도록 데이터를 flat 하게 만드셔야 해요.
users
  - user 데이터
diets
  - diet 데이터

이런 식으로요.

 그리고 FireStore데이터를 변환할 때

City city = documentSnapshot.toObject(City.class);

위와 같은 식으로 해서 바로 오브젝트로 변환할 수 있는 걸로 알고 있는데, 이 방법도 사용해 보세요.
자세한 설명 감사드립니다.. 덕분에 문제 해결하였습니다.
댓글로 써주신 것도 적용 해보겠습니다!
...