TIL) 0831

Hanseul Lee·2022년 8월 31일
0

TIL

목록 보기
2/23

Fragment에서 바인딩을 할 때 초기값이 null이어야 하는 이유

Fragment에서 onCreatView()가 호출될 때까지 레이아웃을 inflate할 수 없기 때문에 null을 허용해야 한다. null을 허용하기 때문에 null safety를 위해 ?를 꼭 포함하자! 그리고 계속 ?을 쓰기는 번거롭기 때문에, ?을 쓰지 않고도 뷰를 참조할 수 있게 get-only 변수도 만들자.

// 변수 이름 앞에 _를 포함하면 이 변수에는 직접 액세스를 할 수 없다는 것을 의미한다
private var _binding: FragmentLetterListBinding? = null

// 아래와 같은 방법으로 binding 속성을 따로 사용해 액세스 하자 
// 이때 get()은 이 변수가 read-only인 걸 나타낸다
// 때문에 값을 가져올 수는 있지만 다른 변수에 할당은 불가하다
private val binding get() = _binding!!

바인딩은 레이아웃이 inflate되는 onCreateView()에서 하고, 아래와 같이 활용한다.

override fun onCreateView(
   inflater: LayoutInflater, container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   _binding = FragmentLetterListBinding.inflate(inflater, container, false)
   val view = binding.root
   return view
}

메모리 누수를 막기 위해 onDetroyView()에서 다시 null로 설정해주는 것도 잊지 말자.

override fun onDestroyView() {
   super.onDestroyView()
   _binding = null
}

https://medium.com/mobile-app-development-publication/not-all-viewbinding-need-null-setting-e16fe6737489

cf) Fragment 생명주기 중 CREATED 상태의 콜백들

  • onCreate() → Fragment가 인스턴화 됨. 뷰가 만들어지지 않았다. 따라서 레이아웃을 inflate할 수 없다.
  • onCreateView() → 레이아웃을 inflate한다.
  • onViewCreated() → 뷰가 만들어진 다음 호출되고, 여기서 특정 뷰에 바인딩이 가능하다.

cf) Fragment 생명주기 중 DESTROYED 상태의 콜백들

  • onDestroyView() → Fragment가 DESTROYED 상태로 전환되기 전에 호출되는 콜백으로, 뷰는 메모리에서 삭제되지만 Fragment 객체는 여전히 남아있다.
  • onDestroy() → Fragment가 DESTROYED로 전환된다.

Navigation을 알아보자!

MainActivity에서 다음과 같이 NavController를 만들었다.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        val navHostFragment =
            supportFragmentManager.findFragmentById(binding.fragmentContainerView.id) as NavHostFragment
        navController = navHostFragment.navController

        setupActionBarWithNavController(navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp() || super.onSupportNavigateUp()
    }

그런데 navController를 Fragment에서 쓰려고 하니 아무래도 companion object에 NavController를 두는 게 좋겠다고 생각했다. 그런데 이렇게 노란 칠이 되면서 @SuppressLint("StaticFieldLeak") 이 뜨는 게 아닌가!

@SuppressLint("StaticFieldLeak") 이란?

설명에도 나와있듯이 memory leak, 메모리 누수를 일으킬 수 있다고 경고를 해두는 거다. 한 마디로 이렇게 쓰지 말라고, 나중에라도 꼬오오오오옥 수정하라고 표기를 시키는 거다. 좀 더 정확하게는 안드로이드를 정적 분석하고, 그래서 코드의 품질을 향상시키기 위해 사용하는 Android Lint가 경고하는 거라 볼 수 있다.

그렇다면 메모리 누수를 막으면서 Fragment에서 어떻게 NavController를 사용할까?

binding.floatingActionButton.setOnClickListener {
            // NavController가 addFragment로 이동시킴
            findNavController().navigate(R.id.action_listFragment_to_addFragment)
        }

아주 간단하다. 위 코드처럼 findNavController()를 사용하면 된다.

DB Browser for SQLite

https://sqlitebrowser.org/

이 프로그램을 활용하면 안드로이드에 저장된 DB를 내 컴퓨터에서 확인할 수 있다.

우선 Device File Explorer에서 다음과 같은 경로로 들어가 내 프로젝트를 찾아간다.

그러면 이렇게 세 가지의 파일이 나오는데 다 로컬에 저장하도록 하자.

그리고 프로그램을 켜서 데이터베이스 열기를 선택하고 모든 파일로 검색을 하는데, 내 테이블 이름만 있는 파일을 연다.

그렴 이렇게 데이터베이스 구조를 편하게 볼 수 있고,

데이터 확인도 가능하다!

notifyDataSetChanged()

Adapter의 메서드다. Adapter에게 데이터가 변경되었으니 모든 뷰는 자체적으로 새로 고쳐야 한다고 알려준다. 그러니까 아이템의 삽입, 삭제, 이동 등이 일어났을 때 사용한다. 아래와 같이.

// ListAdapter.kt

@SuppressLint("NotifyDataSetChanged")
    fun setDate(user: List<User>) {
        this.userList = user
        notifyDataSetChanged()
    }
// ListFragment.kt

mUserViewModler = ViewModelProvider(this)[UserViewModel::class.java]
mUserViewModler.readAllData.observe(viewLifecycleOwner, Observer {
		// user -> 이렇게 바꾸는 게 훨씬 직관적인 코드지만 아직 헷갈리니까 자료형이 보이는 it으로 두자
		// LiveData인 readAllData를 관찰하면서 바뀌는 걸 즉각적으로 반영함!
		adapter.setDate(it)
})

cf) 다른 Adapter 메서드도 알아보자

  • notifyDataSetInvalidated() → 데이터가 유효하지 않거나 사용할 수 없다고 알린다. 이것이 호출되면 Adapter는 유효하지 않고, 추가 변경사항을 보고하지 않는다.

0개의 댓글