The Ultimate Sharing snippet for social platforms

This recipe contains a big snippet that helps sharing media to various social platforms, and the instructions to set it up. Includes working with the Twitter and Facebook SDKs.

Set-up Facebook

After registering a Facebook app, enter the details in your module’s gradle file:

...
    defaultConfig {
        ...
        def facebookId = "12345"; // TODO Change this
        resValue "string", "facebook_app_id", "$facebookId"
        resValue "string", "fb_login_protocol_scheme", "fb$facebookId"
        resValue "string", "fb_provider", "com.facebook.app.FacebookContentProvider$facebookId"
        ...
    }
}

dependencies {
    ...
    // Social
    implementation "com.facebook.android:facebook-android-sdk:4.30.0"
    implementation "com.twitter.sdk.android:twitter-core:3.1.1"
    ...
}

Also add the necessary details to the AndroidManifest.xml:

...
<application>
  
        <meta-data
            android:name="com.facebook.sdk.ApplicationId"
            android:value="@string/facebook_app_id" />

        <activity
            android:name="com.facebook.FacebookActivity"
            android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
            android:label="@string/app_name" />
        <activity
            android:name="com.facebook.CustomTabActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data android:scheme="@string/fb_login_protocol_scheme" />
            </intent-filter>
        </activity>
  
        <provider
            android:name="com.facebook.FacebookContentProvider"
            android:authorities="@string/fb_provider"
            android:exported="true" />
  
  </activity>

Include the ShareUtil.kt snippet

This snippet contains various methods for sharing that are also explained in another snippet. The methods are pretty self-explanatory. Here’s a list of what it can do:

  • ‘Save a GIF to the gallery’ - since GIFs can’t be stored in the media store, this saves it to the Downloads folder instead.
  • Share to SMS, WhatsApp, email, etc.
  • Upload pictures and videos to Facebook and Twitter.
  • Share links to online pictures and videos to Facebook and Twitter.
  • Open the Play Store for apps that are not installed.
package com.pixplicity.shareutil

import android.app.DownloadManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Context.DOWNLOAD_SERVICE
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.provider.Telephony
import android.support.v4.app.ShareCompat
import android.support.v4.content.FileProvider
import android.support.v7.app.AppCompatActivity
import com.facebook.FacebookSdk
import com.facebook.share.model.*
import com.facebook.share.widget.ShareDialog
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException

class ShareUtil {

    companion object {

        const val INSTAGRAM = "com.instagram.android"
        @Deprecated(message = "Don't use for uploading files.", replaceWith = ReplaceWith("shareVideoToFacebook, shareBitmapToFacebook"))
        const val FACEBOOK = "com.facebook.katana"
        @Deprecated(message = "Don't use for uploading files.", replaceWith = ReplaceWith("shareVideoToFacebook, shareBitmapToFacebook"))
        const val FACEBOOK_LITE = "com.facebook.lite"
        const val FACEBOOK_MESSENGER = "com.facebook.orca"
        const val TWITTER = "com.twitter.android"
        const val WHATSAPP = "com.whatsapp"

        fun getSharedFilePath(context: Context, uri: Uri): File = File(File(context.filesDir, SHARED_FOLDER), uri.lastPathSegment)

        fun getUriForFile(context: Context, file: File): Uri = FileProvider.getUriForFile(context, "${context.packageName}.sharefileprovider", file)

    }

    /**
     * GIFs can't be inserted into the media store (just appears as a static jpeg) so we save it into the Downloads folder instead.
     */
    fun saveGifToDownloadsFolder(context: Context, gifFile: File) {
        val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        val app = context.getString(R.string.app_name)
        val now = System.currentTimeMillis()
        val file = File(dir, "${app}-${now}.gif")
        gifFile.copyTo(file)
        val downloadManager = context.getSystemService(DOWNLOAD_SERVICE) as DownloadManager
        downloadManager.addCompletedDownload(file.name, file.name, true, MIME_TYPE_IMAGE_GIF, file.absolutePath, file.length(), true)
    }

    private fun createIntentBuilder(activity: AppCompatActivity, content: String?): ShareCompat.IntentBuilder =
            ShareCompat.IntentBuilder.from(activity).apply {
                if (content != null) {
                    setText(content)
                    setType(MIME_TYPE_PLAIN)
                }
            }

    private fun createIntentBuilder(activity: AppCompatActivity, fileUri: Uri?): ShareCompat.IntentBuilder {
        val intent = ShareCompat.IntentBuilder.from(activity)
                .setType(MIME_TYPE_IMAGE)
        if (fileUri != null) {
            intent.addStream(fileUri)
        }
        return intent
    }

    fun createChooserIntent(activity: AppCompatActivity, fileUri: Uri): Intent =
            createIntentBuilder(activity, fileUri)
                    .createChooserIntent()

    fun createChooserIntent(activity: AppCompatActivity, content: String): Intent =
            createIntentBuilder(activity, content)
                    .createChooserIntent()

    fun createShareIntent(activity: AppCompatActivity, fileUri: Uri): Intent =
            createIntentBuilder(activity, fileUri)
                    .intent

    fun createShareIntent(activity: AppCompatActivity, content: String): Intent =
            createIntentBuilder(activity, content)
                    .intent

    fun createShareIntentForPackage(activity: AppCompatActivity, content: String?, packageName: String): Intent =
            createIntentBuilder(activity, content)
                    .intent
                    .setPackage(packageName)

