[android] Activity Result API

sundays·2023년 3월 16일
0

android

목록 보기
10/18
post-custom-banner

이전 포스팅에서 activity intent를 정리하다보니 result 수신에 사용하는onActivityResult() 가 deprecated 되었네요. 대신에 AndroidX Activity와 Fragment에 도입된 Activity Result API 사용을 적극 권장되고 있습니다.

callback 등록

먼저 result callback을 받기 위해서는 process 및 activity를 재 생성하게 될때 생성할때마다 callback을 실행해야 합니다. 이것은 registerForAcitivityResult() 를 사용하여 받을 수 있습니다.

@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
		@NonNull final ActivityResultContract<I, O> contract,
		@NonNull final ActivityResultRegistry registry,
		@NonNull final ActivityResultCallback<O> callback) {
	return registry.register(
			"activity_rq#" + mNextLocalRequestCode.getAndIncrement()
            , this, contract, callback);
}

@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
		@NonNull ActivityResultContract<I, O> contract,
		@NonNull ActivityResultCallback<O> callback) {
	return registerForActivityResult(contract
    		, mActivityResultRegistry, callback);
}

ActivityResultContract

액티비티의 intent를 파싱하거나 생성하는데 사용하고 있습니다.

abstract class ActivityResultContract<I, O> {
	
    // Activity#startActivityForResult
    // intent 생성
    abstract fun createIntent(context: Context, input: I): Intent
    
	// Activity#onActivityResult
    // intent 파싱
    abstract fun parseResult(resultCode: Int, intent: Intent?): O

	// 결과를 빌드할 필요 없이 주어진 입력의 결과를 확인할 수 있는 경우
    open fun getSynchronousResult(context: Context, input: I)
    	: SynchronousResult<O>? {
        return null
    }
    class SynchronousResult<T>(val value: T)
}

ActivityResultContract를 상속받아 custom contract를 작성하게 할 수 있습니다.
ActivityResultContract#createIntent 는 데이터를 전달할때 사용하는 intent를 작성할 수 있고 ActivityResultContract#parseResult는 데이터를 받아 파싱하여 result intent를 가공할 수 있습니다

class PickRingtone : ActivityResultContract<Int, Uri?>() {
    override fun createIntent(context: Context, ringtoneType: Int) =
        Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
            putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType)
        }

    override fun parseResult(resultCode: Int, result: Intent?) : Uri? {
        if (resultCode != Activity.RESULT_OK) {
            return null
        }
        return result?
        	.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
    }
}

ActivityResultContracts

custom contract를 작성하지 않고 기본으로 내장되어있는 contracts 가 존재하는데요 ActivityResultContracts 클래스를 참고 하면 정말 많은 양의 내장 contacts가 있습니다. 링크를 참조하세요

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you'd like to allow the user to select
        // as the input
        getContent.launch("image/*")
    }
}

위 코드는 기본 내장된 이미지 갤러리를 열수 있는 버튼을 가진 액티비티 입니다.

ActivityResultLauncher

ActivityResultLauncher#launch를 사용하면 contact의 내용을 가지고 액티비티를 실행하거나 result값을 받을 수 있습니다.

package androidx.activity.result;

public abstract class ActivityResultLauncher<I> {

    public void launch(@SuppressLint("UnknownNullness") I input) {
        launch(input, null);
    }

    public abstract void launch(@SuppressLint("UnknownNullness") I input,
            @Nullable ActivityOptionsCompat options);

    @MainThread
    public abstract void unregister();

    @NonNull
    public abstract ActivityResultContract<I, ?> getContract();
}

ActivityResultRegistry

launch로 activity를 실행하면 ActivityResultRegistry에 등록이 되는 로직이 작동합니다
ActivityResultRegistry#register 의 내부에서 lifecycle의 onstatechaged() 가 발생될 때마다 실행되어 콜백을 받아서 리턴 처리할 수 있습니다.

.....
LifecycleEventObserver observer = new LifecycleEventObserver() {
	public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner, @NonNull Lifecycle.Event event) {
		if (Event.ON_START.equals(event)) {
			ActivityResultRegistry.this.mKeyToCallback.put(key, new CallbackAndContract(callback, contract));
			if (ActivityResultRegistry.this.mParsedPendingResults.containsKey(key)) {
				O parsedPendingResult = ActivityResultRegistry.this.mParsedPendingResults.get(key);
				ActivityResultRegistry.this.mParsedPendingResults.remove(key);
				callback.onActivityResult(parsedPendingResult);
			}

			ActivityResult pendingResult = (ActivityResult)ActivityResultRegistry.this.mPendingResults.getParcelable(key);
			if (pendingResult != null) {
				ActivityResultRegistry.this.mPendingResults.remove(key);
				callback.onActivityResult(contract.parseResult(pendingResult.getResultCode(), pendingResult.getData()));
			}
		} else if (Event.ON_STOP.equals(event)) {
			ActivityResultRegistry.this.mKeyToCallback.remove(key);
		} else if (Event.ON_DESTROY.equals(event)) {
			ActivityResultRegistry.this.unregister(key);
		}

	}
};
...

Event.ON_START 의 경우 callback가 있는지 확인하여 onActivityResult로 결과를 등록하며
Event.ON_STOP 의 경우에는 callback할 곳에서 현재 key를 삭제하여 레지스트리를 비우고 있습니다
Event.ON_DESTROY의 경우에는 셋팅되어 있는 key에 해당하는 모든 결과들을 삭제하는 로직을 가지고 있습니다.

결론

activity lifecycle을 다음에 정리해서 같이 보면 더좋을 것같습니다

Reference

profile
develop life
post-custom-banner

0개의 댓글