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

New ActivityResult and FragmentResult

New ActivityResult and FragmentResult

354271902cd8ba2762d05b251dfa0f84?s=128

pluulove (노현석)

March 03, 2021
Tweet

Transcript

  1. New Result Activity / Fragment pluu

  2. Deprecated.

  3. Index 1. New Activity Result 2. New Fragment Result

  4. New Activity Result.

  5. Release Notes Add • activity:1.2.0-alpha02 • fragment:1.3.0-alpha02 API Deprecated •

    activity:1.2.0-alpha04 • fragment:1.3.0-alpha04
  6. 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
  7. 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
  8. Deprecation ComponentActivity FragmentActivity Fragment sta rt ActivityForResult() @Deprecated @SuppressWarnings 


    (“deprecation”) @Deprecated onActivityResult() @Deprecated @SuppressWarnings 
 (“deprecation”) @Deprecated requestPermissions() - - @Deprecated onRequestPermissionsResult() @Deprecated @SuppressWarnings 
 (“deprecation”) @Deprecated
  9. 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
  10. 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
  11. Structure class ActivityResultSampleActivity : AppCompatActivity() { private val requestActivity =

    registerForActivityResult( Sta rt ActivityForResult() ) { activityResult -> // do action } private fun sta rt SecondView() { requestActivity.launch(/**create intent*/) } }
  12. 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 } }
  13. 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
  14. 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
  15. 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
  16. 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
  17. RequestCodeо হחؘਃ?!

  18. 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
  19. 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
  20. 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)
  21. @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
  22. Activity / Fragment ActivityResultContract parseResult createIntent ActivityResultLauncher launch ActivityResultCaller dispatchResult

    ActivityResultRegistry onLaunch register ActivityResultCallback onActivityResult LifecycleEventObserver ON_START onRequestPermissionsResult onActivityResult requestPermissions sta rt ActivityForResult registerForActivityResult
  23. New Fragment Result.

  24. New Fragment Result API • Update, fragment:1.3.0-alpha04 • Fragmentח Ѿҗ

    ࣻन റ, ࢤݺ઱ӝо STARTED ੌ ٸ ௒ߔ ܻझց प೯ • Fragment Ѿҗо FragmentManagerী ੷੢ • ParentFragmentManagerܳ ࢎਊೞৈ setFragmentResultListener() ژח setFragmentResult() API ࢎਊ
  25. Sample Master/Detail Master Fragment Detail Fragment

  26. 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
  27. 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
  28. 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
  29. 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" } }
  30. Via FragmentManager h tt ps://developer.android.com/guide/fragments/communicate

  31. 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); } }
  32. 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)); }
  33. Sample 2 Fragment (Stack 3) in Fragment (Stack 2) in

    Fragment (Stack 1) in Activity Activity Stack 1 Stack 2 Stack 3 setResult Stack
  34. Stack Style Parent੄ ChildFragment == Child੄ ParentFragment

  35. Stack Style Activity Stack 1 Fragment Stack 2 Fragment Parent

    FragmentManager Child FragmentManager Parent FragmentManager Child FragmentManager Suppo rt FragmentManager Fragment Manager
  36. 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
  37. 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
  38. END.