ResultApi Jump in Android

Posted by january_9th on Thu, 23 Dec 2021 23:32:22 +0100

1. Preface

Previously, when jumping from page A to page B, receiving the result from page B usually uses onActivityForResult() to receive the data, but since the jump and return data are not in one place, it is more difficult to maintain. And if a page is likely to go to many places, returning results in one place is also detrimental to decoupling. So Android introduced ResultApi in the new version, abandoning the old one.

2. Adding Dependencies

implementation 'androidx.activity:activity-ktx:1.3.1'
implementation 'androidx.fragment:fragment-ktx:1.3.6'

3. Basic Ways of Use

Here we define a jump using two activities, A->B

A.kt

 //Page used to receive jumps
    inner class ResultContact: ActivityResultContract<Boolean, String>() {
      	
      //Create jump Intent
        override fun createIntent(context: Context, input: Boolean?): Intent = Intent(this@MainActivity,MainActivity2::class.java).apply {
            putExtra("testBool",input)
//            finish()
        }

        override fun parseResult(resultCode: Int, intent: Intent?): String {
            println("YM--->intent Is it null:${null == intent}")
            val isHas = intent?.hasExtra("result")
            println("YM--->intent Include value or not:$isHas")
            return intent?.getStringExtra("result") ?: ""
        }
    }
	//Register jump objects
  private val goMainactivity2 = registerForActivityResult(ResultContact()){
            // The value received here is the value returned by the jumped Activity
            println("----->Received return value is $it")
  }
	private fun goNextPage() {
        goMainactivity2.launch(true)//Here you pass in the parameters you want to pass to the second page
  }

B.kt

 fun onClick(v: View){
        when(v.id){
            R.id.back -> {
                intent.putExtra("result","I'm the second page")
                setResult( 2,intent)
                finish()
            }
        }
    }

The rules for using this implementation are as follows:

Each ActivityResultContract needs to define input and output classes, and if you don't need any input, you can use Void (in Kotlin, Void? Or Unit) as the input type.

