たぬきすのアプリ開発日記

アプリ開発の備忘録兼日記

【Android】ShareIntentで共有先のアプリやアプリごとに共有するデータをカスタマイズできるクラスを作ってみた

どうも。たぬきすです。

最近、仕事でAndroidで画像やテキストをSNSに共有する機能を作成することになり、思いのほか詰まったので記事にしようと思いました。
単純に共有するだけなら数行のコードで簡単に実装できるのですが、タイトルに示したような細かい条件をつけると実装に工夫が必要になります。

一番簡単な方法はこんな感じ

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // シェアインテントの作成
        val shareIntent = Intent(Intent.ACTION_SEND)
        shareIntent.setType("text/plain")   //共有する内容のタイプ
        shareIntent.putExtra(Intent.EXTRA_TEXT, "test")     // テキストを共有
        val chooser = Intent.createChooser(shareIntent, "共有")

        var shareButton = findViewById<Button>(R.id.btShare)

        // ボタンを押してシェア開始
        shareButton.setOnClickListener {
            startActivity(chooser)
        }

    }
}

実行結果は下の通り

f:id:tanukis:20210823065221g:plain

上のGIFのようにこれだけでもシェアはできるのですが、特定のアプリのみに共有することはできませんし、ツイッターのみにハッシュタグをつけるといったカスタマイズもできません。

なので、今回はこれらの目的を実現できるクラスを作成してきたので紹介したいと思います。

下に参考にしたURLを貼っておきます。

https://codehero.jp/android/9730243/how-to-filter-specific-apps-for-action-send-intent-and-set-a-different-text-forcodehero.jp

実装

下がそのクラスになります

class ShareIntentCreator(private val packageManager: PackageManager) {

    private val map = HashMap<String, Intent>()

    public fun create(): Intent {

        val plainIntent = Intent()
        plainIntent.setAction(Intent.ACTION_SEND)
        plainIntent.setType("")

        val sendIntent = Intent()
        sendIntent.setAction(Intent.ACTION_SEND)
        sendIntent.setType("*/*")

        val openInChooser = Intent.createChooser(plainIntent, "共有")
        val resInfoList = packageManager.queryIntentActivities(sendIntent, 0)

        val intentList = ArrayList<LabeledIntent>()

        for (resInfo in resInfoList) {
            val name = resInfo.activityInfo.name

            var targetIntent: Intent? = null

            for(targetPackageName in map.keys) {
                if(name.toLowerCase(Locale.getDefault()).contains(targetPackageName.toLowerCase(Locale.getDefault()))) {
                    targetIntent = map.get(targetPackageName)
                    break;
                }
            }

            if(targetIntent == null) {
                continue
            }

            targetIntent.setComponent(ComponentName(resInfo.activityInfo.packageName, resInfo.activityInfo.name))
            intentList.add(LabeledIntent(targetIntent, resInfo.activityInfo.packageName, resInfo.loadLabel(packageManager), resInfo.icon))

        }


        val extraIntents = intentList.toTypedArray()
        openInChooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents)

        return openInChooser
    }

    public fun addTargetActivity(targetName: String, intent: Intent) {
        map.put(targetName, intent)
    }

}

このクラスがやっていることをざっくり説明すると、

共有するアクティビティがない空のchooserを作成

共有できる全てのアクティビティをもつインテントからアクティビティ情報を取得

取得したアクティビティ情報からchooserに含めたいものをLabeledIntentにしてリスト化する

空のchooserにリスト化したLabeledIntentを追加する

といった流れになっています。

使い方はこんな感じ

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val shareIntentCreator = ShareIntentCreator(this.packageManager);
        val twitterIntent = Intent()
                .setAction(Intent.ACTION_SEND)
                .putExtra(Intent.EXTRA_TEXT, "#ツイッター")
                .setType("text/plain")
        shareIntentCreator.addTargetActivity("twitter.composer", twitterIntent)

        val gmailIntent = Intent()
                .setAction(Intent.ACTION_SEND)
                .putExtra(Intent.EXTRA_TEXT, "Gメール")
                .setType("text/plain")
        shareIntentCreator.addTargetActivity("gmail", gmailIntent)

        val chooser = shareIntentCreator.create()

        var shareButton = findViewById<Button>(R.id.btShare)

        // ボタンを押してシェア開始
        shareButton.setOnClickListener {
            startActivity(chooser)
        }

    }
}

addTargetActivityメソッドにアクティビティ名と共有内容を追加してcreateメソッドでシェアインテントを作成するだけです。
あとはインテントを開始すれば下のような結果になります。

f:id:tanukis:20210826221310g:plain

問題点

この方法はAndroid9までは全てのアクティビティを表示できていたのですが、Android10から2つまでしか表示できなくなるといった仕様になったみたいです。

https://issuetracker.google.com/issues/134367295?pli=1

stackoverflow.com

上のナレッジでは代替手段として共有しないアクティビティを選択して共有から除外する方法を提案していますが、この方法だとアプリごとに共有するデータを設定することはできない気がするので、今回の目的は達成できませんね
いっそのこと共有画面を自作してしまうのもありかもしれません。めんどくさいのでそこまでやる気はないですが。

今回はこの辺で終わろうと思います。誰かの参考になれたら幸いです。

それではまた!