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

HEETAE HEO·2022년 3월 15일
0
post-thumbnail

안녕하세요
이번의 코드 리뷰는 팀 프로젝트에서 제가 작성한 코드에 대해서 실시했습니다.

저는 이번 프로젝트에서 내 정보부분에서 고객센터까지 제작을 담당하였는데요
이번 글에서는 제가 작성한 코드들을 설명하는 시간을 가지도록 하겠습니다.

이전 코드리뷰에서 변경된 BaseClass

저번 두 번째 코드리뷰에서 ViewModel에 대해 논의가 있었습니다.

현재 AAC ViewModel에서 MVVM의 ViewModel의 기능을 함께 수행을 하고 있는데 이런 상황에서는 view와 viewModel의 관계를 1:N이 가능한지 아니면 1:1으로 수행을 해야하는지에 대해 많은 이야기를 나누었고 저희 팀은 불필요한 ViewModel은 줄이는게 맞고 최소한의 ViewModel을 이용해 프로젝트를 제작하기로 하였습니다.

그렇기에 Activity/Fragment의 Base코드들이 변경되었습니다.

변경 전

abstract class BaseFragment<VM: BaseViewModel, VB: ViewBinding>: Fragment() {

    abstract val viewModel: VM

    protected lateinit var binding: VB

해당 생성자 파라미터로 ViewModel을 주입하여 상속을 받는 Activity/Fragment들은 ViewModel을 강제적으로 주입하여야했습니다 논의 후는 BaseCode를

변경 후

abstract class BaseFragment<VB: ViewBinding>: Fragment() {

    protected lateinit var binding: VB

ViewModel부분을 제거하고 ViewBinding부분만 파라미터로 받게하였습니다.

MyInfoActivity

일단 저희 시작 Activity는 MainActivity입니다. 처음의 프로젝트는
BottomNavigationBar를 이용하여 Fragment들을 이동을 하였고 그 이후에서 분리되어 기능별 Activity로 넘어가게 하였습니다. 이렇게 설명을 한다면 그 뒤에 기능들 또한
Activit가 아닌 Fragment로 넘어가면 되는 것이 아니냐 라는 궁금증이 나오실 수 있습니다. 저희 MainActivity에서는 하나의 화면에 Fragment로 3분활이 되어 최상단에는 GPS 및 Network기능으로 찾은 사용자의 위치 최하단에는 BottomNavigationBar 이 두가지에서 제외된 공간 즉 중간에서 화면 전환이 실행 되는데 다른 Fragment로 전환할 때 최상단 과 최하단의 Fragment들이 그대로 있어 화면에서 불필요한 부분을 잡아먹는 현상이 발견되었습니다. 그렇기에 변경된 것은

BottomNavigationBar에서 내 정보 부분을 선택을 했을때 MyInfoActivity가 실행되도록 하였다 즉 MyInfo부터 고객센터(CS)부분은 MyInfoActivit에서 구동되도록 하였다.

화면은 이동은 Navigation을 이용해 Fragment들의 화면전환이 일어나기 MyInfoActivity의 코드는 Base코드들만 있고 별도의 로직은 존재하지않는다.

class MyInfoActivity : BaseActivity<ActivityMyinfoBinding>() {

    override fun getViewBinding(): ActivityMyinfoBinding =
        ActivityMyinfoBinding.inflate(layoutInflater)
    override fun observeData() {
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
    override fun initViews() = with(binding) {
        super.initViews()
    }
}

MyInfoFragment

MyInfoFragment는 처음 내 정보 부분을 실행했을 때 나타나는 첫 화면이라고 생각하면 됩니다.

class MyInfoFragment : BaseFragment<FragmentMyInfoBinding>() {


    private lateinit var getResultImage: ActivityResultLauncher<Intent>


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

    private val check = true;

    override fun getViewBinding(): FragmentMyInfoBinding =
        FragmentMyInfoBinding.inflate(layoutInflater)

