Skip to content

나나공('나보다 나무늘보가 공부 열심히 한다') 트러블 슈팅

JI HUN LEE edited this page May 9, 2023 · 29 revisions

1차 리팩토링

  • 홈 화면 BottomNavigation + supportFragmentManager() -> BottomNavigation + jetpack navigation 으로 migration

  • viewBinding 메모리 누수 방지 onDestroyView _binding = null

  • accessToken을 viewModel에서 사용하기위해 repository에 preference를 주입받아서 사용하도록 구조 변경(레퍼런스 참고)

  • 디자인 관련 화면 피드백 반영(radius, shadow, 빈 공간, 글씨 크기 반영), 새로 추가해주신 화면 추가 (예정 강의 상세화면)

  • Activity에 선언된 변수들 viewModel + livedata로 생명주기와 관련없도록, 안전하게 관리 → DataBinding으로 변경하여 뷰에 데이터를 연결하는 코드들 처리

  • onResume 에서 api 호출한거 onStart로 이동 (onResume은 자주호출되는 콜백이므로 연산량이 많으면 안됨 (아니면 viewModel 에 init에 처리)

  • 각 화면애서 반복적으로 사용되는 함수, BaseActivitiy/Fragment/ViewModel 이용하여 제거

  • activity와 Fragment 의 생명주기 내에서 변수 바인딩하는 작업, bindingAdapter 로 부분 대체

  • 함수 네이밍 통일 → init, bind → set

  • 투데이 화면 Arc progressbar 커스텀 뷰 제작


2차 리팩토링

  • viewModel databinding (onClickListener -> onClick="{()->vm.navigateTo()} → intent 와 같이 context 가 필요한 click method 의 경우는 어떻게 대처할 것인지 공부 → 방법은 알았는데 고치려면 대대적인 공수가.. 하면 되지 → recyclerView Adatper 부분 제외 완료

  • 강의 추가 화면 Jetpack Navigation 적용 → 구현 하려면 navigation show & hide → custom navigation 을 구현해야함 최신버전 대응이 안되는 이슈 → SharedViewModel + StateHandle을 통한 데이터 공유의 방법으로 해결~~

  • Update Validation 예외처리 해야함..
    → 이미 들은 강의수보다 수정한 강의수가 적으면 안된다 라던가 → 0개일때 다른 것을 바꾸면 버튼이 활성화 되어서 그거 토스트메세지 띄우는 형식으로 막아줘야 한다던가, 이부분 예외처리가 미흡했다.

  • inputForm 들 관련해서 liveData로 처리하는 방법 → button의 활성화/비활성화를를 liveData로 MediatorLiveData <-- keyword

  • 투데이, 강의목록 화면도 databinding, livedata (recyclerview와의 연동)

문제) -> 해결

강의 등록 파트 네비게이션으로 변경하려고 함 → 네비로 변경하면 acitivty에서 api불르면 안됨 (타이밍이슈. 네비게이션을 사용하면 activity는 entry point의 목적으로만 사용가능) 첫번째 프래그먼트에서 api 불러서 activity에 변수에 저장시킴 (이럴 경우 첫번째 프래그먼트로 돌아올경우 다시 api를 호출해서 새로고침됨 그리고 강의 등록 api를 호출할때 cast 오류나서 앱이 죽음 → viewModel에서 데이터를 관리하는 형태로 바꾸지 않으면 네비게이션으로 전 처럼 정상적인 진행 불가능? -> ㅇㅇ navigation은 기본적으로 fragment 를 replace 하는 구조 → viewModel과 liveData 를 통해 변수들 상태관리 선행 작업 마무리 후 진행 그런데 상태에 따른 ui와 관련된 코드들은 어쩌지 상태를 통한관리? → 이게 LiveData를 쓰는 이유


리팩토링 말고 해야할 작업 - 기능 추가

  • 강의 초과 수강 기능 추가 (하루 할당량 보다 더 많이 들을 수 있게 해서 조기 종료 할 수 있도록, 나공이와의 대결 승리)
  • 매일 특정 시각마다 알림을 받을 수 있는 기능 추가

보안 부분) kakao API key, google api key firebase 등등

  • gradle properties → local properties 로 이동, 둘의 차이 확인

  • Livedata 를 stateFlow, shared로 변경 → now in android repository 보고 병렬적으로 수행되는 여러 플로우 핸들링 적용 (MediatorLivedata -> flow Combine)

  • 두개의 api를 동기적이게(A->B) 호출하는것이 아닌, 동시에 병렬적으로 호출하여 성능 개선 (A. B ->,타이밍 이슈 안나게)

  • 네트워크 코드 개선

    • SharedPreference to DataStore (SharedPreference 는 곧 deprecated 될 예정)
    • 에러에 대한 핸들링 코드 개선
  • 패키지 분리, repository 따로 패키지 만들어 올바른 패키지 구조 확립

  • 로그인 화면 MVVM 패턴 적용

  • 뷰모델에서 string Resource 를 context 를 사용하지 않고 핸들링(stringResourceProvider)

  • Model 과 Api Response 의 모호한 분리 해결

  • 각 화면마다 대응되는 state data class 들 정리 - viewModel 내부에 담는 것이 공식문서의 방식이긴하다.

  • main 브랜치에 실수로계속적 푸시한거 develop 브랜치로 옮기기 → 깃, 깃허브 강의를 통해 학습 후 해결 → 레몬님의 도움으로 rebase 를 통해 해결 → develop 브랜치에서 main 브랜치의 커밋 cherry pick -> main 브랜치 reset hard 로도 해결할 수 있음

  • 인터넷 연결이 끊겼을때 핸들링

  • 강의 등록 플로우의 ux 를 높히기위한 강의 이름 클립보드 복붙 기능 -> 안드로이드에서 기본 제공

  • textfield 테두리 색이나 포커스같은것도 livedata 또는 stateFlow로할 수 있을거같은데 그럼 지금같이 하나 포커스를 수동으로 풀어주는 코드는 없어질거같은디

  • saveStateHandle 이 반드시 필요한 데이터와 필요하지 않은 데이터를 구분 솔직히 한번 뿌려주고 마는 변수들은 saveStateHandle 필요없다

  • 자동 로그인 처리 방법 바꾸니까 자동로그인시 화면이 깜빡거리는 이슈

  • Single Event function naming convention 준수

  • 기존의 activity 로 만든 화면 fragment 로 최대한 migration 하고 navigation으로 연결 → navGraphViewModel (SAA 패턴 적용)

    → 상세화면, 수정화면 먼저, 로그인 액티비티, 웹뷰 액티비티도 마저 진행,splash 화면도 splash screen api 를 적용하여 splash activity 제거

    → 등록화면 → 상단 툴바가 사라지는 이슈 → 공통으로 쓰는 툴바니깐 inclue 하는 형식으로 만들어줘야하나 → 해결 → include 로 맥일 경우 setNavgationOnClickListener 를 찾을 수 없는 이슈 → toolbar 에 layout 달아줘서 binding.id.id 로 접근 가능

    → 강의 등록 완료시 앱이 종료되지 않고 lessonListFragment 로 돌아오도록

    → 강의 등록이후 강의 등록화면이 backStack 에 화면이 남아있는 이슈, listFragment가 백스택에 2개 있던 이슈 해결 → popUpTo=”listFragment” popUpToInclusive = true 로 해결 → 이게 맞는 방법인건지.. -> 맞음

    → 홈화면에서 뒤로가기를 눌렀을때 앱이 꺼지지 않는 이슈 →로그인 popUpTo 와 popUpToInclusive 모두 설정해주어서 해결 → popUpTo=login, popUpToInclusive = true

  • Domain Layer - Usecase 도입 → vm의 부담을 줄여주기 → 각각의 뷰모델에서 어떤 유스케이스를 사용하는지(어떤 기능을 수행하는지) 한 눈에 파악할 수 있는 장점

  • recyclerview adapter 의 비즈니스 로직, viewModel 로 이동 (어댑터에 뷰모델을 전달하는 방법으로 해결) ~ ing → 공식문서를 통한 고찰 (권장하는 방식이 아님)

  • Loading handling 방식 개편 → UnLoading State를 제거하는 방향으로

  • 멀티 모듈화 (Usecase Entity 도입, 각 레이어의 해당하는 모델(Presentation - item, Domain - entity, Data - model)이 필요, 플로우는 브런치 안드로이드 아키텍처 컨퍼런스 영상 참조)

  • viewModel 의 ~state 라고 네이밍한 이벤트 핸들링 변수들, 변수 네임 명사형태로 변경 → ~Event 로 통일

  • xml 내에 onClick → onSingleClick 함수로 변경 → postDelayed 형태를 코루틴으로 코드 변경 가능한가?(진행 중)

  • Google Analytics, firbase crashylitics 연동 - 맥북에서 나나공 파이어베이스 작업을 하려면 이메일 인증을 해야한다. 2차 인증을 해야함, 데탑으로 연동함

  • 알림 관련해서 너무 불친절하다 알림을 활성화로 변경할 경우 알림을 줘서 몇시에 어떤 알림이 간다는 것을 설명해주는것이 좋을 것 같다. → 전달 드림

  • 강의 조기 종료한 경우 지난 강의에 들어가는데 지난강의에 해당 아이템을 누르면 진행중인 강의의 그것과 똑같이 뜬다 -> 종료된 강의에 대해선 상세화면으로 진입하지 못하도록 우선 처리

  • 투데이 화면에서 오늘들을 강의가 없는 경우 나공이 이미지가 나오지 않음 → 이미지 나오도록 수정

  • 강의 시작일, 완료일관련 아직도 버벅임이 있다. 시작일을 완료일을 설정하기 이전에 커스텀하게 설정할 경우 토스트메세지가 출력된다던가, 한번 등록일이 골르면 그 다음부터는 반응이 없다던가 → 강의 시작일을 먼저 커스텀하게 설정할 경우 토스트 메세지가 출력된다. → endDate 의 stateFlow 가 Date() 로 초깃값이 설정되어있기 때문, 커스텀 설정(오늘 날짜 보다 늦게) 하면 토스트 메세지 출력 → 강의 시작일을 건드리지 않고 완강 목표일을 설정한 뒤에, 강의 시작일을 변경하면 토스트 메세지가 출력된다. → 처음에만 출력되고 시작일을 한번더 바꾸면 출력되지 않는다 → 완강 목표일을 직접 설정한 뒤에 한번더 스피너에서 직접 설정을 눌르면 캘린더가 다시 뜨지 않는다. → 일주일 과 같은 다른 선택지를 누르고 다시 직접 설정을 눌러야 달력이 뜨는 구조

  • 성공 실패 도장 디자인…→ 피그마에 덧글 남김.. 넘 소극적인가.. 한번 더 남김

  • 앱에서 사용하는 툴바들 데이터바인딩의 형태로 전부 include 형식으로 바꿀수도 있을 것같음.. (스타일이 달라서 안되나..?)

    → 툴바내에 데이터바인딩 함수도 넣으려면 어떻게 해야하는지 고민 -> 해결

  • 다이얼로그를 호출하는 함수 네이밍 문제 → showDialogEvent sharedflow → viewmodel.showDialog → view에서 showDialog (뷰모델과 뷰의 함수가 이름이 겹처 뷰모델내의 함수가 호출되는 이슈)

    → 뷰모델에서는 navigateToDialog 뷰에서는 showDialog 라고 컨벤션을 지정하여 해당 문제를 해결

  • 인터넷 연결 없을 떄 인터넷 연결 해주세요 화면이 나오도록 추가 → 베이스뷰에 넣으면 좋을듯 ~ ing → 베이스뷰에는 btn clicklistener 를, 뷰는 include로 모든 뷰에 삽입! → 한꺼번에 모든 처리를 다 할 생각하지말고 만만한 화면부터 적용시켜주자 btn_retry 이벤트랑 ! → include 뷰 데이터바인딩 적용방법 학습 → 강의 등록화면, 강의 업데이트 화면에도 → toolbar를 가리지 않아야 함 → Visible, gone 처리 하는 것이므로 xml 내에서 처리되도록 변경~~

  • 인앱 업데이트 라이브러리 적용 → 분기 나누지말고 즉각 업데이트만 적용하면 좀 쉽게 할 수 있지 않을까? 그렇게 규모가 큰 앱도 아니고

  • 바텀시트 다이얼로그 같은 것들 corner radius style로 적용하기

  • splash screen api 적용 이슈 (30버전이하 크래시 이슈.. → 해결)

  • devideId repositoryImpl(data layer)에서 context 를 주입받아 api request body 에 넣어주는 방식으로 변경

  • NestedScrollView + Recyclerview 로 이루어진 뷰 → 개선 → 리사이클러뷰 상단에 위치한 뷰(텍스트, 이미지 등)를 recyclerview 의 Header or ViewType 로 만들어서 NestedScrollView 를 제거하여 RecyclerView 성능 개선

  • Ui data class 를 통해 데이터바인딩하기 Lesson, Member, LessonDetail, etc

  • 진행중인 강의가 몇개인지 표시하기 → 중요 Feature

  • back button 도 navigationIcon 의 방법으로 바꿀까 → 이미 적용된 것

  • UDF 위반 코드 개선 → viewmodel 함수 호출 → 구독 성공 → (viewModel 내의 Data 변경 ← 이부분) → view 갱신 → 어떻게 개선해야됨? → 뷰모델에서 success 로 response를 받을 경우 해당 블록 내에서 데이터 변경, 초기화 다 한 다음에 → 뷰의 uiState 의 변경을 구독

이슈)

  • Authenticator 가 적용되지 않음

    • 임의의 값을 accessToken 에 대입할 경우 404 forbidden
    • 빈값을 넣을 경우 “” → 401 Unauthorized → 과거버전 동작 확인 → 과거버전엔 authenticator 정상 동작 → retryLimitcount 변수 제거하여 해결
  • Success 일때에 대한 함수들 호출이 로그에 출력이안됨 - 실행이 안되는거같음; → emit을 안해준거였음 → 해결

  • 인터넷 연결 끊겼을 경우에 대한 핸들링 → 인터넷 끊겼을때에 대한 화면 추가

  • appcompat → Material → 불필요한 확장 함수 제거 → ~ing

  • ssaid Datasoure에서 핸들링 → 일단은 그대로 → respositoryImpl 에 applicationContext 주입 받아서 ssaid 추출하는 방식으로 해결

  • style 속성 적용으로 불필요한 background drawable 파일 제거 → ~ing

  • 툴바가 필요한 화면에 툴바 만들기

  • Gradle to KTS

  • →toml 파일을 이용한 version catalog 적용

  • fcm 토큰 저장 로직 정리 (로컬에 fcm 토큰을 저장하지 않는 경우)

    • 처음 앱을 설치한 경우
      • 서버에 해당 아이디로 fcmToken이 저장되어잇는지를 조회 → 없음
      • 생성 후 서버에 저장 → fcm 정상 작동
      • 다시 앱을 실행 한 경우 서버에 fcm토큰이 있는지 조회 → 있음 → fcm 토큰 발급 api 및 서버에 저장하는 api를 실행하지 않음 → fcm 정상 작동
    • 앱을 삭제하고 다시 설치한 경우 (새로운 fcm 토큰이 발급되는 케이스)
      • 서버에 해당 아이디로 fcmToken이 저장되어있는지를 조회 → 있음 (하지만 해당 토큰은 이제 쓸모 없는 토큰) → fcm 토큰 발급 api 및 서버에 저장하는 api를 실행하지 않음→ fcm 작동 X → 새로운 토큰을 발급받아서 이를 서버에 저장하는 로직이 필요 (대체)
  • fcm 토큰 저장 로직 정리 (로컬에 fcm 토큰을 저장하는 경우)

    • 처음 앱을 설치한 경우
      • 서버에 해당 아이디로 fcmToken이 저장되어잇는지를 조회 → 없음
      • 생성 후 서버와 로컬에 저장 → fcm 정상 작동
      • 다시 앱을 실행 한 경우우 로컬에 저장된 fcm 토큰이 있는지 조회 → 있는 경우 서버에 fcm이 있는지를 조회하는 Api를 호출하지 않음 → 무분별한 api 호출 방지
      • 로그아웃 한 경우 auth 토큰과 함께 fcm 토큰 또한 preference에서 제거
      • 다시 앱을 실행한 경우 로그인 → 로컬 fcm 토큰 조회 없음 → 서버에 조회 → 있음 → 있는 경우 다시 로컬에 저장하는 로직이 필요)
    • 앱을 삭제하고 다시 설치한 경우 (새로운 fcm 토큰이 발급되는 케이스)
      • 로컬에 조회 없음 → 서버에 fcm 토큰이 있는지 조회 → 있음 (하지만 해당 토큰은 이제 쓸모 없는 토큰) → fcm 토큰 발급 api 및 서버에 저장하는 api를 실행하지 않음→ fcm 작동 X → 새로운 토큰을 발급받아서 이를 서버에 저장하는 로직이 필요 (대체)
  • 문제(결론) → 해결

    • 앱 진입시 마다 토큰을 발급받아 서버에 전달하는 방식 채택
  • 로그인 화면을 nav_home 에 포함시키기

    • 로그인 화면의 구성

      로그인 액티비티, 로그인 바텀 시트 프래그먼트, 개인정보 처리 확인 바텀 시트 프래그먼트, 개인정보처리 웹뷰 액티비티

    개인정보 처리 액티비티는 이미 프래그먼트로 만들어둔게 있음

    문제 1) 해당 프래그먼트의 뒤로가기는 manage 화면으로 넘어가는 것으로 구현되어있는데 어떻게 개인정보 처리 바텀시트로 넘길까

    앞에서 이동해왔을때 argument가 존재하냐 하지 않냐 등으로 따져서 이동을 분기해볼 순 있을 것 같다.

    문제 2) 어느 화면을 start_Destination으로 해야할 것인가

    로그인 화면으로 해서 auto login 시 빠르게 today 화면으로 넘기는 방향으로 하는게

    ㅇㅇ 이 방법으로 하면 기존의 로직을 크게 바꾸지 않을 수 있음

    문제 3) 바텀시트가 safenavigate로 기존의 애니메이션의 느낌으로 잘 넘거갈까? - 해봐야알것같음~~

    기존엔 show close 의 형식이었는데 close는 어떻게 구현해야하지? 원래 show close 둘다 login activity가 담당했었는데 전부 프래그먼트가 되면..음~~

    home activity에 해당 작업을 이전해줄수있을까?~~

    바텀시트, 네비게이션 연동에 관한 학습이 필요하다.

    스플래시 액티비티는 이제 아예 버려도되나?

    내가 너무 한번에 바꾸려는것 같기도하다

    • 우선 로그인 액티비티쪽 파트를 네비게이션으로 마이그레이션을 먼저 한 뒤에 그 후에 네비게이션을 합치는 방향으로 해보자

    그럴려면 이제 근데 로그인 액티비티는 entrypoint의 역할을 해야됨 비즈니스 로직이 들어가선 안됨 그렇게 짤 수 있는가가 문제

    어쨌든 간에 loginFragment로 모든 비즈니스로직을 이관해야함 둘이 상반된 방향이 아니기 때문에 먼저 이렇게 하기로하자

    • 이제 registerBottomSheet 에 붙은 webviewfragment 만 제외하고 nestedGraph 로 넣어보자
  • BottomNavigationView + Navigation 관련 이슈

    • 메뉴를 통해 이동하고 나서 시스템 백버튼을 누르면 startdestination으로 바로 이동되지 않고 백스택이 하나씩 빠지다가 마지막에 startdestination에 도착되고 종료된다

    • startdestination을 로그인 화면에서 투데이 화면으로 변경해줄 경우 시스템 백버튼을 통해 바로 백스택을 거치지 않고 투데이 화면으로 이동할 수 있지만, 로그아웃을 한번 더 해주고 다시 홈화면에 진입할 경우 initNavigatio이 호출이 되지 않아서 startdestination이 초기화된다 ? → 그 전과 같이 바로 투데외화면으로 이동 → 앱 종료 로직이 깨진다

    • 커스텀하게 setUpWithController 함수를 만들어보았는데 메뉴아이템과의 연동이 되지 않는다(시스템 백버튼을 누르면 메뉴 아이템의 변화가 이뤄지지 않음(클릭 상태))

    • popUpTo, popUpToInclusive의 개념이 잘 잡히지 않아서 그런걸지도 현재 화면에 보여지고있는 화면은 백스택에 포함되지 않는다 로그아웃 액션의 popUpTo와 popUpInclusive 설정에 문제가 있는 듯 하다. → 로그아웃 한다음에 시스템 백버튼을 누르면 manage 화면으로 돌아가는 이슈

      → 로그인 화면으로 돌아가는게 다른 popUpTo 작업과 다르게 동작하는 이유 → 애초에 백스택에서 로그인 화면이 빠져있기때문에 popUpTo = @id/login ← 이게 무의미한 설정이 되버리는 것 것 같다.

      → logout 함수가 비동기라 today_lesson까지 전부 popup 처리해서 loginFragment 로 돌아갈 경우 토큰이 삭제 제거되지 않아서 바로 자동로그인 되어 today_lesson 프래그먼트가 열린다 → 그런데 그러면 쨌든 화면이 켜지고 나서 토큰이 지워져야되는거 아닌가?

      안지워지는것 같은데 → 토큰 삭제함수가 실행이 안됨 → 메소드가 실행되기이전에 프래그먼트가 없어져서 실행 자체가 취소된건가

      이러면 jwt토큰 만료시에도 똑같은 상황이 발생할텐데..? 매우 위험한 버그

    • 또한 homeActivity는 앱이 시작할때 create 되면서 initNavigation 함수를 호출하는데 로그인 후에 startDestinaion를 코드로 바꿔줄수가 있는 걸까 ? → 이건 구독 개념이 들어가야할것같은데

      → 홈액티비티에 이벤트를 수집해줘야할듯

      → 아니다 액티비티는 그저 EntryPoint의 역할을 해줘야한다.

      → 기본적으로 startDestination을 login 이 아닌 today_lesson으로 해주고 → todaylesson 에서 로그인 상태를 체크해서 로그인이 되어있지 않으면 login으로 보내주면 될 것같다.

      → 두 화면이 다른 뷰모델을 사용하는데 어떻게 check를 바꿔주지

      → 두 뷰모델을 공유해야하나, BaseViewModel에 LoginState를 관리하는 건 소용이 없음

      → autoLogin 체킹을 today_lesson에서 해주면 되잖아 → 이거 false 나면 login으로 가고 이러면 화면 버벅임도 없을거고 베스트네 → 해결

      → 또한 startDestination을 today_lesson으로 변경해주었기 때문에 자동적으로 백스택에는 startDestinaion만 남겨지게 됨, 따로 백스택에 startDestinaion만 남도록 설정해주지 않아도 된다.

  • 내가 알던 popUpTo / popUpToInclusive 와 프로젝트에 실 적용된 popUpTo / popUpToInclusive 가 이질적이라 느꼈던 이유

    → actionAtoB 를 통해 이동하면 새로운 스택이 하나 생성되어서 위로 쌓이는 형태이기때문이다

    → 따라서 B를 미포함 하고 pop 설정을 하고 이동하면 A → B (이동하면서 B)삭제 → A → 결국 A → A 의 형태 따라서 시스템 백버튼을 통해 한번 뒤로가기를 수행하면 바로 밑에 한번더 똑같은 화면이 들어가 있던 것이다!.

    → 결론은 순환형 이동시 A → B , B→A를 따로 따로 만들지 말고 그냥 B→A 는 뒤로가기와 동일하게 구현하는게 낫다!

  • 로그인 부분 이슈

    기존의 로직: 신규가입의 경우 첫 로그인 성공시 isNewMember: true가 떨어지면 → 개인정보동의 화면이 뜬다 → 동의하고 시작하기를 누르면 메인 화면으로 이동하게 된다. → 취소를 선택하면 첫 로그인 화면으로 다시 이동된다.

    개인정보동의 화면)

    문제 발생)

    → 이때 다시 로그인을 하게 되면 이미 어쨌든 기존의 로그인을 한 유저이기 때문에 isNewMember:False가 response로 넘어오고 개인정보 동의 화면이 뜨지않고 바로 메인화면으로 이동된다.

    문제를 해결하려면)

    → 취소를 눌렀을 경우 유저 로그인 기록 초기화? api를 호출하여 다음 로그인 api 호출의 결과의 response의 isNewMember가 다시 True로 넘어오도록 해줄 필요가 있을 것 같다.

    • accessToken 을 검사하는 api 추가하여 해결
  • 유스케이스 네이밍 컨벤션 통일 → get/fetch delete/remove

  • updateMemberDialog IllegalStateException 해결 (두번째 dialog open 했을 경우 앱이 꺼지는 문제) [The specified child already has a parent. You must call removeView() on the child's parent first (Android)](https://stackoverflow.com/questions/28071349/the-specified-child-already-has-a-parent-you-must-call-removeview-on-the-chil)

    → DialogFragment 로 migration 하여 해당 문제를 해결하고 Navigation으로 연결 및 databinding 하려고 했던 TODO 도 해결 → 버튼 관련 확장함수 제거, drawable resource 제거 까지 → 처음에 다이얼로그를 열었을 때, hint 가 화면에 출력되지 않는 이슈 해결

  • SlothDialog 도 SlothDialogFragment 로 전환 → onClickListner interface 를 어떤 방식으로 대체 할 수 있을 시 학습 → 각각의 독립적인 뷰모델을 가지는 dialogFragment 로 쪼개면 됨 → 강의 삭제 다이얼로그 관련 백스택 이슈 → dialogFragment 짜부 이슈 -> BaseDialogFragment 에서 size 설정

  • 강의를 삭제할때 한번 눌러도 다이얼로그가 꺼지지 않고 화면은 투데이화면으로 돌아오는 이슈 navigation action 관련 이슈같다. → 다이얼로그 관련 비동기 이슈? → 강의 목록 화면으로 돌아오지 않고 투데이화면으로 돌아오는 이슈 → 백스택 관련 → 에러 재현 강의를 수정하고 상세화면으로 돌아와서 삭제하는 경우 발생 → 삭제되었습니다 토스트메세지도 두번 출력됨 → 토스트가 두번 뜨는거는 뷰모델의 상태를 공유해서 그런것같다? → 독립적인 뷰모델을 가지는 화면으로 만들어야 할것같기도 → Dialog 를 DialogFragment 로 각각 하나씩 쪼개서 해결

  • 투데이 화면 클릭 이벤트 관련 리팩토링 → 뷰에서 뷰의 갱신을 위해 updateLessonCount 함수를 실행하는데 이 내부에서 viewModel의 함수와 어떤 버튼을 눌렀는지에 대한 enum class의 타입을 파라미터로 받는다 → 뷰모델 함수 호출의 결과로 (api호출) 받은 데이터와 기존에 함수의 파라미터로 받은 enum class의 타입 변수를 통해 다음 행동에 대한 조건문이 세워진다 → 따라서 뷰 내에서 viemodel의 함수를 호출하며 바로 결과를 받는 구조이다. withContext ~

    → 기존의 구조처럼 상태를 나타내는 상태변수의 갱신된 값을 통해 (successEvent) 뷰에서 수집하는 형태로 로직을 수정해보려고 했지만 같이 받아줘야하는 enum class의 타입 변수들이 recycler view 의 adapter 내부의 enum class 들이기 때문에 뷰에서 바로 접근을 할 수가 없다 그래서 어댑터의 onClick 익명함수를 구현하는 블럭 내에서 함수를 호출 해주고 있는 형식이다. → 현재 방식이 마음에 안들지만 더 나은 방법으로 고치는 방법을 아직까지는 모르겠다

  • hiltNavGraphViewModel 을 사용할때 이슈

    강의 목록 화면에서 처음으로 클릭한 강의의 id가 savedStatehandle 에 저장되어 다른 강의를 클릭하여도 이전 강의의 상세화면만 보여지는 이슈 lessondetailviewmodel을 viewmodels 로 초기화하면 deleteLesson뷰의 뷰모델을 hiltGraphViewModel 로 할 경우 앱이 죽음 두 뷰가 뷰모델의 상태를 공유하고 있지 않기 때문에 왜 처음 진입한 lessonDetial의 id만 저장되고 그 이후로는 갱신이 되지 않을까?

  • databinding 관련

    업데이트 맴버 화면에 edittext:hint는 절때 databinding이 적용이안되고(화면에 출력이 안되고) edittext:text는 잘만된다.. 이상하다 증말 → 정상이다. 초기값이 “” 이라 “” 로 바인딩이 되고 그 뒤로는 변하지 않는다. EditText 의 hint 의 기본 속성이 그럼

  • 또 다시 발생한 강의 등록에서의 문제

    first에서 second 로 갔다가 다시 first 로 돌아오면 카테고리가 빈칸으로 출력된다. 앱이 죽지는 않는데 아마도 뷰모델 내부에 init 블럭으로 api를 호출해서? 그런거같다. 맞음, startDate만 init에서 초기화해주는게 맞는 방법이다. 나머지는 FirstFragment 의 onStart 콜백에서 호

  • updateLesson 이슈 강의 개수를 수정할 때 뒤에 ‘개’가 정상적으로 붙지않음 개수, 원 수정시 발생 해결

  • flow 중간 연산자를 통해 lesson site, category response data를 변환하여 최종 형태로 바로 뷰에 emit 할 수 있게 변경 → map(), transform() 과 같은 연산자를 이용 → 모듈을 나누면서 mappter 를 통해 하는게 나을수도

  • 온보딩 튜토리얼

     1. 처음 로그인했을 경우 온보딩 튜토리얼 진행 (isNewMember = True)
     2. [메인화면, 1번째 화면] 으로 진입하고 플러스 버튼을 한번 누르면 [완료한게 없는 경우, 2번째 화면] 으로 이동
     3. 3번 클릭을 완료한 경우 [완료한게 없는 경우→ 완료한 경우, 3번째 화면]으로 이동
     → 여기서 다른 버튼들을 눌를 수 있는데 다른 버튼들을 누르는 경우의 대한 핸들링 
     → 막아놔야하는지, 아니면 그대로 둬도되는지 (강의를 등록해버릴 경우) -> 바텀 네비게이션 뷰를 hide
    
     4. 완강하기를 누름 → [진행할게 없는 경우 화면, 4번째 화면]으로 이동
     5. [진행할게 없는 경우] 화면에서 강의 등록하기를 누르면 강의 등록화면 이동(진짜 강의 등록 진행)
     6. 진짜 강의 등록완료 → [강의 목록, 5번째 화면] 으로 이동
     7. [강의 목록화면]에서 방금 진짜로 등록한 강의를 클릭하여 강의 상세 화면으로 이동 → 온보딩 튜토리얼 종료 (isNewMember = False)
    
  • 위로 보이는 나공이는 나중에 구현하고 아래 화면부터 먼저 구성을 해보도록 하자 → 화면 비율 때문에 모든 화면에서 대응이 불가능

  • 온보딩 관련) onBoardingAdapter 와 TodayLessonAdapter 분리의 필요성 ← 차이가 존재 상태관리를 더 세세하게 해야할 것 같다. 투데이화면 온보딩이 종료되었는가? + 강의 목록화면에서의 온보딩이 종료되었는가? 강의를 등록하고 왔는가? checkOnBoarding 해서 false 이면 투데이 화면으로 빠꾸, 이게 확실한 방법

    근데 그러면 true 떴을때 강의 목록에서의 checkDetail 화면을 계속 띄워주게 될 텐데 투데이 화면에서 온보딩 안끝났으면 아예 일로 넘어오지 못하게해야지 않나 check 해서 다시 돌아가게 빠꾸먹일까 그게 쉬울 것 같은데 근데 그러면 checkDetail OnBoarding 화면을 띄울 수 없음 onBoarding 이 Complete 되었을 때 띄운다면 매번 강의목록 화면에 진입할때마다 checkDetail 온보딩이 수행됨

    → 시스템 백 버튼을 막아버리는 것으로 해결

    fetchMemberInfo내에서 onBoarding이 종료되었는지 먼저 검사하고(fetchLessonList 도 마찬가지 종료가 되지 않았다면 빠꾸 종료되었다면 UseCase를 호출하는 식으로 구현해야 UDF를 위반하지 않는다

    강의를 등록하는 절차를 밟지 않아도 바텀 네비를 통해 강의 목록화면으로 이동하면 강의 목록화면 온보딩이 띄워진다 -> 온보딩 진행시 바텀 네비게이션 뷰 hide 그 상태로 투데이화면 가면 또 다시.. 마이페이지 갔다와도 초기화되고 전부 뜯어고쳐서 상태관리를 용이하게 만드는게 더 쉬울수도..?

    → 온보딩의 상태를 하나로 관리하지말고 따로 분리하도록 하자

    • 강의 목록 화면에 도달하였고, 상세화면에 진입했다가 뒤로 돌아왔는데 강의 목록 화면 온보딩 다이얼로그가 뜨는 이슈
    • 강의 목록 화면 온보딩이 띄워진 상태에서 온보딩 을 닫고 투데이화면 탭을 눌러도 눌러지지 않는 백스택 관련 이슈
  • 지금 보이는 맘에 안드는 것들

    • 전체적인 부분 stock market app 참고→ 네트워크 통신(repository, viewmodel) 에러 처리 코드 개선 가능성 다분히 존재해보임.

      scope 함수) with vs apply → binding.apply vs with(binding) → viewModel.apply vs with(viewModel) → 각각의 용도를 학습하여 적용

    • todaylesson

      • auto login → fetchTodaylesson ← UDF 위반

      • when(boolean) { true → else(?) → } 직관적이게 false

        → 아니면 굳이 true else 인데 when 으로 할필요가 있나 싶기도 함 -> 조건 분기가 2개인 경우 그냥 if else 로 해결

      • 조건식이 복잡한 경우 조건식을 변수화 해볼 것(isNotStarted, isFinished)

      • dialogFragment 코드내에 deprecated된 code 수정

      • SharedFlow event 상권님 블로그 참고해서 뷰에 코드 간략화 → 구글 공식 아키텍처 패턴을 고려 (UiState + StateFlow

      • LessonDetail 화면 chip button 이 내려와있다 → onBoading develop 에 merge 하고 바로 수정해야 → 운영서버 배포할때 이것도 할걸…

      • 운영서버 BASE_URL 이 다를때 화면에 인터넷 연결 불가 화면이 뜨는 이슈 (그냥 error 가 아니라 internet error 로 잡히는 이유

      • mapper 를 통해 data , domain, ui 에서 사용하는 모델을 분리 → 투데이, 강의 목록 화면에서 사용하는 모델의 사잇 변환과정을 간략화

      • 변수명의 list → Clean Code 위배 변수의 복수형으로 바꿔볼 것

      • navigation graph 가 너무 복잡한거같은.. nested nav graph 로 좀 분리를 할 수 없나도 고민해보자

      • 웹뷰 화면에도 인터넷 연결이 안되었을 경우 onReceivedError() 콜백을 통해 에러페이지 보여주기

      • splash screen 첫페이지 로드가 성공되고 나서 종료시키기 적용해보기(diary app 참고)

      • 뷰 내에서 .value 로 접근하는 경우 지양 -> 최대한 관련 코드 수정 중

      • .apply 는 값을 변경할 때 사용한다 적절하지 않은 사용 수정

      • string resource 분리, 및 이름들 수정

      • 리스트 깜박이던 문제 해결

      • Coroutine job 을 통한 생명주기에 호출되는 api 동시 실행 제어 → 빠르게 화면을 반복해서 진입할 경우 이전 api 호출이 종료되기 이전에 다음 api 가 호출될 경우에 대한 무한 로딩 이슈 해결

      • 화면 좌우 비율 관련…

      • retry 구현 관련 (abstract fun 이라고 선언해야지만 바디 없는 형태로 선언해둘 수 있다)

      • 2항 연산이면 when 제거

      • drawable 을 비롯한 다른 resources 도 stringProvider 처럼 resourceProvider 를 통해 뷰모델에서 다룰 수 있긴 하다

      • todayLesson 화면 app바 처럼 lessonList 화면도 수정 -> appBar elevation은 android:elevation 이 아니라 app:elevation으로 0dp 를 적용해주어야 그림자가 사라진다

      • DiffUtil 비교값 관련 이해안되는 부분 이해

      • 항상 update 를 통해 stateFlow 를 업데이트해도 되는건지 → 아니다 적절하게 취사 선택해야한다. 동시에 호출될 수 있는 가능성이 있는 경우만 update를 사용하는것으로

2023/2/6 회의에서 언급해야할 것들

  • 온보딩 관련

    • 온보딩이 튜토리얼 형식일 경우
      • 로컬 디비를 통해 온보딩 완료 여부를 저장하고 있었는데, api의 형태로 바꿀지 고민

        앱을 삭제하고 다시 시작할 경우 다시 온보딩 튜토리얼이 진행되는 이슈

      • 온보딩 진행 중에 다른 버튼들을 누를때의 대한 핸들링을 해줘야 한다.

        강의 채워보기 튜토리얼을 하고 있는데 강의 목록 화면으로 가서 강의를 등록해버릴 경우, 온보딩이 끝나지 않았기 때문에 투데이 화면에서 등록한 강의를 볼 수 없다.

      • 화면 대응 불가능, 위에 올라와있는 이미지와 밑에 있는 화면의 버튼, 아이템 등의 크기 비율을 모든 기기에 대해서 대응을 할 수 없다.

  • recyclerview 를 사용하는 화면 관련 변경 가능한 사항(pagination이 지원되지 않아 api 호출시 모든 데이터를 내려주는 경우에)

    • 기존의 방법

      API 호출 → 데이터를 성공적으로 가져왔는지를 뷰에서 구독(Result.Success) → success 이면 뷰에서 list를 세팅 (각 타입의 리스트를 따로따로 뷰에서 선언하여 조건에 맞춰 데이터를 각각 담음)

      → 각각의 리스트를 각각의 어댑터에 부착하여 → concatAdapter로 합침 → adapter를 리사이클러뷰에 부착

      클릭이벤트 전달 방식) adapter 내에 enum class를 선언하여 각각의 adapter마다 상수를 파라미터로 넣어서 해당 파라미터(ex)Adapter.BodyType, Adapter.ClickType) 와, ‘뷰’에서 구현한 클릭이벤트(비즈니스 로직)를 adapter에 생성자에 담아서 전달하여 처리

    • 개선

      1. 각각의 리스트를 뷰가 아닌 뷰모델에서 관리하자

        변경사항이 존재하면 stateFlow.update 확장함수를 통해 변경

      2. 클릭이벤트(비즈니스로직)을 뷰모델내에서 구현한뒤에 해당 뷰모델 함수를 adapter의 파라미터(람다)로 전달 (viewModel 을 adapter에 직접 전달하지 말고, 해당 뷰모델 함수만을 전달). 뷰홀더에 존재하는 클릭리스너 제거

      3. Adapter들을 lazy 하게 초기화해주자

      4. Adapter 에 파라미터로 들어간 enum class 상수를 제거하고 adapter 내의 viewtype 으로 분리 (adapter 내부의 companion object 를 사용하여 adapter의 파라미터를 제거)

      5. TODO → 온보딩을 위에 얹기 (UDF 를 위반하지 않는 로직으로 → api 호출→ 뷰에서 api 를 결과를 성공적으로 받았는지 뷰에서 구독 → 다시 뷰모델내의 api 함수 호출 (x))

      6. TODO → 하나의 뷰에서 너무나도 많은 이벤트를 구독 (상권님 글 5번째 방법을 참고하여 sealed class로 이벤트를 관리하는 방법을 적용하여 initObserver함수 개선) -> 리스트를 뷰모델 내에서 관리

  • remote 를 통해 받아온 list<데이터>를 뷰모델내에 변수(리스트)로 관리하자

  • update 가 이뤄질 경우 api를 호출하고 변경된 list 변수에 접근하여 업데이트해주자.

  • 해당 list를 화면에서 구독하는 형태이므로 업데이트된 것이 바로 반영될 것이다.

    → adapter.submitList 의 형식으로

  • 이러면 매번 각각의 list 를 새로 생성해서 데이터를 조건에 따라 넣고 어댑터에 다시 리스트들을 장착해줄 필요가 없다.

  • 또한 로직이 간편해져서 updateLessonCount 함수를 간소화 할 수 있다.

  • 다만 새롭게 강의가 추가될 경우에 대해 이를 반영해주기 위해선 다른화면에서 있다가 다시 돌아왔을때는 fetchLessonList api를 호출해야한다.

  • 하나의 리스트내에서 특정 값을 검사하여(투데이 화면일 경우에 untilTodayFInished) 이를 통해 뷰타입을 나누고 뷰타입을 통해 하나의 어댑터로 모든 리스트를 관리할 수 있어 concatAdapter 를 사용하지 않아도 된다. ← 추가적인 학습이 필요

  • 강의 목록 내에서는 3가지의 뷰타입으로 뷰홀더를 나눠서 하나의 어댑터로

  • 이제 문제는 아이템 내부의 클릭이벤트는 어떤식으로 개선할 수 있느냐 인데

  • 각 상태의 대한 header viewType 으로 하여금 concatAdapter 를 제거하는 방향으로..? 어렵다

    concatAdapter 를 계속 써야되는건지, 쓰지 않는 방향(emptyLessonAdapter 와 TodayLessonAdapter 로만 분리)으로 완전 변경해야하는건지

    이럴거면 list 도 분리하면 안된다 하나의 리스트로 관리하고 diffUtil 을 통해서 뷰홀더를 구분해줘야한다. → 가능한 건가?

  • 근데 지금 짠대로 만약에 run 하면 뷰들이 뒤죽박죽 섞여있을 것 같다. 정렬이 수반되어야할 것 같은데.. 그게 가능한건가 (채팅과의 차이점)

    각 뷰타입들이 모여있어야 하는데…

