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

변수의 원상복구에 대해

0 추천
public class ParticipantsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    ArrayList<Group> groupList;
    ArrayList<GroupId> groupIdList;
    FirebaseUser user;

    public ParticipantsRecyclerViewAdapter(){
        groupList = new ArrayList<Group>();
        groupIdList = new ArrayList<GroupId>();
        user = FirebaseAuth.getInstance().getCurrentUser();

        DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference();
        dbRef.child("users").child(user.getUid()).child("participatingGroups").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                groupList.clear();
                for(DataSnapshot dataSnapshot : snapshot.getChildren()){
                    GroupId groupId;
                    groupId = dataSnapshot.getValue(GroupId.class);

                    if(groupId!=null) {
                        groupIdList.add(groupId);
                        Log.d(TAG, "onDataChange: " + groupIdList.isEmpty());//1111111111111111111
                    }
                }
                notifyDataSetChanged();
            }
            @Override
            public void onCancelled(@NonNull DatabaseError error) {

            }
        });
        Log.d(TAG, "onDataChange: " + groupIdList.isEmpty());//2222222222222222222222222

주석에서 1에 해당되는 로그캣과 2에 해당되는 로그캣의 값이 서로 다릅니다...

D/ContentValues: onDataChange: false

D/ContentValues: onDataChange: true

즉 저 블럭을 빠져 나오는 순간 제가 groupId를 담아놓은 ArrayList는 작업했던 내용이 날아갑니다..

어디서 익명 함수는 외부변수에 접근을 못한다? 이런 비슷한 내용을 들어본 적이 있긴 한데 여태까지 그런거 신경 안써도 잘 되다가 갑자기 이러니까 막막하네요... 다른 분의 가이드라인을 영상으로 봐도 이 구조랑 별반 다를바 없어서 더욱 모르겠습니다... 어떻게 해야 저 ArrayList에 넣어놓은 내용들이 지워지지 않을까요?...

ㅇㅇㅇㅇㅇㅇㅇㅇ (1,000 포인트) 님이 2022년 4월 12일 질문

1개의 답변

+1 추천
 
채택된 답변

addValueEventListener 콜백에 대해서 이해를 하실 필요가 있습니다. 콜백은 Asynchronous(비동기)로 동작합니다. 따라서 addValueEventListener 밖에 있는 코드는 addValueEventListener 내부와 동기화가 되지 않습니다. 님의 경우는  2번 주석 부분이 먼저 실해이 된 후 Firebase에서 데이터가 도착하면 addValueEventListener.onDataChange이 실행되기 때문에 그렇습니다.

따라서 "저 블럭을 빠져 나오는 순간 제가 groupId를 담아놓은 ArrayList는 작업했던 내용이 날아갑니다" 이 말은 정확하지 않고, 사실은 파이어베이스에서 데이터를 받으면 제대로 초기화를 하고 있지만, 이걸 사용하고 있지 않는 것 뿐이죠.

따라서 주석 2는 지우시고요, 파이어베이스에서 받은 데이터 확인은 addValueEventListener 내에서만 하세요. 그리고 코드를 좀 더 가독성이 생기도록 살짝 수정하면,

public class ParticipatingGroupRepository implments ValueEventListener {

    interface Listener {
        void onSuccess(List<GroupId> groupIdList);
        void onCancel(DatabaseError error);
    }

    private static final USERS_PATH = "users"
    private static final PARTICIPATING_GROUPS_PATH = "participatingGroups"
    private final DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference();

    @Nullable
    private Listener listener;

    private ParticipatingGroupRepository() {
        initParticipatingGroups();
    }

    private void initParticipatingGroups(){
        dbRef.child(USERS_PATH)
              .child(getCurrentUser().getUid())
              .child(PARTICIPATING_GROUPS_PATH)
              .addValueEventListener(this);
    }

    public void setListener(@Nullable Listener listener) {
        this.lisetner = listener;
    }

    private FirebaseUser getCurrentUser() {
        return FirebaseAuth.getInstance().getCurrentUser();
    }
 
    

    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        handleSuccess(snapshot);
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        if (lisetner == null) return;
        listener.onCancel(error);
    }

    private void handleSuccess(DataSnapshot snapshot) {
        List<GroupId> groupIdList = new ArrayList<GroupId>();
        for(DataSnapshot dataSnapshot : snapshot.getChildren()){
            GroupId groupId = dataSnapshot.getValue(GroupId.class);
            if(groupId!=null) {
               groupIdList.add(groupId);
            }
        }

        if (lisetner == null) return;

        listener.onSuccess(groupIdList)
    }
}



public class ParticipantsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private final List<GroupId> groupIdList = new ArrayList<>();
    
    public void submitList(List<GroupId> groupIdList) {
        this.groupIdList.clear();
        this.groupIdList.addAll(groupIdList);

        notifyDataSetChanged();
    }

    ...
}


public class MainActivity extends AppCompatActivity implements ParticipatingGroupRepository.Listener {

    private ParticipatingGroupRepository participatingGroupRepository;
    private ParticipantsRecyclerViewAdapter participantsRecyclerViewAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        participatingGroupRepository = new ParticipatingGroupRepository();
        setupViews();

    }

    private void setupView() {
        participantsRecyclerViewAdapter = new ParticipantsRecyclerViewAdapter();
    }

    @Override
    public void onShow() {
        super.onShow();
        participatingGroupRepository.setListener(this);
    }

    public void onStop() {
        super.onStop();
        participatingGroupRepository.setListener(null);
    }

    @Override
    public void onSuccess(List<GroupId> groupIdList) {
        participantsRecyclerViewAdapter.submitList(groupIdList);
    }
    
    @Override
    public void onCancel(DatabaseError error) {
        // 에러처리
    }
}

 

RecyclerViewAdpater나 액티비티 같은 View는 네트워크로 데이터를 가져오거나 하는 로직이 있으면 좋지 않습니다. 해당 코드는 분리를 해야 추후에 테스트도 쉽고 수정하기도 쉬워집니다.  이건 특별한 부분도 아니고 뷰와 뷰와 관련이 없는 로직을 분리하는 것은 앱개발에서 기본적이 부분에 해당합니다.

spark (224,800 포인트) 님이 2022년 4월 12일 답변
spark님이 2022년 4월 12일 수정
전 여태 까지 정말 아무것도 모르고 사용하고 있었던 것 같네요
정말 감사합니다 이렇게 까지 자세히 올려주시고...
올려주신 예시와 충고를 보고 더 이해도를 높여나가겠습니다
코드를 약간 수정했습니다. 이유는  다른 리스너를 사용하면 이전 코드가 맞을 것 같은데, addValueEventListener를 사용하고 있어서 addValueEventListener가 여러번 호출되면 안될 것 같아서 입니다. 확인해 보세요.
감사합니다 콜백이라는 개념도 이제 처음 알았네요
Glide 클래스를 사용하면서 이미지가 로딩중임에도 어플리케이션은 프리징 없이 정상적으로 작동하는 이유가 궁금했는데 콜백 함수의 존재 때문이였군요
...