    override fun initViews(){
        super.initViews()
        ...
        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()
                }
      	}
      }
    }

    private fun loadImage() {

        var intent_image = Intent()
        intent_image.type = "image/*"
        intent_image.action = Intent.ACTION_GET_CONTENT

        getResultImage.launch(intent_image)
//        startActivityForResult(Intent.createChooser(intent_image,"Load Picture"),galley)
    }

...

코드의 일부분 입니다.

코드에서 getResultImage = registerForActivityResult 부분이 있는데 이는 사용자의 기기에서 사용자의 기기에서 Image파일을 가지고와 사용자의 프로필사진을 변경할 수 있도록 하는 명령어이다. 처음에 저는 이 코드를 구성할 때 startActivityForResult를 이용하여 이미지를 가지고 왔지만 팀원 중 한 분이 제 코드를 보고 지금 사용하고 있는 startActivityForResult는 지금 사용을 하지않고 registerForActivityResult 사용을 권장해준다라고 말해주었고 저는 해당 기능을 사용하기 위해서 자료를 찾아 봤습니다.

startActivityForResult Deprecated된 이유

startActivityResult 메서드를 사용하면 onActivityResult에서 콜백을 처리해야하는데 두개가 같은곳에서 구현을 하고 동작을 하면 메모리가 부족해서 프로세스와 Activity가 사라질 수 있다.

그렇기에 AndroidX Activity/Fragment에 도입된 Activity Result API 사용을 권장하고 있다.

다음 기능은 PopUp() 메서드 입니다.

이 메서드는 알람설정을 받을 것인지 받지않을 것인지를 설정하는 Switch 인데 해당 버튼을 클릭하면 Dialog가 나오게 되고 버튼을 누를때 설정 시간이 Toast메시지로 출력되게 하였습니다.
이 출력되는 순간에 System에서 데이터를 변수를 저장하게 되고 변환시키는 과정이 MVVM 패턴에 위배가 된다고 생각했습니다. View는 단순히 사용자에게 보여주는 기능과 Event를 감지하는 역할을 수행하며 직접적인 데이터의 호출 및 변환은 없애는 것이 맞다고 생각해 따로 Method라는 Class를 만들어 requireContext()로 해당 함수를 불러와 기능을 수행하도록 하였습니다.


다음과 같이 Toast메시지가 출력이 된다.

화면 전환

다음 기능들은 화면 전환을 어떻게 하고 있는가 이다.
처음 프로젝트를 계획하고 코드 작업을 했을 때는 Activity로 퍼져나갔기 때문에 intent를 사용하고 FragmentManager에서 replace 명령어를 사용해 화면을 전환했었다. 하지만 MyInfoActivity를 통해 내 정보 및 고객센터 부분을 단일 액티비티로 구현하자는 의견이 나온 후 Android jetpack Navigation을 통해 Action을 설정해주고 화면을 이동할 수 있도록 하였다.

기능 수행을 위한 코드

cs_graph.xml

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