2023/2/20 회의에서 언급해야할 것들

  • 백엔드 투데이화면 오늘 아직 끝내지 않은 강의 (untilTodayLessonFinished 가 false)인 것을 우선으로 정렬해주세요.

    lessonId 는 이제 후 순위

  • 투데이 화면에서 강의 등록시, 온보딩에서 강의 등록시 등록완료 후 투데이 화면 바텀 탭이 눌리지 않는 이슈

    → 강의 등록 네비게이션이 강의 등록 완료시 기존의 강의 목록 화면까지의 백스택을 제거해주면서(popUpTo) 종료되는데, 이 경우엔 백스택에 강의 목록 화면이 없음

    → 따라서 해당 화면이 백스택에 없으므로 백스택 자체가 제거되지 않았기 때문에 발생

    → 시스템 백버튼을 누르면 강의 등록 화면으로 다시 이동됨

    → 근데 왜 투데이 화면 바텀 탭 자체가 눌리지 않는지 그 이유는 모르겠음..(singleTop 설정을 해줘야하는건지)

    → 조건부 탐색 을 활용해서 popUpTo 설정에 들어갈 화면을 ‘동적’으로 설정해줘야 해결 가능할듯

    조건부 탐색  |  Android 개발자  |  Android Developers

    → 아님, safeargs 를 사용해서 각각의 화면에서 강의 목록화면으로 이동할때 인자를 전달해서 그 인자를 통해 어떤 화면에서 왔는지 판별→ 상황에 맞게 강의 완료 후 이동하는 화면을 분기처리해주면 된다

  • 시행착오

    viewmodel 에서 SSOT 를 만족시키기 위해 uiModelList 내의 데이터를 직접 수정하는 형식으로 짜봤는데 화면이 불필요하게 깜빡거리고 프로그래스바의 애니메이션도 부드럽지않고 끊겨서 데이터를 이원화 시키는 방향으로 다시 돌림..

    fun updateOnBoardingItemCount(count: Int) {
            if (count == 1) {
                increaseOnBoardingUiItemCount()
            } else {
                decreaseOnBoardingUiItemCount()
            }
        }
    
    private fun increaseOnBoardingUiItemCount() {
            lateinit var newItem: TodayLessonResponse
            lateinit var currentItem: TodayLessonResponse
            val currentList = _onBoardingUiModelList.value.toMutableList()
            var flag = false
    
            for (i in currentList.indices) {
                if (currentList[i] is OnBoardingUiModel.OnBoardingDoingItem) {
                    currentItem = (currentList[i] as OnBoardingUiModel.OnBoardingDoingItem).todayLesson
                    break
                }
            }
            Timber.d("$currentItem")
            Timber.d("$flag")
    
            val isOutOfRange = currentItem.presentNumber >= currentItem.untilTodayNumber
            if (isOutOfRange) return
    
            val isFinished = currentItem.presentNumber + 1 == currentItem.untilTodayNumber
    
            if (isFinished) {
                newItem = currentItem.copy(presentNumber = currentItem.presentNumber + 1, untilTodayFinished = true)
                flag = true
            } else {
                newItem = currentItem.copy(presentNumber = currentItem.presentNumber + 1)
            }
    
            currentList.replaceAll {
                if (it is OnBoardingUiModel.OnBoardingDoingItem) {
                    OnBoardingUiModel.OnBoardingDoingItem(newItem)
                } else {
                    it
                }
            }
    
            // _onBoardingUiModelList.value = currentList
            _onBoardingUiModelList.update { currentList }
            Timber.d("${_onBoardingUiModelList.value}")
            if (flag) setOnBoardingItem()
        }
    
    private fun decreaseOnBoardingUiItemCount() {
            lateinit var newItem: TodayLessonResponse
            lateinit var currentItem: TodayLessonResponse
            val currentList = _onBoardingUiModelList.value.toMutableList()
            var flag = false
    
            for (i in currentList.indices) {
                if (currentList[i] is OnBoardingUiModel.OnBoardingDoingItem) {
                    currentItem = (currentList[i] as OnBoardingUiModel.OnBoardingDoingItem).todayLesson
                    break
                }
            }
            val isOutOfRange = currentItem.presentNumber == 0
            if (isOutOfRange) return
            val isNotFinished = currentItem.presentNumber == currentItem.untilTodayNumber
    
            if (isNotFinished) {
                newItem = currentItem.copy(presentNumber = currentItem.presentNumber - 1, untilTodayFinished = false)
                flag = true
            } else {
                newItem = currentItem.copy(presentNumber = currentItem.presentNumber - 1)
            }
    
            currentList.replaceAll {
                if (it is OnBoardingUiModel.OnBoardingDoingItem) {
                    OnBoardingUiModel.OnBoardingDoingItem(newItem)
                } else {
                    it
                }
            }
            // _onBoardingUiModelList.value = currentList
            _onBoardingUiModelList.update { currentList }
            Timber.d("$flag")
            if (flag) setOnBoardingItem()
        }
    
    private fun setOnBoardingUiItem() {
            Timber.d("setOnBoardingItem() 호출")
            _onBoardingUiModelList.update {
                if (_onBoardingList.value.isEmpty()) {
                    listOf(
                        OnBoardingUiModel.OnBoardingHeaderItem(OnBoardingType.EMPTY),
                        OnBoardingUiModel.OnBoardingTitleItem(OnBoardingType.EMPTY),
                        OnBoardingUiModel.OnBoardingEmptyItem
                    )
                } else {
                    onBoardingList.value.groupBy {
                        it.untilTodayFinished
                    }.values.map { todayLessonList ->
                        todayLessonList.map { lesson ->
                            if (lesson.untilTodayFinished) {
                                OnBoardingUiModel.OnBoardingFinishedItem(lesson)
                            } else {
                                OnBoardingUiModel.OnBoardingDoingItem(lesson)
                            }
                        }
                    }.flatMap { todayLessons ->
                        when (todayLessons.first()) {
                            is OnBoardingUiModel.OnBoardingDoingItem -> {
                                todayLessons.toMutableList().apply {
                                    add(0, OnBoardingUiModel.OnBoardingTitleItem(OnBoardingType.DOING))
                                }
                            }
                            is OnBoardingUiModel.OnBoardingFinishedItem -> {
                                todayLessons.toMutableList().apply {
                                    add(0, OnBoardingUiModel.OnBoardingTitleItem(OnBoardingType.FINISHED))
                                }
                            }
                            else -> return
                        }
                    }.let { lessons ->
                        val isFinished = lessons.find { it is OnBoardingUiModel.OnBoardingDoingItem } == null
                        when {
                            isFinished -> {
                                lessons.toMutableList().apply {
                                    add(0, OnBoardingUiModel.OnBoardingHeaderItem(OnBoardingType.FINISHED))
                                }
                            }
                            else -> {
                                lessons.toMutableList().apply {
                                    add(0, OnBoardingUiModel.OnBoardingHeaderItem(OnBoardingType.DOING))
                                }
                            }
                        }
                    }
                }
            }
        }
    • 조기 완료한 강의 lessonStatus 를 PAST 로 변경해달라고 요청드려야 함

    • 투데이 화면 이슈 login → onBoarding 완료 → fetchTodayLesosnList 호출 플로우에 collect 함수에 로그를 찍어본 결과 다른 화면 (바텀네비와 연결된 강의목록, 관리화면)에 이동했다가 다시 돌아오면 로그가 n 배씩 호출되는 이슈 (api 자체는 1번만 호출되도록 막아놔서 1번만 호출) → 근본적인 원인은 순환형 함수 호출 (funA() 함수 내에서 funB() 호출, funB() 함수 내에서 funC() 함수 호출) → 설계적 측면에서 A 함수가 B,C 함수에 대한 행위의 책임까지 전부 지게 됨 → 단일 책임 원칙 위배 → 테스트 측면에서 A 함수를 테스트하기 위해서는 B,C 까지 처리해야하는 문제가 생김 → 개발 측면에서 A -> B -> C 로 이어지는 Call stack 이 단일 stack 이라면 상관없지만 어딘가에서 B를 호출하는 순간 순환 호출 구조가 성립될 가능성이 있음

    • 온보딩 강의 업데이트와 투데이화면 강의 업데이트의 로직이 동일한 것 같은데 투데이화면에서 uiModel.todayLesosn.presentNumber ++ — 는 viewModel 내에 관리되는 리스트의 원소를 직접 업데이트하는 반면에 온보딩화면에서의 uiModel.todayLesson.presentNumber ++ — 는 viewModel 내에 관리되는 리스트의 원소를 직접 업데이트하지 않는 이슈 두 코드의 차이를 확인 후 어느쪽이 잘못된 것인지 확인하고, 잘못된 쪽을 확인하여 주말내에 머지 후 배포를 수행해야 함

      → 투데이 화면의 updateLessonCount 가 비동기 (네트워크 호출) 함수 이기 때문에 발생했던 이슈(타이밍 이슈) → viewModel 의 updateLessonCountItem 함수를 Result.Success 일 때 호출하는 것이 아닌, 메소드 호출시 바로 호출되는 식으로 변경하여 (동기적으로 호출) 해결

    update 적용사항)

    • 온보딩 추가
    • 통계 기능 추가
    • 투데이화면에서 강의를 등록하고 나서 투데이 화면으로 돌아갈 수 없는 문제를 해결(하단 탭의 버튼을 눌러도 반응이 없던 문제)
    • 알림 목록 화면 추가 (Paging 라이브러리 적용은, api response 수정 후에 적용)

  • 모듈 분리 (data/domain/feature)

    • 멀티 모듈 브랜치에서 작업 후 develop 으로 돌아오면 모듈이 생성되어 있는 이슈.. checkout 해서 그런가 되게 불편하다
    • 다음 회의 이전까지 모듈화 해놓기 그 이후 기능 추가
    • diaryApp 강의 참고
    • domain 모듈에 안드로이드 의존성을 포함하지 않기 위해 자바/코틀린 라이브러리로 도메인 모듈 생성 → paging-runtime → paging-common 라이브러리로 대체 → hilt-android → java-inject 라이브러리로 대체
  • 모듈 분리 이후 이슈

    • 잘 작동하던 온보딩 관련 작업의 문제가 생김
    • 처음 온보딩 완료 후 로그아웃을 한 뒤에 다시 로그인을 누르면 바로 온보딩화면으로 이동되는 이슈 → accessToken 이 존재하지 않아 retry 하다가 too many follow-up request 발생(왜 로그인화면으로 이동하는 다이얼로그가 뜨지 않는거지)
    • 그 이후에(온보딩 종료 후) 앱에 로그인하여 다시 앱에 진입해 로그아웃하면 로그인버튼을 눌러도 계속 로그인화면으로 유지된다 → 온보딩 완료 로직을 로그인 화면으로 끌고와서 그런가.. sharedflow 관련 이슈일듯..
    • hiltNavGraphViewModel 로 두화면을 연결했을 경우 생명주기가 어떻게 되는 것인지 → 상권님 글(MVVM 의 이벤트를 처리하는 6가지 방법)의 6번 방법(EventFlow 확장함수)을 이용했더니 바로 해결된거같다.. ㄷㄷ
  • 첫 로그인 이후 → 온보딩 → 마이페이지 → 세팅 → 로그아웃 이후 백버튼 눌렀을때 다시 세팅화면으로 돌아가는 이슈 → 해당 플로우에서는 이미 투데이화면이 백스택에 존재하지 않기 때문에 로그아웃(투데이화면을 포함한 모든 백스택 제거)시 백스택에 투데이화면이 존재하지 않기때문에 전체 백스택이 지워지지 않아서 생기는 문제 → 기존의 로직대로 롤백을 했을 경우 온보딩으로 넘어갈때 투데이화면을 백스택에 남겨두면 온보딩 종료시 바텀탭에 투데이화면을 클릭해도 투데이화면으로 이동하지 않는다… 시스템 백버튼을 누를 경우에는 투데이화면이 출력되긴하는데 빈화면이다.. 대체 뭐가 문제일까 → 의도된 동작이라고 한다 → startDestination이 A화면(투데이화면)인 경우, A 화면이 활성화 되어있으며, 사용자가 뒤로 가기 버튼을 누르지 않는한 백 스택에 A화면이 계속 유지 됨 -> 그런데 A화면에서 nested navigaiton graph 로 진입하여 최종적으로 B화면(강의 목록 화면)으로 이동하면 A 화면은 백스택에 남아 있으므로 메뉴의 A 아이콘을 다시 클릭해도, A화면으로 이동하지 않음. 이는 navigation component 의 표준 동작, 원하는 동작을 수행하려면 B 화면으로 이동할 때 A 화면을 백스택에서 제거해야지 메뉴의 A 아이콘을 통해 A화면으로 이동할 수 있다.

    → EventFlow 확장함수를 적용해본 결과 다시 투데이화면에 도달할 경우 login 되어있는지 체크와, onBoarding이 완료되었는지 체크를 하지 않는 것을 확인할 수 있었다. (여기가진 바라던 동작, 따라서 fetchtodayLessonList api 도 호출이 되지 않는다..(바라지 않는 동작)) -> 로그아웃 로직을 변경하는 것으로 해결 -> 투데이화면까지 (popUpTo = 투데이화면, popUpToInclusive = true) 모든 백스택을 제거하는 것이 아닌, 현재 존재하는 모든 백스택을 제거하도록 변경

  • renovate 나나공 프로젝트에 연동해보기

    • 내 레포지토리가 아닌 레포에 (depromeet 그룹에 속하는 레포) renovate 연동하는 방법 알아보기
  • expired 다이얼로그 관련 이슈

    현재 expired 화면으로 잘 이동하는지 모르겠지만 만약 이동한다고 해도 그 화면에서 logout 를 호출하는데 애초에 logout Api 를 호출하려면 accessToken 을 필요로 한다. 앞뒤가 맞지않는다 → 해당 화면에서는 그냥 백스택을 지우고, dataStore 에 존재하는 만료된 토큰을 지우고 로그인화면으로 돌아가야한다.-> 적용 완료 → 이 화면에 도달하는것 자체를 테스트해볼 필요가 있다.. → 도달하지 않는다 (인터넷 연결 체크 화면으로 이동한다)

    → accessToken 와 refreshToken이 존재하지 않을때 retry를 20번 수행하고 (too many request 가 뜨는 이유) 인터넷 체크 화면으로 가버리는 것 같다. authenticator 가 잘못 설정되어있는 듯 하다.

  • 토스트 에러메세지가 3번씩 출력되는 이유는 대체 뭘까

  • 어쩌다 알아낸 authenticator 제대로 동작되는지 확인하는 방법

    → logout 상황에서 백스택을 날릴때 현재 백스택에 존재하지 않는 화면 id 을 넣는다 → 그러면 백스택이 날아가지 않는다 → 로그아웃한뒤에 시스템백버튼을 눌러서 이전 화면으로 돌아온다 → 해당화면에 api 를 호출시켜 어떤식으로 화면의 에러처리가 동작되는지 로그를 통해 확인해본다 ㅋㅋ

  • 업데이트 적용 사항) 종료돤 강의에 대한 상세 화면으로 이동 이벤트 제거 알림 목록으로 이동할때 받은 알림이 없을 경우의 대한 에러 해결(nullable 로 데이터가 내려와서 data type 을 nullable 하게 설정) 투데이 화면에 존재하는 숫자에 대한 부가 설명 추가(들어야 할 강의 수)

  • 업데이트 해야할 사항) 달력 기능 추가하기 회원 탈퇴 기능 구현, 첫 로그인일 경우에만 온보딩 튜토리얼로 이동하도록 로직 변경 프로필 이미지 변경 기능 구현 알림 목록 최하단 아이템에 온보딩 튜토리얼로 진입할 수 있는 아이템 추가

  • 강의 등록 화면에서 사용했던 Calendar, Date API → ZonedDateTime API 로 변경 → formatter 를 사용하여 기존의 사용했던 문자열 변환 확장함수를 대체

  • 앱 버전을 확인하여 플레이스토어로 이동시키는 기능 추가

    • firebase remote config 에 앱 버전을 명시
    • 해당 버전과 현재 설치된 앱의 버전이 다를 경우 플레이스토어로 이동시키기
    val intent = Intent(Intent.ACTION_VIEW).apply {
        data = Uri.parse(
                "https://play.google.com/store/apps/details?id=com.example.android")
        setPackage("com.android.vending")
    }
    startActivity(intent)
  • buildSrc 모듈 제거

  • gson to kotlin serialization migration

  • DataSource 추가

    • data/network → data/source/remote 로 변경, data/preferenes → data/source/local/preferences 로 변경
  • DataSourceImpl 에서 entity 로 mapper 를 통해 변환하지 말고 repositoryImpl 에서 변환해야한다.

    • login과 logout 을 하나의 Service, DataSource,Repository 에 묶기 위해, Member → UserProfile Login → UserAuth 로 변경
  • Data Layer 반복되는 response 처리, exception 처리 함수 확장함수로 처리

  • 회원 탈퇴 기능 구현

  • 온보딩 튜토리얼이 최초 로그인 시에만 실행되도록, 알림 목록화면에서 다시 온보딩을 진행할 수 있도록하는 아이템 추가하기

Clone this wiki locally