Android/실전 회고

[Android/Kotlin] startForActivityForResult 가 Deprecated 라고? Activity Result APIs 를 써보자

노소래 2022. 2. 19. 07:44

[시작 전 반성]

한동안 Jetpack navigation 을 사용해서 프로젝트를 진행해서 startForActivityForResult 가 Deprecated 인지 몰랐다.

(알게된 건 작년 여름쯤) 

Deprecated 라는 것은 더이상 개발되지 않고 사라질 수 있으니 사용을 지양하라는 의미로 알고 있어서 대체제를 찾았다.  

찾아보니 Activity 를 시작하고 값을 받아오는 기능 뿐만 아니라 Permission 을 받아오는 기능을 깔끔한 콜백코드로 대체할 수 있어서 좋았다.

프로젝트에 치여 기록을 잊지말자는 반성으로 쓰기 시작해야겠다.

 

활동에서 결과 가져오기  |  Android 개발자  |  Android Developers

활동에서 결과 가져오기 개발자 앱 내의 활동이든 다른 앱의 활동이든 다른 활동을 시작하는 것이 단방향 작업일 필요는 없습니다. 다른 활동을 시작하고 다시 결과를 받을 수도 있습니다. 예를

developer.android.com

이 글은 위 문서를 읽을 때 이해하기 쉽게하기 위한 전반적인 내용과

내가 실제 프로젝트에 사용한 예시코드를 담고있다. 

 

[Activity Result APIs]

Activity Result APIs 는 result 가 시스템에의해 전달되었을 때 result 를 registering 하고 launching 하고 handling 할 수 있게 해준다. 

 

[왜 생겨났을 지 예상해보자]

액티비티를 새로 하나 시작했을 때 카메라같은 메모리를 많이 먹는 작업을 할 때 메모리가 부족하다면 앱의 프로세스와 액티비티가 시스템에 의해 죽을 수 있다.

그래서 result 콜백과 새 액티비티를 launch 하는 코드를 분리한다.

즉, 액티비티가 다시 만들어질 때 콜백을 사용하기 위해, result 콜백이 액티비티가 만들어질 때마다 등록되게 한다.

 

추가적으로 내 생각엔 기존 onActivityResult 와 onRequestPermissionsResult 같은 것은 오버라이딩하여 리퀘스트 코드를 관리하고 클래스 중간쯤에 함수를 추가로 작성하는 게 가독성을 떨어뜨린다고 생각한다. 

그래서 나는 Activity Result APIs 가 굉장히 편하다고 느껴졌다.

 

[사용 개념]

ComponentActivity 나 Fragment 에서 Activity Result APIs 는 result 콜백을 등록하기 위해 registerForActivityResult 을 제공한다. 

 

registerForActivityResult 

registerForActivityResult 은 ActivityResultContractActivityResultCallback 을 받고 ActivityResultLauncher 를 반환한다. (이 함수를 사용한다고 바로 액티비티를 시작하고 요청을 하는 게 아니다. 그저 ActivityResultLauncher 객체 반환)

이 ActivityResultLauncher 객체로 다른 액티비티를 launch 해서 액티비티를 시작하고 요청하는 것이다.

ComponentActivity 와 Fragment 는 ActivityResultCaller 라는 인터페이스 구현체이기 때문에 registerForActivityResult 를 사용할 수 있다. 

만약 ActivityResultCaller 구현체가 아닌 클래스에서 activity result 를 받고 싶다면 ActivityResultRegistry 를 직접 사용하면 된다.

 

ActivityResultContract 

ActivityResultContract 는 result 를 생성하는데 필요한 input type 과 result 의 output type 을 정의한다.

여기서 input type 은 launch 함수의 인자로 전달해야하는 타입이고 output type 는 callback 으로 전달받는 인자이다.

그리고 default contract 를 제공한다. 사진을 찍고 permission 요청 하는 것 같은 기본적인 intent action 을 default contract 로 지원한다.

물론 custom contract 도 만들 수 있다. (이에 대해선 상단 링크의 아랫부분을 참고하면 된다.)

 

 

ActivityResultCallback 

ActivityResultCallback 은 onActvityResult 라는 메소드를 하나 가진 인터베이스다. (그래서 람다로 뺄 수 있다.) 그 메소드는 ActivityResultContract 에 정의된 output type 객체를 인자로 받는다. 

요청한 작업이 끝났을 때 이 콜백이 수행된다.

 

launch

result를 생성하는 프로세스를 시작한다.

ActivityResultContract 의 input type 으로 launch 의 인자로 전달한다.

 

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
	// ActivityResultContract 에 정의된 output type 객체를 여기서 처리하면 된다.
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    val selectButton = findViewById<Button>(R.id.select_button)
    selectButton.setOnClickListener {
    	// ActivityResultContract 의 input type
        getContent.launch("image/*")
    }
}

 

[예시 코드]

Permission 받아오기

val startForRequestAccessFineLocationPermission =
        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
            if (it[Manifest.permission.ACCESS_FINE_LOCATION] != true || it[Manifest.permission.ACCESS_COARSE_LOCATION] != true) {
                showToast(R.string.toast_must_permit_access_fine_location)
            } else {
                startScanBluetoothDevicesByBLE()
            }
        }
        
        
        
   private fun requestAccessFineLocationPermission() {
        startForRequestAccessFineLocationPermission
            .launch(
                arrayOf(
                    Manifest.permission.ACCESS_COARSE_LOCATION,
                    Manifest.permission.ACCESS_FINE_LOCATION
                )
            )
    }

갤러리

 private val galleryCallback =
        registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
            uri?.let {
                uploadImage(uri, 1)
            }

        }
        
        
  galleryCallback.launch("*/*")

액티비티 시작

 private val startActivityForResultLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
            activityResult.data?.let { intent ->
                // TODO 가져온 결과 처리
            }
        }
        
       
   private fun startCoinDetailActivity(coinId: String) {
        val intent = Intent(this@CoinListActivity, CoinDetailActivity::class.java)
        intent.putExtra(PARAM_COIN_ID, coinId)
        startActivityForResultLauncher.launch(intent)
    }
    
    
    
    // DetailActivity .. 특정버튼을 누르면 아래 코드가 실행된다고 가정해보자
       Intent().putExtra(PARAM_COIN_ID, binding.tvCoinDetail.text.toString()).also { i ->
                setResult(RESULT_OK, i)
                finish()
            }