    fun createShareIntentForPackage(activity: AppCompatActivity, fileUri: Uri?, packageName: String): Intent =
            createIntentBuilder(activity, fileUri)
                    .intent
                    .setPackage(packageName)

    /**
     * Checks if the user has a certain app installed
     *
     * @return `true` if the user has the app, `false` otherwise.
     */
    fun hasApp(packageManager: PackageManager, packageName: String): Boolean = try {
        val info = packageManager.getApplicationInfo(packageName, 0)
        info != null
    } catch (e: PackageManager.NameNotFoundException) {
        false
    }

    /**
     * Launches the share intent for the specified application, if it is installed.
     */
    fun launchIf(activity: AppCompatActivity, fileUri: Uri?, packageName: String): Boolean {
        if (hasApp(activity.packageManager, packageName)) {
            activity.startActivity(createShareIntentForPackage(activity, fileUri, packageName))
            return true
        }
        return false
    }

    /**
     * Launches the Play store to download the specified application.
     * If this is a device without Google Play, falls back to opening Google Play on the web.
     */
    fun launchGooglePlay(activity: AppCompatActivity, packageName: String) {
        try {
            activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName)))
        } catch (anfe: android.content.ActivityNotFoundException) {
            activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + packageName)))
        }
    }

    /**
     * Shares to the default SMS message app, via MMS if a file is specified
     */
    fun createShareSms(activity: AppCompatActivity, fileUri: Uri?, mimeType: String? = null): Intent {
        val intent: Intent
        if (Build.VERSION.SDK_INT >= 19) {
            val p = Telephony.Sms.getDefaultSmsPackage(activity)
            intent = createShareIntentForPackage(activity, fileUri, p)
        } else {
            intent = Intent(Intent.ACTION_VIEW, Uri.parse("smsto:"))
        }
        intent.apply {
            // putExtra("sms_body", "")
            // putExtra("address", "")
            if (fileUri != null) {
                putExtra(Intent.EXTRA_STREAM, fileUri)
                type = mimeType ?: MIME_TYPE_IMAGE
            }
        }
        return intent
    }

    fun createShareEmail(content: String?, fileUri: Uri?): Intent {
        val intent = Intent(Intent.ACTION_SEND)
        intent.apply {
            type = "message/rfc822"
            if (content != null) {
                putExtra(Intent.EXTRA_TEXT, content)
            }
            if (fileUri != null) {
                putExtra(Intent.EXTRA_STREAM, fileUri)
            }
        }
        return intent
    }

    /**
     * Uses the Facebook SDK. Can be used for LOCAL uris, for GIFs and mp4
     */
    fun shareVideoToFacebook(activity: AppCompatActivity, localUri: Uri) {
        FacebookSdk.sdkInitialize(activity.applicationContext)
        val video = ShareVideo.Builder()
                .setLocalUrl(localUri)
                .build()
        val content = ShareVideoContent.Builder()
                .setVideo(video)
                .build()
        showFacebookShareDialog(activity, content)
    }

    fun shareBitmapToFacebook(activity: AppCompatActivity, bitmap: Bitmap) {
        FacebookSdk.sdkInitialize(activity.applicationContext)
        val photo = SharePhoto.Builder()
                .setBitmap(bitmap)
                .build()
        val content = SharePhotoContent.Builder()
                .addPhoto(photo)
                .build()
        showFacebookShareDialog(activity, content)
    }

    private fun showFacebookShareDialog(activity: AppCompatActivity, content: ShareContent<*, *>) {
        val dialog = ShareDialog(activity)
        if (!dialog.canShow(content)) {
            if (hasApp(activity.packageManager, FACEBOOK)) {
                // User has to log in, launch the app
                val intent = activity.packageManager.getLaunchIntentForPackage(ShareUtil.FACEBOOK)
                if (intent != null) {
                    activity.startActivity(intent)
                }
            } else if (hasApp(activity.packageManager, FACEBOOK_LITE)) {
                // User has to log in, launch the app
                val intent = activity.packageManager.getLaunchIntentForPackage(ShareUtil.FACEBOOK_LITE)
                if (intent != null) {
                    activity.startActivity(intent)
                }
            } else {
                // User has to download app
                launchGooglePlay(activity, FACEBOOK)
            }
        } else {
            dialog.show(content)
        }
    }
  
    /**
     * Combines `launchIf` and `launchGooglePlay` to either launch the app or to launch the Play store.
     *
     * @param activity The parent activity
     * @param packageName The app to share to. E.g. ShareUtil.TWITTER. Use `shareMore` for sharing to any app.
     * @param fileUri Optional uri for a <strong>local</strong> file to attach/upload.
     */
    fun share(activity: AppCompatActivity, packageName: String, fileUri: Uri?) {
        if (!shareUtil.launchIf(this, fileUri, packageName)) {
            shareUtil.launchGooglePlay(this, packageName)
        }
    }
  
    /**
     * Shares to any app
     */
    fun shareMore(activity: AppCompatActivity, fileUri: Uri?) {
        val chooserIntent = createChooserIntent(activity, fileUri)
        if (fileUri != null) {
            activity.packageManager.queryIntentActivities(chooserIntent, PackageManager.MATCH_DEFAULT_ONLY)
                .map { it.activityInfo.packageName }
                .forEach { grantUriPermission(it, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION) }
        }
        startActivity(chooserIntent)
    }
  
}