    <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" />
        <action
            android:id="@+id/action_myInfoFragment_to_CSCenterFragment"
            app:destination="@id/CSCenterFragment" />
        <action
            android:id="@+id/action_myInfoFragment_to_configurationFragment"
            app:destination="@id/configurationFragment" />
        <action
            android:id="@+id/action_myInfoFragment_to_personalFragment"
            app:destination="@id/personalFragment" />

위의 코드는 res폴더에서 navigation이라는 폴더를 만든 후 xml 파일로 만든 cs_graph파일이다. Fragment를 정의할 때 위에 처럼 코드를 작성해도 되지만

design 쪽에서 빨간 네모칸의 버튼을눌러 추가하는것이 더 편하다 그 후 다음 사진 처럼

마우스로 드래그 하여 다음 화면 전환을 할 Fragment로 이어준다. 그 후 코드를 보면 <action이라고 되어 있는 부분이 있을 것이다. 이 코드의 id를 기억해

화면 전환 코드

view?.let { it1 ->
            Navigation.findNavController(it1)
                .navigate(action ID....)

        }

동작 이벤트 안에 (binding...setOnClickListener 또는 findByID...setOnClickListener)
원하는 동작의 ID를 넣으면 화면전환이 실행된다.

다크 모드

다음은 다크 모드를 실행 하기 위해 사용되는 코드이다.
현재 프리미엄급 스마트폰에는 OLED 디스플레이 패널이 들어가는데 이는 뒤에 빛을 쏘는 판 없이 소자 자체가 빛을 내 색을 표현을 한다. 그렇기에 화면의 검은 부분이 있다면 소자들은 빛을 꺼 어둡게 만든다. 어떠한 화면에 검은 부분이 많다면 그만큼 소자들은 빛을 끌 것이고 전력은 그 만큼 세이브 되는 것이다. 스마트폰의 사용시간을 늘리고 싶다면 다크모드 사용을 추천한다. 본론으로 돌아와 다음과 같이 AppCompatDelegate 함수를 사용해 색을 반전해 줌으로서 어둡게 만들어줍니다.

  private fun darkMode() {
        if (check == binding.darkSwitch.isChecked) {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
        } else {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
        }
    }

다크 모드의 주의 사항

해당 명령어를 통해 다크모드를 실행하면 Activity가 다시 create되는 현상이 발생해 다크 모드의 동작을 수행 Activity가 재구성 됩니다. 이 부분의 해결 방법은 추후 프로젝트를 진행하며 문제가 되었던 것들을 모아 글을 작성할 텐데 그 글에서 해결방법 또한 같이 작성하겠습니다.

CSCenterFragment

이 부분은 고객센터와 내 정보 사이의 Fragment이다. 여기서 고객센터와의 전화 또는 이메일 작성을 할 수 있고 자주 물어보는 질문 페이지로 넘어갈 수 있다.

이 곳에서의 코드 일부분을 보자면

private fun permissionCheck_CallYU() {
        val callCheck =
            android.Manifest.permission.CALL_PHONE
        val permission =
            ContextCompat
                .checkSelfPermission(
                    binding.centerNumber.context,
                    android.Manifest.permission.CALL_PHONE
                )
        if (permission == PackageManager.PERMISSION_GRANTED) {
            callYU()
        } else {
            ActivityCompat.requestPermissions(
                requireActivity(),
                arrayOf(android.Manifest.permission.CALL_PHONE), 0
            )
            Toast.makeText(context, "권한에 동의되었습니다. 다시 버튼을 눌러주새요", Toast.LENGTH_SHORT).show()
        }

    }

    private fun permissionCheck_food() {
        val callCheck =
            android.Manifest.permission.CALL_PHONE
        val permission =
            ContextCompat
                .checkSelfPermission(
                    binding.centerNumber.context,
                    android.Manifest.permission.CALL_PHONE
                )
        if (permission == PackageManager.PERMISSION_GRANTED) {
            foodSafecall()
        } else {
            ActivityCompat.requestPermissions(
                requireActivity(),
                arrayOf(android.Manifest.permission.CALL_PHONE), 0
            )
            Toast.makeText(context, "권한에 동의되었습니다. 다시 버튼을 눌러주새요", Toast.LENGTH_SHORT).show()
        }

    }

일단 PermissionCheck...() 메서드는 바로 통화로 연동을 시켜주는 기능을 가지고 있는데 메서드 이름처럼 권한이 되어있는지 동작하기전에 확인하는 그러한 메서드이다. 동작에 필요한 권한이 허용되어있지 않다면 사용자에게 권한을 요청하고 동작을 실행한다.

다음과 같이 화면에 나타난다.

다음 글 예고

하나의 글에 다 적기에는 너무 길어질 거 같아 다음 글에 나머지 코드들에대해 설명하겠습니다 읽어주셔서 감사합니다.

profile
Android 개발 잘하고 싶어요!!!

0개의 댓글