일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- SAA
- 재밌긴함
- fragmentcontainer
- 백준
- Stack
- 알고리즘
- 자바
- rxandroid
- parentfragment
- 중첩네비게이션
- 너무 어렵다
- 공유오피스
- 스택
- media3 transformer
- media3
- innernavigation
- Android
- 후기
- MVVM
- 더베일리하우스 삼성점
- Kotlin
- 파이썬
- 패파
- 코틀린
- 사무실
- 가든웨딩
- 내부프레그먼트
- 안드로이드
- 아키텍쳐
- 패스트파이브
삽질도사
[안드로이드] SAA 문제해결 - child fragment 와 데이터 통신/전달 그리고 장단점과 문제해결 본문
최근에 SAA에 관한 글을 포스팅했었는데 마침 개발 중에 관련된 어려움을 겪었던 적이 있어서 글을 남겨봅니다.
해당 아키텍쳐를 사용하다보면 특정 스크린에서는 프래그먼트 안에 새로운 fragment container를 만들어서 또 다른 navigation을 연결해서 child fragment의 형태처럼 써야할 때가 있습니다.
저는 child fragment와 parent fragment의 개념으로 접근해서 fragment간의 데이터 전달을 실시간으로 하기 위해서 setFragmentResultListener와 setFragmentResult를 이용해서 손쉽게 함수 2개로 처리하려 했습니다. (일반적인 방법)
그래서 parent에서는 childFragmentManager.setFragmentResultListener를 사용하고 child에서는 setFragmentResult를 사용하였는데 이러한 방식으로 parent-child로 접근하니 데이터 통신이 아예 안되는 경험을 겪었습니다..!
그래서 꽤 유능한 분들이 많이 계신 오픈카톡방에 괸련 질문을 남긴 적이 있었는데 의외로 모든 분들께서 코드에는 문제가 없다고 생각을 하셨었는데 결국은 코드와 관련된 문제였습니다. 놀랍게도 parent-child frament의 개념으로 접근한 것 자체가 문제였습니다.
-Parent fragment
private fun setupNav() {
val navHost = childFragmentManager.findFragmentById(R.id.view_bottom_sheet) as NavHostFragment
val arguments = bundleOf()
val navOptions = navOptions { popUpTo(R.id.searchBottomSheetFragment) }
searchNavController = navHost.navController
searchNavController.navigate(R.id.searchBottomSheetFragment,arguments,navOptions)
}
private fun showSearchBottomSheet() {
childFragmentManager.setFragmentResultListener(SEARCH, viewLifecycleOwner) { _, bundle ->
when (bundle.getInt(STATE)) {
BottomSheetBehavior.STATE_COLLAPSED -> bringMenuOut(true)
else -> bringMenuOut(false)
}
}
}
xml은 다른 건 볼게없고 이런식으로 fragment안에 또다른 nav를 넣기 위해 fragmentContainer를 사용한 모습만 보시면 됩니다.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="jaden.kr.presentation.feature.map.MapViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/notice_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textStyle="bold"
android:textColor="@color/white"
android:text="@string/_7days_text"
android:padding="6dp"
android:background="@drawable/gray_tr_bg"
android:layout_marginTop="30dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<jaden.kr.presentation.base.view.BaseImageView
android:id="@+id/help"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/report"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/notice_text"
app:layout_constraintBottom_toBottomOf="@id/notice_text"/>
<jaden.kr.presentation.base.view.BaseImageView
android:id="@+id/report"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/help"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/notice_text"
app:layout_constraintBottom_toBottomOf="@id/notice_text"/>
<RelativeLayout
android:id="@+id/map_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/locate_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="0dp"
android:src="@drawable/ic_locate"
android:backgroundTint="@color/white"
app:borderWidth="0dp"
app:maxImageSize="20dp"
app:fabCustomSize="40dp"
android:onClick="@{() -> viewModel.clickedLocateMap()}"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
tools:ignore="ContentDescription" />
</RelativeLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/view_bottom_sheet"
android:layout_width="0dp"
android:layout_height="0dp"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/search_bottom_nav"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
-Child fragment
override fun onStateChanged(state: Int) {
setFragmentResult(SEARCH, bundleOf(Pair(STATE,state)))
}
전체 코드를 볼 필요는 없는 것 같아서 함수만 올렸는데, 위 코드 보시면 아시다시피 child fragment manager로 container에 꽂아넣은 새로운 fragment안 코드에서 상태가 변함에 따라 parent에 데이터를 전달하기 위해 setFragmentResult를 사용했습니다만, 이렇게하면 전송이 안되었고 제가 이론이 틀렸을 것을 감안하여 어떤 방식을 사용하더라도(child , parent, nothing) 전부다 교차로 모든 경우의 수를 다 대입해도 작동하지 않는 걸 확인 했습니다.
제 생각에 child fragment manager를 이용해서 listener를 등록한 것이 올바른 방법이라고 생각하지만 결론적으로 child에서는 기존 parent에서 사용했던 child fragment manager 에 result를 set하는 것은 아닌 것처럼 보였습니다.(디버깅 결과 주소값이 다름을 확인)
물론 확실하지는 않습니다... 아시는 분 계시면 이유를 알려주세요 부탁드립니다.. 🙇♂️
-결론-
-Parent fragment
private fun showSearchBottomSheet() {
requireActivity().supportFragmentManager.setFragmentResultListener(SEARCH, viewLifecycleOwner) { _, bundle ->
when (bundle.getInt(STATE)) {
BottomSheetBehavior.STATE_COLLAPSED -> bringMenuOut(true)
else -> bringMenuOut(false)
}
}
}
-Child fragment
override fun onStateChanged(state: Int) {
requireActivity().supportFragmentManager.setFragmentResult(SEARCH, bundleOf(Pair(STATE,state)))
}
제가 사실 이 두 함수 때문에 3일을 삽질하면서 아.. 그냥 다른 방법으로 접근할까 다른 방법으로 할 수도 있고 방법이 많을텐데 라는 생각을 하면서도 그렇게 한번두번 정석적인 방법을 회피하고 이유를 모른채로 문제를 지나치면 나중에 똑같은 문제를 겪었을 때 또 다시 삽질하고 심지어 다른 방법조차 안통할 때는 멘붕이 올수도 있다고 생각했고, 이 문제를 한번 해결하면 다음에도 쓸 일이 많을 것 같아서 사실 유연하게 해결하기 보다는 고민하고 공부하면서 물어보며 너무 시간 잡아먹는다 싶으면 다른 쪽 개발하면서 계속 내용을 찾아보고 그랬던 것 같습니다. (물론 다시는 안만날 것 같은 문제이고 정답이 없어서 우회가 가능한 방법을 택해서 시간을 단축할 수 있다면 그 경우에는 빠르게 우회하는 것이 정답이라고 생각합니다. 일단 개발하고 다시 들여봐도 되니까.)
아무튼 SAA라는 아키텍쳐에서 결국엔 하나의 Activity를 쓴다는 이점을 잘 활용한 해결법인것 같습니다.
진짜 방법은 별게 없고 하나의 Activity를 모든 Fragments가 공유하기 때문에 위처럼 좀 구조가 복잡한 경우에 requireActivity().supportFragmentManager.를 사용해서 Activity가 가지고 있는 공통의 fragmentManager 정보에 접근하여 확실하게 함수를 사용한 것입니다. (왜 SAA를 쓰면서 이생각을 못했찌??)
SAA를 사용하는 단점과 장점이 명확하게 들어난 케이스인 것 같아서 끄적여봤습니다. (SAA 아니었으면 그냥 fragment class에 파라미터로 리스너 함수를 전달해서 주고 받으면 그만이니까)
그래도 트러블슈팅이랍시고 적긴 적었는데 사실 이게 누구에게 도움이 될지 모르겠지만(아마 대부분 금방 해결 하셨을듯..) 개발하다보면 진짜 코드 두줄 밖에 안되는거 몇 일씩 헤매고 알고보면 ㄱ뚝딱 고칠 수 있는 문제를 접했을 때 대부분 정말 현타가 쎄게 오는 것 같아서.. 아무쪼록 도움이 되었으면 좋겠습니다. 더 좋은 방법이나 이야기 있으면 해주세요. 감사합니다.
'안드로이드' 카테고리의 다른 글
[안드로이드] ffmpeg 종료로 인한 media3 transformer 사용 (8) | 2025.04.25 |
---|---|
[안드로이드] ci/cd 사용 및 슬랙 메시지 보내기 (2) | 2025.04.10 |
[안드로이드] SAA(Single Activity Architecture) + Navigation 후기 (0) | 2024.05.10 |
[안드로이드] 키 해쉬 / sha-1 구하기 (0) | 2023.08.29 |
[안드로이드] ExoPlayer Controller 커스텀하기 및 기능사용하기. (0) | 2023.07.11 |