[팀 프로젝트]세 번째 코드 리뷰 (작성자-3)

12973 단어 androidkotlinandroid

코드 리뷰에서 제시된 의견 및 질문들!!

질문!
1. 왜 내 정보 부분을 이동한 후 새로운 Activity를 생성하여 구현하였나요?
2. Method라는 클래스를 왜 생성하였나요?
의견 !
1. onActivityResult 대신에 registerForActivityResult를 사용하는게 어떻냐
2. (내 정보란 구역을 MyInfoActivty로 변환하자고 결정이 되었을때) 내 정보부분에서는Fragment들을 FragmentManager.replace()를 사용할 수 도 있겠지만 이번 부분에서는 jetpack navigation을 사용해 보는게 어떻냐

왜 처음 부분에서 각 기능마다 Activity를 새로 생성하였는가?

처음 개발을 시작했을 때는 MainActivity를 기반으로 Bottom Navigation bar을 통해 Fragment들을 이동하며 홈, 찜, 지도, 내 정보를 이동하였다 프로젝트의 기본 기능이 GPS와 인터넷을 통해 사용자의 위치정보를 불러오는 만큼 MainActivty에 사용자의 현재위치와 Bottom Navigation bar을 구현했기에 Fragment로 내 정보부분에서 퍼져나간다면

이미지와 같이 빨간색 네모 부분들이 이 후의 Fragment에서도 생겨 화면이 가려지는 일이 발생했다. 그렇기에 각 기능의 출발점이 되는 부분에서 Activity를 새로 만들어 그 안에서 Fragment들을 replace해 화면전환을 실행했다라고 설명을 했고 그 날 이 문제에 대해 팀원들과 이야기하여 내 정보 부분을 Navigation에서 Fragment로 가는게 아니라 내 정보 부분을 Activity로 구현하여 위치 정보와 Bottom Navigation bar을 가릴 수 있게 하였고 back 로직을 만들어 다시 MainActivity로 돌아갈 수 있도록 수정하였습니다.
<기존 코드>

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/my_info"
    app:startDestination="@id/subFragment">
    
    <Fragment
    android:id = "@+id/subFragment"
    android:name="com.example.YUmarket.screen.myinfo.MyInfoFragment"
    anroid:label="subFragment"/>

  

<변경 코드>

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/my_info"
    app:startDestination="@id/subFragment">

    <activity
        android:id="@+id/subFragment"
        android:name="com.example.YUmarket.screen.myinfo.MyInfoActivity"
        android:label="subFragment" />

Navigation Graph에서 다음과 같이 변경 해준다.
이렇게 변경하여 Navigation Bar에서 내 정보를 누른다면 MyInfoActivity가 동작하면서 Fragment를 불러온다.

Method라는 클래스를 왜 생성하였나요?

Method에는 현재 알림 설정을 켜고 끌수 있는 AlertDialog 메서드가 들어가 있습니다. 여기서 켜기 끄기를 선택하는 순간 System에서 현재시간을 받아와 Toast메시지로 "현 시간부로 알림 설정을 켰습니다."를 출력하는데 이러한 시간 데이터를 받아오고 if문을 통해 로직을 이루는게 어찌보면
MVVM 패턴에 위배가 될 수 있다고 판단하였습니다.

MVVM 패턴

MVVM 패턴에서 View의 역할은 UI 요소에 들어갈 데이터를 관리하고 사용자의 이벤트를 받는 것이다. 데이터 같은 경우 Model에 존재하는데 ViewModel이 데이터를 받아와 View에 전달만 해준다. 수정은 불가능(LiveData) 하게 말이다.

즉 View는 사용자에게 보여주고 받는 역할만을 해야하는데 View가 System에서 데이터를 직접 받아와 보여준다는 것이 패턴 위배라고 생각했고 그렇기에 Method라는 별도의 클래스를 만들어 필요로 하는 View에서 불러와 사용하려고 했습니다.

아래의 코드는 해당 기능을 필요로 하는 View에서 선언을 하여 데이터를 불러옵니다.

 private fun popUp() {
        requireContext().let { it1 -> Method().popUp(it1) }
    }

여기서requireContext와 getContext와의 차이를 여쭤 보실 수 있는데
getContext()는 Nullable이고 requireContext()는 NonNull Annotation이 붙어있습니다.

일반적으로 Fragment에서 context에 접근하게 되면 null이 아닌 값을 반환하지만 Fragment가 Activity에 attach 되지않는 경우 등의 예외가 발할 수 잇으므로 Fragment.getContext()가 항상 NonNull인 것은 아니다. 따라서 requireContext()를 통해 Context가 Null이 아님을 보장받을 수 있기때문에 requireContext()를 사용했습니다.

사용하는 언어에 따라 선택해서 사용하시면 될 거 같습니다.

Java의 경우는 getContext()를 사용하면 되지만 Kotlin이라는 언어 특성상 Null이 아닌 Context를 전달해주려면 requireContext()를 사용해야 합니다.

