Blog Infos
Author
Published
Topics
,
Published
Topics
,

 

In order to better understand sharing functionality, let’s define its use case. For example, if you’re developing a social media app, you might want to provide the ability to share text or images. In fact, there are many more applications for this functionality. Almost every messenger, social network, and even simple note-taking applications use this feature.

For a non-compose application there is a pretty straight forward guide in the official Android documentation, but I found out that this is not exactly the same for compose applications. So let’s go and learn how to do this step by step.

Tech stack

First, let’s discuss what technologies and libraries we will use. To keep this article simple, I’m assuming that most of you are familiar with Compose and all other modern tools. If not, I highly recommend checking them out.

  1. Jetpack Compose is our UI toolkit.
  2. Compose Navigation to navigate between composables while taking advantage of the Navigation component’s infrastructure and features.
  3. Hilt is our DI framework. Here is also important to know how to integrate Hilt with Jetpack Compose

In this list Hilt is an optional component, but it will make your life so much easier, so I highly recommend using it.

Photo by Myriam Jessier on Unsplash

Now it is time to write some code

First let’s update our manifest. In order to mark our application as a share target we need to specify <intent-filter> section for our Activity.

<activity
android:name=".features.main.MainActivity"
android:theme="@style/Theme.AppSplash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
view raw Manifest.xml hosted with ❤ by GitHub

By specifying the intent filter section for our activity, we let the system know that we are ready to process the intent with the specified parameters.

So in our case those parameters will be:

<action android:name="android.intent.action.SEND" />

This is the default action that is used to share some data.

<data android:mimeType="text/*" />

Here we let the system know that we can process any text data.

<data android:mimeType="image/*" />

And this is serving for receiving an image.

For more in depth description please read the official guide.

If you are going to launch your application right now, and then share something — your app should appear in the android share sheet.

Navigation

Now it is time to update our navigation graph, as we probably want to show a specific screen that can process shared data.

Your navigation graph should look like this:

@Composable
fun Navigation(appState: PoiAppState, paddingValues: PaddingValues) {
NavHost(appState.navController, startDestination = Screen.Home.route, Modifier.padding(paddingValues)) {
...
composable(
route = "share_target_route"
deepLinks = listOf(
navDeepLink {
action = Intent.ACTION_SEND
mimeType = "text/*"
},
navDeepLink {
action = Intent.ACTION_SEND
mimeType = "image/*"
}
)
) {
ShareTargetScreen()
}
...
}
}

Pretty simple isn’t it? Just like for regular deep links, we specify the list of navDeepLink{} . But instead of uriPattern we are specifying action and mimeType that correspond to our intent filter in the manifest. The next question is how can i access shared data? Let’s look.

ViewModel
@HiltViewModel
class SharingTargetViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
...
) : ViewModel() {
private val sharedContentState = savedStateHandle.getStateFlow(NavController.KEY_DEEP_LINK_INTENT, Intent())
.map { intent -> intent.parseSharedContent() }
.map {
// map to ui state
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = SharedContent.EMPTY
)
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Managing state beyond ViewModels and Hilt

Separation of concerns is a common best practice followed by all successful software projects. In Android applications, there is usually a UI layer, a data layer, and a domain layer. .
Watch Video

Managing state beyond ViewModels and Hilt

Ralf Wondratschek
Principal Engineer
Amazon

Managing state beyond ViewModels and Hilt

Ralf Wondratschek
Principal Engineer
Amazon

Managing state beyond ViewModels and Hilt

Ralf Wondratsche ...
Principal Engineer
Amazon

Jobs

And of course let’s inject our ViewModel into Composable:

@Composable
fun ShareTargetScreen(
...
viewModel: SharingTargetViewModel = hiltViewModel()
) {
...
}

Our Intent is already available to our ViewModel via the SavedStateHandle without any additional work. To access it, we need to use key NavController.KEY_DEEP_LINK_INTENT. Hilt will do all the magic for us.

Fetching shared data from intent

The last step is to get the data from the intent. So let’s implement our Intent.parseSharedContent() function.

fun Intent.parseSharedContent(): SharedContent {
if (action != Intent.ACTION_SEND) return SharedContent(null, ContentType.EMPTY)
return if (isTextMimeType()) {
val textContent = getStringExtra(Intent.EXTRA_TEXT) ?: ""
if (Patterns.WEB_URL.matcher(textContent).matches()) {
SharedContent(textContent, ContentType.URL)
} else {
SharedContent(textContent, ContentType.PLAIN_TEXT)
}
} else if (isImageMimeType()) {
val imageContent = getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
if (imageContent != null) {
SharedContent(imageContent.toString(), ContentType.LOCAL_IMAGE)
} else {
SharedContent.EMPTY
}
} else {
SharedContent.EMPTY
}
}
private fun Intent.isTextMimeType() = type?.startsWith(MIME_TYPE_TEXT) == true
private fun Intent.isImageMimeType() = type?.startsWith(MIME_TYPE_IMAGE) == true
private const val MIME_TYPE_TEXT = "text/"
private const val MIME_TYPE_IMAGE = "image/"

As you can see from this piece of code, you need to use the following method to access text data:

val textContent:String? = getStringExtra(Intent.EXTRA_TEXT)

And to access image data you need to use:

val imageContent: Uri? = getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri

And that is it!

This feature, along with many others, was implemented in my open source project. So make sure you check out the repository and thanks for reading.

This article was originally published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Compose is a relatively young technology for writing declarative UI. Many developers don’t even…
READ MORE
blog
When it comes to the contentDescription-attribute, I’ve noticed a couple of things Android devs…
READ MORE
blog
In this article we’ll go through how to own a legacy code that is…
READ MORE
blog
Compose is part of the Jetpack Library released by Android last spring. Create Android…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu