코틀린-안드로이드

52일차)알고리즘 문제(행렬 테두리 회전하기, 전력망을 둘로 나누기), AppleMarket 과제, MVVM과제 , 특강 정리(View Rendering, ViewBinding, Fragment 데이터 전달, ViewPager2와 TabLayout)

songyooho 2024. 7. 15. 20:56

>알고리즘 문제

 

1. 행렬 테두리 회전하기

1)문제

rows x columns 크기인 행렬이 있습니다. 행렬에는 1부터 rows x columns까지의 숫자가 한 줄씩 순서대로 적혀있습니다. 이 행렬에서 직사각형 모양의 범위를 여러 번 선택해, 테두리 부분에 있는 숫자들을 시계방향으로 회전시키려 합니다. 각 회전은 (x1, y1, x2, y2)인 정수 4개로 표현하며, 그 의미는 다음과 같습니다.

  • x1 행 y1 열부터 x2 행 y2 열까지의 영역에 해당하는 직사각형에서 테두리에 있는 숫자들을 한 칸씩 시계방향으로 회전합니다.

다음은 6 x 6 크기 행렬의 예시입니다.

이 행렬에 (2, 2, 5, 4) 회전을 적용하면, 아래 그림과 같이 2행 2열부터 5행 4열까지 영역의 테두리가 시계방향으로 회전합니다. 이때, 중앙의 15와 21이 있는 영역은 회전하지 않는 것을 주의하세요.

행렬의 세로 길이(행 개수) rows, 가로 길이(열 개수) columns, 그리고 회전들의 목록 queries가 주어질 때, 각 회전들을 배열에 적용한 뒤, 그 회전에 의해 위치가 바뀐 숫자들 중 가장 작은 숫자들을 순서대로 배열에 담아 return 하도록 solution 함수를 완성해주세요.


제한사항

  • rows는 2 이상 100 이하인 자연수입니다.
  • columns는 2 이상 100 이하인 자연수입니다.
  • 처음에 행렬에는 가로 방향으로 숫자가 1부터 하나씩 증가하면서 적혀있습니다.
    • 즉, 아무 회전도 하지 않았을 때, i 행 j 열에 있는 숫자는 ((i-1) x columns + j)입니다.
  • queries의 행의 개수(회전의 개수)는 1 이상 10,000 이하입니다.
  • queries의 각 행은 4개의 정수 [x1, y1, x2, y2]입니다.
    • x1 행 y1 열부터 x2 행 y2 열까지 영역의 테두리를 시계방향으로 회전한다는 뜻입니다.
    • 1 ≤ x1 < x2 ≤ rows, 1 ≤ y1 < y2 ≤ columns입니다.
    • 모든 회전은 순서대로 이루어집니다.
    • 예를 들어, 두 번째 회전에 대한 답은 첫 번째 회전을 실행한 다음, 그 상태에서 두 번째 회전을 실행했을 때 이동한 숫자 중 최솟값을 구하면 됩니다.

 

2)솔루션

class Solution {
    fun solution(rows: Int, columns: Int, queries: Array<IntArray>): IntArray {
        var answer = intArrayOf()
        val arr=Array(rows){IntArray(columns)}
        for(i in 0..rows*columns-1){
            arr[i/columns][i%columns]=i+1
        }
        for(i in queries){
            val qtop=ArrayDeque<Int>()
            val qleft=ArrayDeque<Int>()
            val qright=ArrayDeque<Int>()
            val qbot=ArrayDeque<Int>()
            
            val q=intArrayOf(i[0]-1,i[1]-1,i[2]-1,i[3]-1)
            
            var min=Int.MAX_VALUE
            for(j in q[1]..q[3]){
                qtop.addLast(arr[q[0]][j])
                qbot.addLast(arr[q[2]][j])
                min=minOf(min,arr[q[0]][j])
                min=minOf(min,arr[q[2]][j])
            }
            if(q[0]<=q[2]-2){
                for(j in q[0]+1..q[2]-1){
                    qleft.addLast(arr[j][q[1]])
                    qright.addLast(arr[j][q[3]])
                    min=minOf(min,arr[j][q[1]])
                    min=minOf(min,arr[j][q[3]])
                }
                qtop.addFirst(qleft.removeFirst())
                qright.addFirst(qtop.removeLast())
                qbot.addLast(qright.removeLast())
                qleft.addLast(qbot.removeFirst())
                
                for(j in q[1]..q[3]){
                    arr[q[0]][j]=qtop.removeFirst()
                    arr[q[2]][j]=qbot.removeFirst()
                }
                for(j in q[0]+1..q[2]-1){
                    arr[j][q[1]]=qleft.removeFirst()
                    arr[j][q[3]]=qright.removeFirst()
                }
                
            }
            else{
                qtop.addFirst(qbot.removeFirst())
                qbot.addLast(qtop.removeLast())
                for(j in q[1]..q[3]){
                    arr[q[0]][j]=qtop.removeFirst()
                    arr[q[2]][j]=qbot.removeFirst()
                }
            }
            answer+=min
            
            
            
        }
        return answer
    }
}

-일일히 회전을 시키면서 원소를 옮기면 시간이 굉장히 오래걸리기에 다른 방법을 사용

-위, 아래, 양 옆을 각각 ArrayDeque으로 생성한 뒤 맨 앞, 혹은  맨 뒤 원소를 빼서 다음 deque에 넘겨주는 방식으로 회전 구현

-원소의 최솟값은 ArrayDeque에 원소를 넣는 과정에서 구한다.

 

 

2. 전력망을 둘로 나누기

1)문제

문제 설명

n개의 송전탑이 전선을 통해 하나의 트리 형태로 연결되어 있습니다. 당신은 이 전선들 중 하나를 끊어서 현재의 전력망 네트워크를 2개로 분할하려고 합니다. 이때, 두 전력망이 갖게 되는 송전탑의 개수를 최대한 비슷하게 맞추고자 합니다.

송전탑의 개수 n, 그리고 전선 정보 wires가 매개변수로 주어집니다. 전선들 중 하나를 끊어서 송전탑 개수가 가능한 비슷하도록 두 전력망으로 나누었을 때, 두 전력망이 가지고 있는 송전탑 개수의 차이(절대값)를 return 하도록 solution 함수를 완성해주세요.


제한사항
  • n은 2 이상 100 이하인 자연수입니다.
  • wires는 길이가 n-1인 정수형 2차원 배열입니다.
    • wires의 각 원소는 [v1, v2] 2개의 자연수로 이루어져 있으며, 이는 전력망의 v1번 송전탑과 v2번 송전탑이 전선으로 연결되어 있다는 것을 의미합니다.
    • 1 ≤ v1 < v2 ≤ n 입니다.
    • 전력망 네트워크가 하나의 트리 형태가 아닌 경우는 입력으로 주어지지 않습니다.

 

2)솔루션

class Solution {
    lateinit var tree:Array<IntArray>
    fun solution(n: Int, wires: Array<IntArray>): Int {
        var answer: Int = -1
        tree=Array(n){IntArray(n)}
        for(i in wires){
            tree[i[0]-1][i[1]-1]=1
            tree[i[1]-1][i[0]-1]=1
        }
        
        var minValue=Int.MAX_VALUE
        for(i in 0..n-1){
            val visited=BooleanArray(n){false}
            visited[i]=true
            var min=Int.MAX_VALUE
            for((idx,v) in tree[i].withIndex()){
                if(v==1){
                    visited[idx]=true
                    val tmp=TreeSum(idx,visited)
                    min=minOf(min, Math.abs(n-2*tmp))
                }
            }
            minValue=minOf(min,minValue)
            
        }
        return minValue
    }
    fun TreeSum(n:Int, visited:BooleanArray):Int{
        var result=1
        var checkleaf = true
        for((i,v) in tree[n].withIndex()){
            if(v==1&&!visited[i]){
                checkleaf=false
                visited[i]=true
                result+=TreeSum(i,visited)
            }
        }
        if(checkleaf){
           return 1 
        } 
        return result
    }
}

-각 노드를 루트로 잡고 바로 아래 서브트리의 총 개수를 구하는 식으로 진행

-서브트리의 총 개수를 구하면 해당 서브 트리를 잘라냈을때 생기는 두 트리의 노드 개수 차를 구해서 가장 작은 값을 비교함.

-각 노드별로 위의 과정을 반복해서 가장 작은 값을 구하면 된다.

 

 

>AppleMarket 과제

 

https://appdevelopjava.tistory.com/70

 

AppleMarket 구현 과제

@ 과제 링크https://teamsparta.notion.site/Android-de05cc5f0d054de9964f8ad1f116b784 Android 앱개발 숙련 개인 과제 | NotionGoal : 사과마켓 앱 만들기 (feat. 당근마켓)teamsparta.notion.site 2. 설계1) 데이터Item: 인텐트로 주

appdevelopjava.tistory.com

 

 

>MVVM 과제

 

https://appdevelopjava.tistory.com/69

 

회원가입 MVVM 과제

1. Model(Model.kt)object Model { private val users = ArrayList() init { users+=User("test1","test1","test1") users+=User("test2","test2","test2") } fun AddUser(user: User)=users.add(user) fun ReturnIds()=users.map(){it.id} as ArrayList}data class User(val

appdevelopjava.tistory.com

 

 

 

>특강 정리

1. View Rendering

1)View 생명주기

-onMeasure(): 루트 뷰와 차일드 뷰의 사이즈가 결정되면 호출

-onLayout(): 뷰와 차일드뷰의 사이즈와 포지션 적용시 호출

-onDraw(): 뷰가 화면에 컨텐츠(텍스트, 이미지등)을 그릴 준비가 되면 호출

 

 

2)안드로이드에서의 GPU활용

<1>Rasterization: 문자열 ,버튼, 도형같은 객체를 픽셀로 변환시키고 스크링상의 텍스처로 나타내는 과정

=>레스터화는 연산량이 많은데 이 과정을 빠르게 하는것이 GPU이다.

<2>과정

[1]폴리곤 메시와 텍스처 데이터를 CPU에서 준비하여 GPU로 보냄

[2]GPU는 레스터화를 진행 후 화면에 나타

<3>용어

[1]폴리곤: vertex가 edge로 이어진 삼각형 =>폴리곤이 여러개면 폴리곤 메시

[2]텍스처: 폴리곤의 삼각형마다 입히는 2차원이미지로 디테일을 표현하는 렌더링 요소

=>UV매핑: 삼각형 폴리곤 하나하나에 알맞은 텍스처를 오려붙임

 

@실제에서 레스터화: xml상의 view들이 inflatation을 걸쳐 메모리에 적재되고 이 화면에 나타낸 수 있는 texture와 pixel로 바꾸는 것

 

 

3)Layout Performance

@Rendering Process

<1>Measure: 뷰트리의 전위순회로 각 View Group및 View의 요소 크기 결정

=>ViewGroup측정시 하위 View도 측정

<2>Layout: ViewGroup이 Measure에서 결정된 크기를 사용해 위치를 결정하는 또다른 하위 순회 발생

<3>Draw: 뷰 트리의 각 객체에 대해 전위순회를 하며 Canvas객체가 GPU에게 그릴 명령목록을 보냄

 

@ViewGroup: view를 배치하기 위해 필요한 객체. 보통 레이아웃이라 부름

=>레이아웃, 툴바, 뷰페이저, 어댑터뷰(스피너도 여기 포함)가 여기 해당

@View: 앱을 실행시켰을때 보이는 모든 요소. 보통 위젯이라 부름

=>가장 상위 클래스로 전부 포함.

 

<1>주요 영향 요소

[1]Manage Complexity(복잡성 관리)

:Layout을 이용해 UI를 짤때 View를 계층형태로 중첩가능

=>중첩을 많이 하면 depth가 높아지고 복잡해지면서 오버헤드 증가

 

[2]이중과세

:일반적으로 한번의 layout-measure 과정을 걸쳐 빠르게 끝낸다. 하지만 일부 복잡한 레이아웃의 경우 요소의 위치를 잡기위해 계층의 일부를 여러번 통과를 반복해야 해결되는 경우가 존재

=>이경우 여러번 layout-measure 과정 반복이 필요한데 이를 이중과세라 한다.

@예시 RelativeLayout: 다른 뷰 를 기준으로 위치를 잡는 레이아웃

(1)각 자식 객체에 대해 mearsue-layout을 통해 위치와 크기를 계산

(2)이 데이터와 객체의 가중치도 고려하여 서로 연관된 뷰들간의 적절한 위치를 찾음

(3)최종적으로 두번째 layout을 실행해 최종적으로 객체의 위치를 결정지음

 

@다중 Measure-Layout 단계가 그 자체로 성능을 저하시키는 것은 아님

=>아래의 조건이 컨테이너에 적용되는 경우 주의

(1)컨테이너가 뷰계층의 root element라면 주의: 

(2)컨테이너 아래에 깊은 뷰 계층이 형성되는 경우

=>1,2번은 비슷한 

(3)리스트뷰처럼 너무많은 인스턴스로 화면을 채우게 되는 경우

 

=>결론적으로 뷰 계층을 평탄화 하고 레이아웃 중첩을 지양하는 것이 좋음

 

 

4)Compose 렌더링

<1>컴포즈: Android Jetpack 라이브러리주 하나로 programmatic방식으로 UI개발

=>플러터와 유사

<2>컴포즈 UI렌더링 - xml보다 성능 높음

[1]Composition : UI가 무엇을 보여줄리를 의미. 컴포저블 함수들을 실행하고 UI의 설명을 만들음

[2]Layout: UI가 어디 위치하는지 의미

=>측정과 배치, 두 단계로 구성: 레이아웃 요소들은 레이아웃 트리의 각 노드에 대해 자신과 하위요소 측정 후 2D좌표에 배치

[3]Drawing: 어떻게 랜더링 될지 의미. 보통 디바이스 화면인 캔버스에 그림

 

 

5)ConstraintLayout의 장점

-depth를 줄여 성능 최적화와 코드 가독성 증대

-이유(depth가 깊어지면 생기는 문제)

<1>레이아웃 계산 시간 증가: 깊은 뷰 계층구조는 구조 계산에 시간이 많이걸림

<2>뷰 계층 구조 복잡성 증가: 구조가 복잡해져 뷰들간 상호작용역시 복잡해짐

=>UI이벤트 처리, 애니메이션 처리 시간이 오래걸림

<3>메모리 사용 증가: 안드로이드는 뷰 계층 구조 정보 저장을 위해 메모리를 사용하는데 깊은 뷰 계층을 가진 레이아웃일 수록 메모리 사용량이 늘어남.

 

 

6)ConstraintLayout vs Others

<1>다른 뷰 중앙에 새로운 뷰를 배치하는 경우(여기서 배치되는 레이아웃을 의미)

=> FrameLayout > ConstraintLayout > LinearLayout

<2> 100개 아이템을 가진 RecyclerView를 넣는 경우(RecyclerView가 들어가는 레이아웃을 의미)

=> ConstraintLayout > RelativeLayout > LinearLayout

<3> TextView와 Image로만 이루어진 간단한 UI

=>LinearLayout > ConstraintLayout

@간단한 UI에서 1depth의 배치만 하는 경우는 ConstraintLayout이 필요없음

<4>여러 다양한 뷰로 구성된 복잡한 UI

=> ConstraintLayout > Other Layouts

@절대적 성능 우세를 가지는 것은 아니나 이중과세등의 랜더링 성능 영향 요소를 이유로 ConstraintLayout이 이점을 가짐

 

7)Activity LifeCycle(주요 요소)

<1>onResume():

-포그라운드에 있는상태

-interrupt발생으로 사용자 포커스가 사라지면 발생 => 이후 다시 onResume에서 진행

-액티비티 재개시 필요한 초기화 작업 수행은 여기서

 

<2>onPause()

- 사용자가 잠시 액티비티를 떠나거나 다른 액티비티로 포커스 될 시 호출(포그라운드에 위치하지 않음)

-예시: 다른 인터럽트(전화)가 오거나 멀티 윈도우에서 다른앱에 포커스를 줌

-배터리 아끼기 위해 시스템 리소스, 하드웨어 센서 할당 해제

-onPause는 잠시 호출되기에 데이터 저장, 네트워크 호출, db transaction등은 하면 안됨 

=>이건 onStop에서

-이후 메모리상에 남아있는 Activity instance를 불러와 onResume호출

 

<3>onStop

-사용자에게 더이상 액티비티가 표시되지 않는 경우

-예시: 새로 시작된 액티비티가 화면 전체를 차지

-필요작업: 불필요한 리소스를 해제하거나 조정.

<1>애니메이션 일시정지

<2>GPS 세밀한 위치에서 대략적인 위치로 전환해 배터리 아낌

<3>CPU를 많이 소모하는 작업 종료 =>정보를 DB에 저장하는것은 보통 이곳에서 

 

@UI관련 중지 작업은 onStop에서!

=>onPause에서 하면 단순히 포커스만 떠난경우(앱 두개를 킨 상태에서 다른 앱에 포커스를 옮기는 경우) UI가 멈춰버리는 문제 발생함.

 

<4>onRestart():onResume에서 액티비티로 돌아오는 경우 재시작하여 onStart()로 돌아감

 

<5>onDestroy(): 액티비티가 완전히 소멸전에 메소드 호출

-예시: finish() 혹은 앱 종료, 화면구성 변경(기기 회전)으로 일시적으로 액티비티 소멸

 

@화면 회전시 콜백 메소드 호출 순서

  • onPause()
  • onStop()
  • onSaveInstanceState()
  • onDestroy()
  • onCreate()
  • onStart()
  • onRestoreInstanceState()
  • onResume()

 

 

2. ViewBinding

1)액티비티에서 사용

private val binding: MainActivityBinding by lazy {
        MainActivityBinding.inflate(layoutInflater)
}

..

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

 

2)프래그먼트에서 사용

//Global Variable로 선언
private var _binding: MainFragmentBinding? = null
private val binding get() = _binding!!

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

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

-fragment는 onDestroyView()가 호출돼도 내부적으로 View들을 재사용하기 위해 보관=>Viewbinding 사용해서 없애게 

-Fragment생명주기는View보다 길음: 메모리 누수 방지를 위해 binding을 null로 만들어 GC에서 수집하게 해야함

 

 

3. 프래그먼트 데이터 전달

<1>Bundle: 고전 방식

val bundle = Bundle()
bundle.putString("key", "value")

val receiverFragment = ReceiverFragment()
receiverFragment.setArgument(bundle)

parentFragmentManager.beginTransaction()
	.replace(R.id.frameconainer ,receiverFragment).commit()
val result= arguments?.getString("key") // result == "value"

-보내는 프래그먼트에서 프래그먼트를 생성할때 setArgument로 Bundle() 인스턴스를 설정해줌.

-이후, 받는 프래그먼트에서 Argument?.get~()으로 받으면 됨

 

<2>Fragment Result API

-프래그먼트간, 혹은 프래그먼트와 액티비티간 일회성 값 전달 케이스에 사용

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResultListener("requestKey") { requestKey, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported.
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}

-setFragmentResultListener("requestKey") { requestKey, bundle -> bundle.get~}으로 사용

-받아오는 코드에 해당

 

 button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

-setFragmentResult("키", 번들) 로 데이터를 보냄

 

 

3. ViewPager2 와 TabLayout

1)MainTabModel

data class MainTabModel(
    val fragment : Fragment,
    @StringRes val title : Int
)

-뷰페이저에 사용될 데이터클래스

-@StringRes는 val title : Int값이 String Resource값임을 나타냄

 

2)MainViewPagerAdapter

class MainViewPagerAdapter(fragmentActivity : FragmentActivity) : FragmentStateAdapter(fragmentActivity) {

    private val fragments = listOf(
        MainTabModel(FirstFragment.newInstance(),R.string.first_fragment_label),
        MainTabModel(SecondFragment.newInstance(), R.string.second_fragment_label)
    )

    override fun getItemCount(): Int = fragments.size

    override fun createFragment(position: Int): Fragment = fragments[position].fragment

    fun getTitle(position : Int) : Int = fragments[position].title
}

-인자로 fragmentActivity를 받음

@FragmentActivity: AppCompatActivity를 서브클래스로 하는 부모클래스이므로 Activity에서 this로 받아올 수있다.

=>FragmentActivity나 그 하위 클래스에서 getSupportFragmentManger() (=supportFragmentManager) 사용가능

=> getSupportFragmentManger() 는 FragmentActivity의 메소드. 즉, fragment를 갈아끼기 위해선 인자로 FragmentActivity를 받아야 함.

-FragmentStateAdapter 상속

-getItemCount와 createFragment는 필수

=>createFragment 는 화면에 보여줄 fragment를 나타냄

 

3)FirstFragment:데이터 보내는 프래그먼트

class FirstFragment : Fragment() {
    companion object {
        fun newInstance() = FirstFragment()
    }

    private var _binding: FragmentFirstBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {

        _binding = FragmentFirstBinding.inflate(inflater, container, false)
        return binding.root

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        initView()
    }

    private fun initView() = with(binding) {
        buttonNext.setOnClickListener {
            //TODO step 1
            //setFragmentResult("bundle_key",bundleOf("bundle_key" to 88))
            //TODO step 2
            sendObject()
        }
    }

    private fun sendObject() {
        val user = User("sparta", 4)
        setFragmentResult("bundle_key_2", bundleOf("bundle_key_2" to user))
    }

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

-자기자신을 반환하는 함수는 보통 newInstance()로 정한다.

-fragment의 경우 위와같은 방식으로 새 인스턴스를 생성한다. =>팩토리 메소드

@위의 방식에서 파라미터를 추가해 인수를 전달하기도 한다.

-setFragmentResult로 데이터 전달: 위의 경우 user와 int데이터를 각각 전달

 

 

4)SecondFragment: 데이터 받는 프래그먼트

class SecondFragment : Fragment() {
    companion object {
        fun newInstance() = SecondFragment()
    }

    private var _binding: FragmentSecondBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {

        _binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        initView()
    }

    private fun initView() = with(binding) {
        /*setFragmentResultListener("bundle_key") { requestKey, bundle ->
            txtBundleNum.text = bundle.getInt("bundle_key").toString()
        }*/
        setFragmentResultListener("bundle_key_2") { requestKey, bundle ->
            val user = bundle.getParcelable<User>("bundle_key_2")
            Log.d("debug23323", user!!.name)
        }
    }


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

-setFragmentResultListener("키"){ requestKey, bundle -> }로 데이터를 받아옴

 

 

@ setFragmentResult 원리: FragmentManager을 통해서 주고 받는 방식

부모 - 자식 프래그먼트와 프래그먼트 - 액티비티간의 주고 받기도 가능

프래그먼트간 데이터 전달
부모-자식 프래그먼트간 데이터 전달

// 부모 fragment
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // We set the listener on the child fragmentManager
    childFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("bundleKey")
        // Do something with the result
    }
}
button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

 

 

@액티비티에서 프래그먼트 데이터 받기

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .setFragmentResultListener("requestKey", this) { requestKey, bundle ->
            val result = bundle.getString("bundleKey")
        }
    }
}