Each agreement must implement [createIntent()](https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract#createIntent(android.content.Context, I)) method that accepts Context and input as parameters and constructs an Intent to be used with startActivityForResult().

Each agreement must also implement [parseResult()] ( https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract#parseResult (int, android.content.Intent), which generates output based on the specified resultCode (such as Activity.RESULT_OK or Activity.RESULT_CANCELED) and Intent.

If the result of the specified input can be determined without calling createIntent(), starting another activity, and building the result with parseResult(), the agreement can optionally implement [getSynchronousResult()](https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract#getSynchronousResult(android.content.Context, I)).

4. Use ResultApi in Non-Activity/Fragment

Lifecycle Owner is the best way to manage the life cycle here. Otherwise, page destruction may occur but registered objects are not unbound unless you call ActivityResultLauncher yourself. Unregister ().

class MyLifecycleObserver(private val registry : ActivityResultRegistry)
        : DefaultLifecycleObserver {
    lateinit var getContent : ActivityResultLauncher<String>

    override fun onCreate(owner: LifecycleOwner) {
        getContent = registry.register("key", owner, GetContent()) { uri ->
            // Handle the returned Uri
        }
    }

    fun selectImage() {
        getContent.launch("image/*")
    }
}

class MyFragment : Fragment() {
    lateinit var observer : MyLifecycleObserver

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

        observer = MyLifecycleObserver(requireActivity().activityResultRegistry)
        lifecycle.addObserver(observer)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val selectButton = view.findViewById<Button>(R.id.select_button)

        selectButton.setOnClickListener {
            // Open the activity to select an image
            observer.selectImage()
        }
    }
}

5. Functions of startup system

What should we do if we want to start the function of the system? The system has made some default support for this, all encapsulated as the default Contract in ActivityResultContracts. The main references here are from the following links:

https://mp.weixin.qq.com/s/lWayiBS4T4EHcsUIgnhJzA

As you can see, the new Activity Results API seems a bit cumbersome to use, with Contract defined each time.

Google must have taken this into account, so Google has predefined a lot of Contracts, basically thinking of usage scenarios you can think of. They are all defined in the ActivityResultContracts class, and there are the following Contracts:

StartActivityForResult() 
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
CreateDocument()
OpenDocumentTree()
OpenMultipleDocuments()
OpenDocument()
GetMultipleContents()
GetContent()

These contracts are described below:

  • StartActivityForResult: A generic Contract with no conversion, Intent as input and ActivityResult as output, which is also the most common protocol.
  • RequestMultiplePermissions: Used to request a set of permissions
  • RequestPermission: Used to request a single permission
  • TakePicturePreview: Call MediaStore.ACTION_IMAGE_CAPTURE takes a picture and returns a Bitmap picture
  • TakePicture: Call MediaStore.ACTION_IMAGE_CAPTURE takes a picture and saves it to the given Uri address. Returning true indicates that the save was successful.
  • TakeVideo: Call MediaStore.ACTION_VIDEO_CAPTURE takes a video, saves it to a given Uri address, and returns a thumbnail.
  • PickContact: Get contacts from address book APP
  • GetContent: Prompts you to select a content and return a Uri address (content://form) that accesses native data through ContentResolver#openInputStream(Uri). By default, it adds Intent#CATEGORY_OPENABLE, which returns content that represents a stream.
  • CreateDocument: Prompts the user to select a document and returns a Uri starting with (file:/http:/content:).
  • OpenMultipleDocuments: Prompts the user to select documents (you can select multiple), returning their Uri separately, as a List.
  • OpenDocumentTree: Prompts the user to select a directory and returns the user's selection as a Uri, allowing the application to fully manage the documents returned to the directory.

In these predefined Contract s above, except StartActivityForResult and RequestMultiplePermissions, they are basically all about interacting with other APP s and returning scenes of data, such as taking photos, selecting pictures, selecting contacts, opening documents, and so on. StartActivityForResult and RequestMultiplePermissions are the most used.

With these predefined Contracts, it is much easier to transfer data between activities, such as the previous example, which can be simplified as follows:

1. Register the agreement and get ActivityResultLauncher:

private val myActivityLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ activityResult ->  
    if(activityResult.resultCode == Activity.RESULT_OK){
        val result = activityResult.data?.getStringExtra("result")
        Toast.makeText(applicationContext,result,Toast.LENGTH_SHORT).show()
        textView.text = "Return data: $result"
    }
}

2. Construct the data you need to pass and start page jumps

button.setOnClickListener {
    val  intent = Intent(this,SecondActivity::class.java).apply {
         putExtra("name","Hello,Most Technological TOP")
    }
    myActivityLauncher.launch(intent)
}

OK, that's it!!!

For example, our permissions, applications, see the code:

request_permission.setOnClickListener {
    requestPermission.launch(permission.BLUETOOTH)
}

request_multiple_permission.setOnClickListener {
    requestMultiplePermissions.launch(
        arrayOf(
            permission.BLUETOOTH,
            permission.NFC,
            permission.ACCESS_FINE_LOCATION
        )
    )
}

// Request single permission
private val requestPermission =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        // Do something if permission granted
        if (isGranted) toast("Permission is granted")
        else toast("Permission is denied")
    }

// Request a set of permissions
private val requestMultiplePermissions =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map<String, Boolean> ->
        // Do something if some permissions granted or denied
        permissions.entries.forEach {
            // Do checking here
        }                                                                             
}

With this, we can discard all third-party privilege request frameworks and simply put these two Contract s into BaseActivity or extract them into a separate class to apply for privileges anytime, anywhere. Is it convenient!!!

6. Reference Links

  1. Bye! onActivityResult! Hello, Activity Results API!

    https://mp.weixin.qq.com/s/lWayiBS4T4EHcsUIgnhJzA

  2. Get results from Activity

    https://developer.android.com/training/basics/intents/result

Topics: Android jetpack