개발/쏠라로이드(Solaroid)

02.26.진행상황 : 카메라 화면전환 // 즐겨찾기 기능 추가 (PopupMenu, 프래그먼트 재구성, Room - ViewModel - LiveData 심화 학습, FragmentTransaction) // Material Design 일부 적용 (color,theme,icon etc)

란서 2022. 2. 26. 15:35

Commit

 

02.13 카메라 화면 전환 기능 추가.

02.13.Commit

cameraSelector를 FRONT와 BACK CAMERA를 두어 UI 내 카메라 전환 버튼을 클릭 시, 전환되게끔 구현.

            val cameraSelector = if(!cameraConverter) {
                CameraSelector.DEFAULT_BACK_CAMERA
            } else {
                CameraSelector.DEFAULT_FRONT_CAMERA
            }
            
            

    //버튼클릭 시 카메라 셀렉터 전환. (BACK <-> FRONT) , false->BACK, true->FRONT
    private val _cameraConverter = MutableLiveData<Boolean>(false)
    val cameraConverter : LiveData<Boolean>
        get() = _cameraConverter

 

Camera 전환

 

 

 


02.21~02.26 즐겨찾기 기능 추가.

commit
즐겨찾기 on/off

해당 프래그먼트에서만 나타나는 바텀 네비게이션을 사용하여 즐겨찾기 버튼을 노출.

즐겨찾기 버튼을 누르면 현재 보고 있는 포토티켓을 업데이트. -> Room Database Dao Update 수행.

 

즐겨찾기

상단우측에 위치한 Filter 버튼을 클릭하면 팝업 메뉴가 나타난다. 

포토티켓을 최신순  또는 즐겨찾기 만으로 나열할 수 있는 옵션을 선택할 수 있다.

 

 

즐겨찾기 기능 구현에서의 문제점과 해결과정

 

즐겨찾기 기능을 구현하는데 있어서 가장 어려웠던 점은 database로부터 즐겨찾기만 표시된 데이터를 얻었을 때

    //최신순 정렬
    @Query("SELECT * FROM photo_ticket_table ORDER BY id DESC")
    fun getAllPhotoTicket() : LiveData<List<PhotoTicket>?>

    //즐겨찾기 표시된 포토티켓만 정렬
    @Query("SELECT * FROM photo_ticket_table WHERE favorite == :favorite ORDER BY id DESC")
    fun getFavoritePhotoTicket(favorite:Boolean) : LiveData<List<PhotoTicket>?>


얻은 LiveData<List<PhotoTicket>>을 곧바로 현재 UI에 적용하여 보여주는 것 이었다.

 

구현하기 전에는 상당히 간단했을 것 같은 문제가 생각한데로 이루어지지 않아 시간이 많이 걸렸다.

 

다음은 이를 구현하기 위해 했던 실패 사례를 적어보겠다. 

 

  1. viewModel 내에서 val photoTickets 프로퍼티를 var photoTickets으로 읽고 쓰기가 가능한 프로퍼티로 수정하고 팝업 메뉴의 선택 옵션에 따라 database.dao를 통해 반환되는 LiveData<List<PhotoTicket>> 값으로 변환하는 방식.

    실패 이유 : 선택 옵션에 따라 photoTickets 프로퍼티에 값을 할당하는 것 까진 성공하였으나 해당 프로퍼티의 데이터를 Observe하고 있던 관찰자는 활동하지 않는다. -> 이전에 할당되어있던 LiveData의 값이 변경된 경우가 아니기에 작동하지 않음.

    따라서 해당 fragment가 재구성되어서 viewModel 값을 다시 얻어오는 경우에만 새로운 프로퍼티 값을 관찰하기 시작하여 원하는 데이터를 UI에 display



  2. MediatorLiveData 사용 - 처음부터 최신순LiveData와 즐겨찾기LiveData 프로퍼티를 만들고, MediatorLiveData에 add하여 사용하는 방식.

    실패 이유: 결국 LiveData는 내부의 데이터값이 변경되어야 Observer가 변경 여부를 알아채고 action을 취하는건데, 두 개의 LiveData를 모두 관찰한다고 하더라도, 팝업 메뉴의 옵션 선택은 결국 LiveData의 데이터를 변경시키는 것이 아니기 때문에 원하는 데로 작동되지 않는다. 

    -> 이와 같은 문제가 계속 영향을 미침.


  3. LiveData<LiveData<List<PhotoTicket>>>을 사용? 만약 LiveData<List<PhotoTicket>을 관찰하는 LiveData가 있다면 해당 값인 LiveData<List<~>>가 옵션 선택에 의해 변경되면서 가능하지 않을까? 라는 생각에서 구현.

    실패 이유:  LiveData<List<~>>값이 변경되는 것 까지는 관찰이 가능했다. 따라서 LiveData<LiveData<List<PhotoTicket>을 observe하여 해당 값이 바뀔 때 adapter.submitList() 를 호출하여 LiveData<List<PhotoTicket>의 value를 인자로 넘겨주는 방식을 구현하였다.

    될 줄 알았는데 LiveData<List<PhotoTicket>의 value값이 계속해서 null 이 나오는 것을 Log 기록을 통해 확인하였다. 왜 null값이 나오는지는 잘모르겠다. LiveData.value값을 분명 외부에서도 사용할 수 있는 것으로 아는데.. List<> 여서 였을까? 이유는 모르겠지만 어쨌든 실패.

    -> 다음에 왜 안됐었는지 좀 더 확인해봐야겠다.

