6

I have following code

val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
    //Some code here..
}

and somewhere else ,

getContent.launch("application/vnd.openxmlformats-officedocument.wordprocessingml.document")

I can successfully select the docx file . I need to select either pdf or doc or text or docx rather just to be able to select one kind(here docx).

Nikki
  • 71
  • 1
  • 7

3 Answers3

20

I would recommend using OpenDocument instead of GetContent.

val documentPick =
    registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
        // do something 
    }

While launching the intent just add the mime types you want to get

documentPick.launch(
            arrayOf(
                "application/pdf",
                "application/msword",
                "application/ms-doc",
                "application/doc",
                "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                "text/plain"
            )
        )
Hussain
  • 1,243
  • 12
  • 21
  • Thanks! This is exactly what I needed :) – Nikki Mar 27 '21 at 20:00
  • Android docs are not updated yet properly with `registerForActivityResult` , it still shows `startActivityForResult` instead :/ – J.Stange Apr 28 '21 at 19:38
  • 3
    Where did you find the docs on how to pass this `arrayOf()` arg to `launch`? I have been searching for this syntax in the docs and can't seem to find anything close. – topher217 Dec 10 '21 at 14:44
  • 1
    How exactly we will know, which document type we picked? – kallis Apr 02 '23 at 22:15
5

Using array doesn't work in my case. Instead, the following worked correctly. Here custom class of ActivityResultContracts.GetContent is used. fun "createIntent" is overrided to customize method to make intent from the input.

    // custom class of GetContent: input string has multiple mime types divided by ";"
    // Here multiple mime type are divided and stored in array to pass to putExtra.
    // super.createIntent creates ordinary intent, then add the extra. 
    class GetContentWithMultiFilter:ActivityResultContracts.GetContent() {
        override fun createIntent(context:Context, input:String):Intent {
            val inputArray = input.split(";").toTypedArray()
            val myIntent = super.createIntent(context, "*/*")
            myIntent.putExtra(Intent.EXTRA_MIME_TYPES, inputArray)
            return myIntent
        }
    }

    // define ActivityResultLauncher to launch : using custom GetContent
    val getContent=registerForActivityResult(GetContentWithMultiFilter()){uri ->
    ... // do something
    }

    // launch it
    // multiple mime types are separated by ";".
    val inputString="audio/mpeg;audio/x-wav;audio/wav"
    getContent.launch(inputString)
lglink
  • 51
  • 1
  • 1
  • Make sure you call use the intent from the super! `val intent = super.createIntent(context, input)` – em_ Aug 11 '22 at 00:26
1

The GetContent contract only takes a single String argument, which can only contain a single MIME type.

The OpenDocument contract takes an array of String, each a single MIME type.

There are pros and cons to each so see this StackOverflow question to help you decide which to use. I have no idea why it was decided to limit GetContact to a single MIME type.

lglink's answer shows one way to get multi-MIME GetContent by extending that contract with a ;-delimited String of concatenated MIME types.

I solved it another way, by following the official docs to make a custom contract:

class BespokeContract: ActivityResultContract<Array<String>, Uri?>() {
    @CallSuper
    override fun createIntent(context: Context, input: Array<String>): Intent {
        return Intent(Intent.ACTION_GET_CONTENT)
            .addCategory(Intent.CATEGORY_OPENABLE)
            .setType("*/*")
            .putExtra(Intent.EXTRA_MIME_TYPES, input)
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
        return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
    }
}

As a newcomer to Android programming, I struggled a lot to get this. There were a few things those docs didn't mention without which my code was not working. Perhaps they are common knowledge amongst experienced Android devs, but for anyone jumping in in 2023, not so much. I ended up having to look at the Android source for GetContent:

  1. A GetContent contract uses the ACTION_GET_CONTENT Intent:
        Intent(Intent.ACTION_GET_CONTENT)
    
  2. If you're making a new contract rather than extending an existing one, you need to add the OPENABLE category. You'll get a confusing error if you don't:
            .addCategory(Intent.CATEGORY_OPENABLE)
    
  3. Before you put extra mime types you must first use setType(), even though what you put there will be ignored. */* is commonly seen and is perhaps the shortest. Again you'll get a confusing error if you don't:
            .setType("*/*")
    
  4. And of course the main part:
            .putExtra(Intent.EXTRA_MIME_TYPES, input)
    

For anyone confused like I was by seeing many ways of doing this when searching the internet for help and not knowing which are deprecated and which are due to old Views code vs new Compose code, etc, this is an equivalent different way to code createIntent:

override fun createIntent(context: Context, input: Array<String>) =
    Intent(Intent.ACTION_GET_CONTENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "*/*"
        putExtra(Intent.EXTRA_MIME_TYPES, input)
    }
hippietrail
  • 15,848
  • 18
  • 99
  • 158