LoginFragment.kt에서 NullPointerException 발생
- 이 오류가 처음 발생했을 때 첫 번째 줄에 getBinding이 표시되었습니다.
바인딩되어 있습니까? 제 생각에는. - 그러나 나중에 ViewTreeObserver 및 OnGlobalLayout의 문제로 인해 발생한 것으로 밝혀졌습니다.
- 오류 코드를 읽을 때는 전반부만 보지 말고 후반부도 보는 습관을 길러야 합니다!
- 오류 코드를 읽을 때는 전반부만 보지 말고 후반부도 보는 습관을 길러야 합니다!
로그인 Fragment.kt
class LoginFragment : Fragment() {
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!
!
private var waitTime = 0L
@SuppressLint("ClickableViewAccessibility")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentLoginBinding.inflate(inflater, container, false)
initView()
...
return binding.root
}
...
@SuppressLint("ClickableViewAccessibility")
private fun initView() {
val showDefaultHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 150f,
resources.displayMetrics
).toInt()
val hideDefaultHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 150f,
resources.displayMetrics
).toInt()
binding.loginLayout.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val viewHeight: Int = binding.loginLayout.height
val rootHeight: Int = binding.loginLayout.rootView.height
val diff = rootHeight - viewHeight
if (diff > showDefaultHeight) {
setSNSLoginUIVisibility(binding, View.GONE)
} else if (diff < hideDefaultHeight) {
Handler(Looper.getMainLooper()).postDelayed({
setSNSLoginUIVisibility(
binding,
View.VISIBLE
)
}, 10)
}
binding.loginLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
...
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
분석하다
- 우선 OnGlobalLayout의 로직은 rootView의 높이를 계산하고 소프트 키보드의 적절한 위치에 엔터 버튼을 생성하는 것입니다.
- ViewTreeObserver 사용 방법을 검색하면 항상 설명이 있습니다.
다음과 같이- ViewTreeObserver를 사용한 후에는 removeOnGlobalLayoutListener()를 구현해야 합니다.
- 그렇지 않으면 Observer가 무한한 수의 레이아웃 변경 감지를 요청하기 때문에 메모리 누수가 발생합니다.
- 레이아웃 변경만 감지하려는 경우 addOnGlobalLayoutListener에서 removeOnGlobalLayoutListener를 직접 구현할 수 있습니다.
- 레이아웃 변경만 감지하려는 경우 addOnGlobalLayoutListener에서 removeOnGlobalLayoutListener를 직접 구현할 수 있습니다.
- 그렇지 않으면 Observer가 무한한 수의 레이아웃 변경 감지를 요청하기 때문에 메모리 누수가 발생합니다.
- ViewTreeObserver를 사용한 후에는 removeOnGlobalLayoutListener()를 구현해야 합니다.
- 하지만 제 경우에는 지속적으로 변화를 감지하고 다음 화면으로 이동할 때 멈추기를 원합니다.
- 한 걸음 더 나아가 원래 화면으로 돌아왔을 때 옵저버가 다시 작동하면 좋겠다는 생각을 했습니다.
- 이 경우 프래그먼트의 onStop() 수명 주기에서 removeOnGlobalLayoutListener를 구현할 수 있습니다.
- onStop()은 프래그먼트가 화면에서 사라질 때 호출되는 메서드입니다.
- 이 경우 프래그먼트의 onStop() 수명 주기에서 removeOnGlobalLayoutListener를 구현할 수 있습니다.
- 한 걸음 더 나아가 원래 화면으로 돌아왔을 때 옵저버가 다시 작동하면 좋겠다는 생각을 했습니다.
내가 뭘 하고 싶어
사용자가 ID 창이나 PW 창을 클릭할 때마다 Observer가 레이아웃의 크기를 감지하여 키보드 뒤의 모든 요소를 사라지게 하거나 다시 나타나게 하는 기능을 구현하고 싶습니다.
즉, 레이아웃 변경을 지속적으로 감지할 수 있기를 원합니다.
또한 다른 화면으로 이동했다가 돌아올 때 Observer가 OnGlobalLayoutListener를 다시 모니터링하기를 원합니다.
로그인 Fragment.kt
class LoginFragment : Fragment() {
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!
!
private var waitTime = 0L
// onDestroyView()에서 사용할 수 있도록 멤버변수로 선언
private var listener: ViewTreeObserver.OnGlobalLayoutListener? = null
@SuppressLint("ClickableViewAccessibility")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentLoginBinding.inflate(inflater, container, false)
initView()
...
return binding.root
}
override fun onResume() {
super.onResume()
initView()
}
...
@SuppressLint("ClickableViewAccessibility")
private fun initView() {
val showDefaultHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 150f,
resources.displayMetrics
).toInt()
val hideDefaultHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 150f,
resources.displayMetrics
).toInt()
listener = ViewTreeObserver.OnGlobalLayoutListener {
val viewHeight: Int = binding.loginLayout.height
val rootHeight: Int = binding.loginLayout.rootView.height
val diff = rootHeight - viewHeight
Log.d("SH_TAG", "viewHeight = $viewHeight")
Log.d("SH_TAG", "rootHeight = $rootHeight")
Log.d("SH_TAG", "diff = $diff")
Log.d("SH_TAG", "showDefaultHeight = $showDefaultHeight")
Log.d("SH_TAG", "hideDefaultHeight = $hideDefaultHeight")
if (diff > showDefaultHeight) {
setSNSLoginUIVisibility(binding, View.GONE)
} else if (diff < hideDefaultHeight) {
Handler(Looper.getMainLooper()).postDelayed({
setSNSLoginUIVisibility(
binding,
View.VISIBLE
)
}, 10)
}
}
binding.loginLayout.viewTreeObserver.addOnGlobalLayoutListener(listener)
}
...
override fun onStopView() {
super.onStop()
binding.loginLayout.viewTreeObserver.removeOnGlobalLayoutListener(listener)
}
}
내 기분
긴 내비게이터 애플리케이션 여정이 마침내 끝났습니다.
무언가를 배우는 데 정말 오랜 시간이 걸립니다.
그래서 남들보다 더 성실해야 한다.
또한 집중하는 데 문제가 있습니다.
아아, 생존은 쉽지 않다
그래도 마지막 순간에 나는 커피에서 힘과 집중력을 얻었습니다!
오늘도 수고했어요~~!