대표적으로 위와 같은 실패 사례 외에도 여러 방식으로 시도를 하였으나 (인터넷 검색 포함) 원하는 결과를 찾지 못하였다.

그러던 중 SharedViewModel과 관련한 포스팅을 통해 해결책을 찾을 수 있었다.

 

해결과정


viewModel에 저장된 값을 이용해 프래그먼트끼리 데이터를 공유.


최신순 정렬을 하는 viewPager를 가진 프래그먼트
즐겨찾기 정렬을 하는 viewPager를 가진 프래그먼트

 

2개의 프래그먼트를 팝업 메뉴의 옵션 선택에 따라 이동하면서 프래그먼트를 재구성한다면 위 실패사례 중 1번의 방식을 사용하면서 item list를 보여줄 수 있다.

따라서 다음과 같은 프래그먼트 구조를 설계하였다.

SharedViewModel 개념을 이용하여 2개의 프래그먼트가 같은 viewModel을 공유하고, 팝업 메뉴의 옵션 선택에 따라 

"최신순 또는 즐겨찾기"을 선택 -> viewModel 내에서 현재 photoTickets의 프로퍼티 변경 ->  FragmentLately or Favorite 생성 -> viewModel로 부터 현재 photoTickets.value 값을 받아와 adpater의 item으로 설정

와 같은 과정을 거치게 된다.

-SharedViewModel 정리

https://ranseo.tistory.com/289

 

[Android/Kotlin] SharedViewModel (ViewModel을 이용해 데이터 공유)

개요 개인 프로젝트 Solaroid 앱 개발을 하면서 프래그먼트간 데이터 공유가 필요한 상황이 발생. 이를 위해 viewModel을 이용한 프래그먼트간 데이터 공유를 사용하였고, 이에 대해 간략하게 정리하

ranseo.tistory.com

 

이 후 ViewModelStore역할을 할 FragmentContainer를 추가하여 containerView 와 ChildFragmentManager, FragmentTransaction을 이용하여 Fragment전환.

 

이에 따라 PopUp Menu기능 또한 FragmentContainer에 구현.

 

최종 프래그먼트 구조

https://ranseo.tistory.com/290

 

[Android/Kotlin] FragmentTransaction 프래그먼트 트랜젝션

최근 상위/하위 프래그먼트를 다루기 위해 Fragment에 관한 공부를 다시 하던 중 프래그먼트 매니저와 프래그먼트 트랜젝션에 관한 내용이 잘 기억이 나지않아 간단하게 다시 포스팅하고자 한다.

ranseo.tistory.com

 

또한 다음은 프래그먼트 트랜젝션을 이용하여 FragmentContainer내에서 두 개의 Fragment를 교체하는 코드이다.

        viewModel.naviToLately.observe(viewLifecycleOwner, Observer {
           if(it) {
               val lately = childFragmentManager.findFragmentByTag(TAG_L)
               if(lately==null) {
                   childFragmentManager.commitNow {
                       add<SolaroidFrameLately>(R.id.fragment_frame_container_view, TAG_L)
                   }

                   childFragmentManager.commit {
                       replace<SolaroidFrameLately>(R.id.fragment_frame_container_view)
                   }
               }
               viewModel.doneNavigateToLately()
           }
        })

        viewModel.naviToFavorite.observe(viewLifecycleOwner, Observer {
            if(it) {
                val favorite = childFragmentManager.findFragmentByTag(TAG_F)
                if(favorite==null) {
                    childFragmentManager.commitNow {
                        add<SolaroidFrameFavorite>(R.id.fragment_frame_container_view, TAG_F)
                    }

                    childFragmentManager.commit {
                        replace<SolaroidFrameFavorite>(R.id.fragment_frame_container_view)
                    }
                }
                viewModel.doneNavigateToFavorite()
            }
        })