<의견 부분>

onActivityResult 대신에 registerForActivityResult를 사용하는게 어떻냐

2021년 하반기에 사용자의 스마트폰에서 저장소에서 이미지를 불러와 프로필 이미지를 변경시키는 방법으로 onActivityResult를 사용했습니다. 그렇기에 이번에도 자연스럽게 사용하게 되었지만
제가 gitHub에 올렸던 코드를 본 팀원이 onActivityResult대신에 registerForActivityResult를 사용해봐라라고 말해주었고 바로 기술문서들을 찾아봤습니다.

알고보니 onActivityResult는 deprecated되어졌다 그 이유들은 다음과 같았다.

1.프로젝트의 크기가 커진다면 onActivityResult의 코드가 길어진다.
프로젝트가 커지면서 액티비티의 수도 늘어가며 메서드 안에 들어가는 코드의 양도 굉장히 방대해진다. (각 액티비티에서 발생하는 콜백 함수안에서 분기처리를 해줘야함)
2.Permission 요청이 불편하다.
startActivityForResult, onActivityResult와 메서드 이름과 역할은 다르지만 로직은 같다.
임의의 requestCode를 주어 분기 처리를 해주는데 이 과정이 requestCode에 따라 분기를 나눠주고 -> 권한이 있는지없는지에 따른 동작을 수행 -> 없다면 Permission 요청 -> 있다면 동작실행 등 동작이 너무많이 분기되고 코드가 복잡해진다.

onActivityResult와 registerForActivityResult 코드의 예시를 보겠다.

<onActivityResult코드>

```
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if(galley == requestCode){
        if(resultCode == RESULT_OK){
            val dataUri : Uri? = data?.data
            try {
                val bitmap : Bitmap = MediaStore.Images.Media.getBitmap(context?.contentResolver,dataUri)
                binding.profileImage.setImageBitmap(bitmap)
            }  catch (e: Exception) {
                Toast.makeText(context,"$e",Toast.LENGTH_SHORT).show()
            }
        }
    }
}
  임의의 resultCode를(해당 동작을 하는) 선언해주고 갤러리에 접근하게 된다.
  
<registerForActivityResult 코드>

//변수 선언
private lateinit var getResultImage: ActivityResultLauncher<Intent.>

 getResultImage = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == RESULT_OK) {
            val dataUri: Uri? = result.data?.data
            try {
                val bitmap: Bitmap =
                    MediaStore.Images.Media.getBitmap(context?.contentResolver, dataUri)
                binding.profileImage.setImageBitmap(bitmap)
            } catch (e: Exception) {
                Toast.makeText(context, "$e", Toast.LENGTH_SHORT).show()
            }
        }
    }

위의 코드에서 보이듯이 if문이 줄어들었고 requestCode라는 파라미터 요청이 없다.
팀원의 제의로 몰랐던 메서드에 대해 알게되었고 기존에 쓰고 있던 코드에서도 deprecated된 코드가 없는지 확인하게되었다.

내 정보부분에서는Fragment들을 FragmentManager.replace()를 사용할 수 도 있겠지만 이번 부분에서는 jetpack navigation을 사용해 보는게 어떻냐

Activity로 퍼져나갔을때는 모든 Activity에 Navigation을 쓰기에는 너무 부담일거 같아 replace를 하였지만 내 정보부분이 단일 Activity로 변경된 시점에서는 충분히 쓸 가치가 있다고 느껴졌다. 그렇기에 바로 Navigation_graph를 생성하였다.

각각의 Fragment들을 불러와 Design에서 Fragment들끼리 드래그하여 연결하여도 상관없고 Code부분으로 와 다음과 같이 선언해주어도 문제없다.

<fragment
      android:id="@+id/myInfoFragment"
      android:name="com.example.YUmarket.screen.myinfo.MyInfoFragment"
      android:label="MyInfoFragment"
      tools:layout="@layout/fragment_my_info"
      >
      <action
          android:id="@+id/action_myInfoFragment_to_termsFragment"
          app:destination="@id/termsFragment" />

  private fun openTerms() {
        view?.let { it1 ->
            Navigation.findNavController(it1).navigate(R.id.action_myInfoFragment_to_termsFragment)
        }
    }

MyInfoFragment안에서 action을 통해 termsFragment로 갈수있다고 id와 목적지를 설정해준다. 그 후 MyInfoFragment에서 findNavController를 통해 NavController를 찾아주고 정의된 action ID를 찾아온다. 이 명령어를 통해 지정해놨던 destination으로 이동한다.

graph에 Fragment의 이동이 직관적으로 보이다보니 확실히 코드보다 좋아보였다.

마치며

이렇게 제가 받았던 질문들과 의견에 대해서 적어보았습니다. 다음 글은 제가 코드를 작성하며 생겼던 오류부분들과 해결했던 방법들에 대해서 작성하도록 하겠습니다 읽어주셔서 감사합니다.

좋은 웹페이지 즐겨찾기