Upgrade to Pro — share decks privately, control downloads, hide ads and more …

New ActivityResult and FragmentResult

New ActivityResult and FragmentResult

pluulove (노현석)

March 03, 2021
Tweet

More Decks by pluulove (노현석)

Other Decks in Programming

Transcript

  1. Deprecated Deprecated Old Request class SampleActivity : AppCompatActivity() { private

    val request_second_code = 100 private fun sta rt SecondView() { val intent = Intent(this, ResultSecondActivity::class.java) sta rt ActivityForResult(intent, request_second_code) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == request_second_code) { // do action } } } Basic
  2. Old Request class SampleActivity : AppCompatActivity() { private val request_location_code

    = 101 private fun requestLocation() { val permission = Manifest.permission.ACCESS_FINE_LOCATION if (checkPermissions(permission)) { toast("Permission granted") } else { requestPermissions(arrayOf(permission), request_location_code) } } private fun checkPermissions(vararg permissions: String): Boolean { return permissions.all { permission -> ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED } } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == request_location_code) { if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { toast("Permission granted") } else { toast("Permission denied or canceled") } } } Location
  3. Deprecation ComponentActivity FragmentActivity Fragment sta rt ActivityForResult() @Deprecated @SuppressWarnings 


    (“deprecation”) @Deprecated onActivityResult() @Deprecated @SuppressWarnings 
 (“deprecation”) @Deprecated requestPermissions() - - @Deprecated onRequestPermissionsResult() @Deprecated @SuppressWarnings 
 (“deprecation”) @Deprecated
  4. New Result API class SampleActivity : AppCompatActivity() { private val

    requestActivity = registerForActivityResult( Sta rt ActivityForResult() ) { activityResult -> // do action } private fun sta rt SecondView() { val intent = Intent(context, ResultSecondActivity::class.java) requestActivity.launch(intent) } } Basic
  5. New Result API class SampleActivity : AppCompatActivity() { private val

    requestLocation = registerForActivityResult( RequestPermission(), ACCESS_FINE_LOCATION ) { isGranted -> // do action } private fun sta rt Location() { requestLocation.launch() } } Location [ ✔︎ ] checkSelfPermission [ ✔︎ ] requestPermissions [ ✔︎ ] call ActivityResultCallback
  6. Structure class ActivityResultSampleActivity : AppCompatActivity() { private val requestActivity =

    registerForActivityResult( Sta rt ActivityForResult() ) { activityResult -> // do action } private fun sta rt SecondView() { requestActivity.launch(/**create intent*/) } }
  7. Signature h tt ps://cs.android.com/androidx/pla tf orm/frameworks/suppo rt /+/androidx-main:activity/activity/src/main/java/androidx/activity/ComponentActivity.java @NonNull @Override

    public fi nal <I, O> ActivityResultLauncher<I> registerForActivityResult( @NonNull ActivityResultContract<I, O> contract, @NonNull ActivityResultCallback<O> callback) { return registerForActivityResult(contract, mActivityResultRegistry, callback); } @NonNull @Override public fi nal <I, O> ActivityResultLauncher<I> registerForActivityResult( @NonNull fi nal ActivityResultContract<I, O> contract, @NonNull fi nal ActivityResultRegistry registry, @NonNull fi nal ActivityResultCallback<O> callback) { return registry.register( "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback); } class ActivityResultSampleActivity : AppCompatActivity() { private val requestActivity = registerForActivityResult( Sta rt ActivityForResult() ) { activityResult -> // do action } }
  8. ActivityResultRegistry ActivityResultCallbackਸ ੷੢ೞח Registry ActivityResult* ActivityResultContract<I, O> Input Typeী ٮܲ

    Activityܳ ഐ୹ೞҊ Output Typeਵ۽ ୊ܻೞب۾ ҅ড ActivityResultCallback<O> Activity#onActivityResult੉ ഐ୹ؼ ٸ ࢎਊغח Callback ActivityResultLauncher<I> ActivityResultContractܳ प೯ೞӝ ਤೠ Launcher FragmentActivity ComponentActivity ActivityResultCaller ActivityResult झఋੌ ഐ୹੗ Fragment
  9. Signature ActivityResultContract Input createIntent Output Sta rt ActivityForResult Intent ActivityResult

    GetContent String Intent.ACTION_GET_CONTENT Uri RequestMultiplePermissions String[] Map<String, Boolean> PickContact Void Intent.ACTION_PICK Uri TakeVideo Uri MediaStore.ACTION_VIDEO_CAPTURE Bitmap OpenDocument String[] Intent.ACTION_OPEN_DOCUMENT Uri TakePicture Uri MediaStore.ACTION_IMAGE_CAPTURE Boolean Sta rt IntentSenderForResult IntentSenderRequest ActivityResult OpenDocumentTree Uri Intent.ACTION_OPEN_DOCUMENT_TREE Uri OpenMultipleDocuments String[] Intent.ACTION_OPEN_DOCUMENT List<Uri> TakePicturePreview Void MediaStore.ACTION_IMAGE_CAPTURE Bitmap CreateDocument String Intent.ACTION_CREATE_DOCUMENT Uri GetMultipleContents String Intent.ACTION_GET_CONTENT List<Uri> RequestPermission String Boolean
  10. StartActivityForResult public static fi nal class Sta rt ActivityForResult extends

    ActivityResultContract<Intent, ActivityResult> { public static fi nal String EXTRA_ACTIVITY_OPTIONS_BUNDLE = "androidx.activity.result" + ".contract.extra.ACTIVITY_OPTIONS_BUNDLE"; @NonNull @Override public Intent createIntent(@NonNull Context context, @NonNull Intent input) { return input; } @NonNull @Override public ActivityResult parseResult( int resultCode, @Nullable Intent intent) { return new ActivityResult(resultCode, intent); } } onActivityResult
  11. RequestPermission public static fi nal class RequestPermission extends ActivityResultContract<String, Boolean>

    { @NonNull @Override public Intent createIntent(@NonNull Context context, @NonNull String input) { return RequestMultiplePermissions.createIntent(new String[] { input }); } @NonNull @Override public Boolean parseResult(int resultCode, @Nullable Intent intent) { if (intent == null || resultCode != Activity.RESULT_OK) return false; int[] grantResults = intent.getIntArrayExtra(EXTRA_PERMISSION_GRANT_RESULTS); if (grantResults == null || grantResults.length == 0) return false; return grantResults[0] == PackageManager.PERMISSION_GRANTED; } @Override public @Nullable SynchronousResult<Boolean> getSynchronousResult( @NonNull Context context, @Nullable String input) { if (input == null) { return new SynchronousResult<>(false); } else if (ContextCompat.checkSelfPermission(context, input) == PackageManager.PERMISSION_GRANTED) { return new SynchronousResult<>(true); } else { // proceed with permission request return null; } } } onRequestPermissionsResult launch
  12. requestCode public abstract class ActivityResultRegistry { @NonNull public fi nal

    <I, O> ActivityResultLauncher<I> register( @NonNull fi nal String key, @NonNull fi nal LifecycleOwner lifecycleOwner, @NonNull fi nal ActivityResultContract<I, O> contract, @NonNull fi nal ActivityResultCallback<O> callback) { ... fi nal int requestCode = registerKey(key); ... return new ActivityResultLauncher<I>() { @Override public void launch(I input, @Nullable ActivityOptionsCompat options) { onLaunch(requestCode, contract, input, options); } ... }; } } h tt ps://cs.android.com/androidx/pla tf orm/frameworks/suppo rt /+/androidx-main:activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java generated requestCode
  13. requestCode public abstract class ActivityResultRegistry { // Use upper 16

    bits for request codes private static fi nal int INITIAL_REQUEST_CODE_VALUE = 0x00010000; private Random mRandom = new Random(); /** * Generate a random number between the initial value (00010000) inclusive, and the max * integer value. If that number is already an existing request code, generate another until * we fi nd one that is new. * * @return the number */ private int generateRandomNumber() { int number = mRandom.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1) + INITIAL_REQUEST_CODE_VALUE; while (mRcToKey.containsKey(number)) { number = mRandom.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1) + INITIAL_REQUEST_CODE_VALUE; } return number; } } Range 65536 ~ Int.MAX_VALUE h tt ps://cs.android.com/androidx/pla tf orm/frameworks/suppo rt /+/androidx-main:activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java
  14. ActivityResultRegistry mActivityResultRegistry = new ActivityResultRegistry() { @Override public <I, O>

    void onLaunch( fi nal int requestCode, @NonNull ActivityResultContract<I, O> contract, I input, @Nullable ActivityOptionsCompat options) { ComponentActivity activity = ComponentActivity.this; // Immediate result path fi nal ActivityResultContract.SynchronousResult<O> synchronousResult = contract.getSynchronousResult(activity, input); if (synchronousResult != null) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { dispatchResult(requestCode, synchronousResult.getValue()); } }); return; } // Sta rt activity path ... } }; ActivityResultRegistry (1)
  15. @Override public <I, O> void onLaunch(...) { // Sta rt

    activity path Intent intent = contract.createIntent(activity, input); ... if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) { // requestPermissions path String[] permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS); if (permissions == null) { return; } List<String> nonGrantedPermissions = new ArrayList<>(); for (String permission : permissions) { if (checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid()) != PackageManager.PERMISSION_GRANTED) { nonGrantedPermissions.add(permission); } } if (!nonGrantedPermissions.isEmpty()) { ActivityCompat.requestPermissions(activity, nonGrantedPermissions.toArray(new String[0]), requestCode); } } else if (ACTION_INTENT_SENDER_REQUEST.equals(intent.getAction())) { ... } else { // sta rt ActivityForResult path ActivityCompat.sta rt ActivityForResult(activity, intent, requestCode, optionsBundle); } } ActivityResultRegistry (2) Permissions sta rt ActivityForResult
  16. Activity / Fragment ActivityResultContract parseResult createIntent ActivityResultLauncher launch ActivityResultCaller dispatchResult

    ActivityResultRegistry onLaunch register ActivityResultCallback onActivityResult LifecycleEventObserver ON_START onRequestPermissionsResult onActivityResult requestPermissions sta rt ActivityForResult registerForActivityResult
  17. New Fragment Result API • Update, fragment:1.3.0-alpha04 • Fragmentח Ѿҗ

    ࣻन റ, ࢤݺ઱ӝо STARTED ੌ ٸ ௒ߔ ܻझց प೯ • Fragment Ѿҗо FragmentManagerী ੷੢ • ParentFragmentManagerܳ ࢎਊೞৈ setFragmentResultListener() ژח setFragmentResult() API ࢎਊ
  18. Old Version class FragmentB : Fragment() { // Fragment р੄

    ాनਊ Listener ੿੄ inte rf ace OnResultListener { fun onResult(value: String) } private var listener: OnResultListener? = null fun setListener(listener: OnResultListener) { this.listener = listener } private fun clickDone() { listener?.onResult(/** Write result */) } } class FragmentA : Fragment(), FragmentB.OnResultListener { private fun showFragmentB() { parentFragmentManager.commit { replace(R.id.container, FragmentB().apply { // FragmentB ಴दೡٸ Listenerܳ ੹׳ setListener(this@FragmentA) }) addToBackStack(null) } } // Implement FragmentB.OnResultListener override fun onResult(value: String) { // do action } } (1) setListener
  19. Old Version class FragmentB : Fragment() { // Fragment р੄

    ాनਊ Listener ੿੄ inte rf ace OnResultListener { fun onResult(value: String) } private var listener: OnResultListener? = null override fun onA tt ach(context: Context) { super.onA tt ach(context) val p = parentFragment if (p is OnResultListener) { this.listener = p } } private fun clickDone() { listener?.onResult(/** Write result */) } } class FragmentA : Fragment(), FragmentB.OnResultListener { private fun showFragmentB() { parentFragmentManager.commit { replace(R.id.container, FragmentB()) addToBackStack(null) } } // Implement FragmentB.OnResultListener override fun onResult(value: String) { // do action } } (2) Check ParentFragment
  20. Use SharedViewModel class ListFragment : Fragment() { // Using the

    activityViewModels() Kotlin prope rt y delegate from the // fragment-ktx a rt ifact to retrieve the ViewModel in the activity scope private val viewModel: ItemViewModel by activityViewModels() // Called when the item is clicked fun onItemClicked(item: Item) { // Set a new item viewModel.selectItem(item) } } class MainActivity : AppCompatActivity() { // Using the viewModels() Kotlin prope rt y delegate from the activity-ktx // a rt ifact to retrieve the ViewModel in the activity scope private val viewModel: ItemViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.selectedItem.observe(this, Observer { item -> // Pe rf orm an action with the latest item data }) } } h tt ps://developer.android.com/guide/fragments/communicate#fragments
  21. New Fragment Result class DetailFragment : Fragment(R.layout.fragment_detail) { override fun

    onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setFragmentResultListener(MasterFragment.requestKey) { _, bundle -> binding.tvLabel.text = bundle.getString( MasterFragment.resultKey ) } } } class MasterFragment : ListFragment() { private val list = (0..20).map { "Item $it" } override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) { super.onListItemClick(l, v, position, id) setFragmentResult( requestKey, bundleOf(resultKey to list[position]) ) } companion object { const val requestKey = " fl exible" const val resultKey = "item" } }
  22. FragmentManager public abstract class FragmentManager implements FragmentResultOwner { private fi

    nal ConcurrentHashMap<String, Bundle> mResults = new ConcurrentHashMap<>(); private fi nal ConcurrentHashMap<String, LifecycleAwareResultListener> mResultListeners = new ConcurrentHashMap<>(); } private static class LifecycleAwareResultListener implements FragmentResultListener { private fi nal Lifecycle mLifecycle; private fi nal FragmentResultListener mListener; private fi nal LifecycleEventObserver mObserver; LifecycleAwareResultListener(@NonNull Lifecycle lifecycle, @NonNull FragmentResultListener listener, @NonNull LifecycleEventObserver observer) { mLifecycle = lifecycle; mListener = listener; mObserver = observer; } public boolean isAtLeast(Lifecycle.State state) { return mLifecycle.getCurrentState().isAtLeast(state); } @Override public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle result) { mListener.onFragmentResult(requestKey, result); } public void removeObserver() { mLifecycle.removeObserver(mObserver); } }
  23. FragmentManager @Override public fi nal void setResult(@NonNull String requestKey, @Nullable

    Bundle result) { if (result == null) { // if the given result is null, remove the result mResults.remove(requestKey); return; } // Check if there is a listener waiting for a result with this key LifecycleAwareResultListener resultListener = mResultListeners.get(requestKey); // if there is and it is sta rt ed, fi re the callback if (resultListener != null && resultListener.isAtLeast(Lifecycle.State.STARTED)) { resultListener.onFragmentResult(requestKey, result); } else { // else, save the result for later mResults.put(requestKey, result); } } @SuppressLint("SyntheticAccessor") @Override public fi nal void setResultListener(@NonNull fi nal String requestKey, @NonNull fi nal LifecycleOwner lifecycleOwner, @Nullable fi nal FragmentResultListener listener) { if (listener == null) { mResultListeners.remove(requestKey); return; } fi nal Lifecycle lifecycle = lifecycleOwner.getLifecycle(); if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) { return; } LifecycleEventObserver observer = new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_START) { Bundle storedResult = mResults.get(requestKey); if (storedResult != null) { listener.onFragmentResult(requestKey, storedResult); setResult(requestKey, null); } } if (event == Lifecycle.Event.ON_DESTROY) { lifecycle.removeObserver(this); mResultListeners.remove(requestKey); } } }; lifecycle.addObserver(observer); mResultListeners.put(requestKey, new LifecycleAwareResultListener(lifecycle, listener)); }
  24. Sample 2 Fragment (Stack 3) in Fragment (Stack 2) in

    Fragment (Stack 1) in Activity Activity Stack 1 Stack 2 Stack 3 setResult Stack
  25. Stack Style Activity Stack 1 Fragment Stack 2 Fragment Parent

    FragmentManager Child FragmentManager Parent FragmentManager Child FragmentManager Suppo rt FragmentManager Fragment Manager
  26. Stack Style Activity Stack 1 Fragment Stack 2 Fragment Suppo

    rt FragmentManager Fragment Manager Parent FragmentManager Child FragmentManager Parent FragmentManager Child FragmentManager (3) setFragmentResult (1) setFragmentResultListener (2) setFragmentResultListener (4) setFragmentResult
  27. Reference • Ge tt ing a result from an activity

    : h tt ps://developer.android.com/training/ basics/intents/result • Communicating with fragments : h tt ps://developer.android.com/guide/ fragments/communicate