Click here to Skip to main content
15,884,739 members
Articles / Mobile Apps / Android

Receiving Simple Data From Other Apps (via the Share menu) - Fixing Problem in API

Rate me:
Please Sign up or sign in to vote.
4.92/5 (4 votes)
24 Mar 2016CPOL8 min read 16.5K   280   2  
If you use the Google documented method of sharing text on API Levels before 21 (Lollipop), the text isn't shared properly with your app. This article describes the problem and how to fix it.

Introduction

While developing an application to receive text and save it in a database for the user, I stumbled upon a hole in the Android Intent APIs which are supposed to allow other apps to share text with your app.

Summary

I am attempting to get data sent from the user via the Share menu. In this case, I'll use the basic Android web browser to select text and then share it to my app.

Problem Summary

The first time the user shares the text, my app gets the text as expected and displays it via Log.d() -- see the handleSendText() method in the code below.

However, each time thereafter, even though the user has selected new text in the web browser and shared it with my app, I still get the original text the user selected (previous value).

Question

How do you reset the Intent (or the value sent on the Intent) so that I can obtain the new text the user has selected after the first time?

Details

My application has a MainActivity and I've followed the Google docs at
http://developer.android.com/training/sharing/receive.html (loads in new tab).

With code like the following in my MainActivity:

Java
    public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = getIntent();
        String action = intent.getAction();
        String type = intent.getType();
  
        // when the other app Shares text it is placed as a text/plan mime type 
        // on the intent so we can then retrieve that text off the incoming intent

        if (Intent.ACTION_SEND.equals(action) && type != null) {
            if ("text/plain".equals(type)) {
                handleSendText(intent, "onCreate"); // Handle text being sent
            }
        }
    }

    @Override
    public void onResume(){
        super.onResume();
        Intent intent = getIntent();
        String action = intent.getAction();
        String type = intent.getType();

        if (Intent.ACTION_SEND.equals(action) && type != null) {
            if ("text/plain".equals(type)) {
                handleSendText(intent, "onResume"); // Handle text being sent
            }
        }
    }

    void handleSendText(Intent intent, String callingMethodName) {
        String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
        if (sharedText != null) {
        Log.d("MainActivity", "sharedText : " + 
        sharedText + " called from : " + callingMethodName);
        }
    }
}

My AndroidManifest section for the activity has the filter added like:

XML
<activity android:name=".MainActivity">
             <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/plain" />
             </intent-filter>
         </activity>

Walk-Thru With Screens and Log

**NOTE:** Please notice that I've implemented the onResume() in my app also to ensure that I don't only get the Intent when onCreate() is called since the MainActivity's onCreate() is only called when the app istarts.

Start up the browser and grab the text "hurricane"

grab text hurricane

Choose the app to share with (our test app).

Share with GrabText app

View the log and notice that onCreate() and onResume() are called and value is 'hurricane'.

view log see values

Go back to browser again to share more text...

back to browser

Select a new word, Atlantic, to share.

select new word: atlantic

Extra note: When we click that Share link this time, the Android MenuChooser doesn't display, instead, it automatically opens GrabText again. I found that behavior somewhat odd.

Notice that the Intent text still has the value of hurricane. You can see that there are now two new entries in the logcat.

intent text is still hurricane

Attempted Workaround Solutions

Destroy MainActivity & App

I have found that I can destroy the app entirely by overriding onPause() and calling finish() on my Activity (thus closing the entire app) and that seems to work.

Since the MainActivity and the App is destroyed, then the next time you attempt to Share text from the browser, the system displays the Share menu again and allows me to choose GrabText and the new text is retrieved properly from the Intent every time.

Unfortunate Side Effects

However, there are unfortunate side effects to that solution. If you implement that solution and you need to display a Dialog box to your user, then onPause() will get called and your app will be destroyed. That's no good.

Also, every time you switch away from your app, then onPause() will be called and your app will be destroyed.

Additionally, the system itself may decide that memory is low and pause your app and then of course your app will be destroyed. None of these are great so I wanted to find a workaround.

Workaround Attempt: Override onNewIntent()

While searching for answers to this question about why the text is never right after the first time someone said I should add an @Override onNewIntent() in my MainActvity and then I would get the new text. I tried adding it as in the following code:

Java
     @Override
        protected void onNewIntent(Intent intent) {
            super.onNewIntent(intent);
            Log.d("MainActivity", "onNewIntent()...");
            String action = intent.getAction();
            String type = intent.getType();
            if (Intent.ACTION_SEND.equals(action) && type != null) {
                if ("text/plain".equals(type)) {
                    handleSendText(intent, "onNewIntent"); // Handle text being sent
                }
            }    
        }

Upon adding that code and running and attempting the copy and then second copy of the new word, I still saw the following in logcat indicating that I still hadn't captured the new text:

still doesn't capture new word

Furthermore, since I had the log statement in onNewIntent() I could see that onNewIntent() was never called.

Dev Setting: Don't Keep Activities

I noticed that each time the Intent was coming in that onCreate() and onResume() were being called. That means MainActivity should've been unloading entirely, since the onCreate() was being called every time I shared text. This should've helped me get the text, but I figured I should try changing it up and see what happens.

I altered the emulator Settings...Developer Options... and turned off the "Don't keep activities" setting. It was previously turned on (checked).
don't keep activities

After that, I ran it again with the overridden onNewIntent() but now it shows just the one onCreate() in the log (which makes sense because the activity is still loaded and onCreate() isn't called the second time) but still does not show the onNewIntent() call.
In this sample, I captured the word "remnants".

another attempted capture

Workaround Attempt: Run Program On Real Hardware

I built the app and created an APK and deployed it to my Samsung Galaxy Core Prime and I ended up with the same results. onNewIntent() is never called.

I looked more closely at the Google Dev Docs on onNewIntent and it states:

onNewIntent(Intent intent) This is called for activities that set launchMode to "singleTop" in their package, or if a client used the FLAG_ACTIVITY_SINGLE_TOP flag when calling startActivity(Intent).

I altered my AndroidManifest.xml so that it looked like the following:

XML
<activity android:name=".MainActivity" android:launchMode="singleTop">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
            <action android:name="android.intent.action.SEND" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="text/plain" />
        </intent-filter>
    </activity>

Switched API LEVELS (Android Version)

When I made that change, I actually made another change too and it somewhat invalidated my tests because I switched API Levels on my emulator. I was running API LEVEL 15 (Android v4.0.4). However, at this point, I switched to API LEVEL 21 (v5.0 Lollipop) to see if there were any differences.

Selected Text Did Change: Success?

On Android API Level 21, the Intent text was now coming in different each time I selected text in the browser.

onNewIntent Is Never Called

However, onNewIntent() is NEVER called. Not even with the change to the Manifest or the API Level change. I don't ever see onNewItent() fire.

Share Menu Displayed Every Time

Also, now (on API 21), I see the Share menu every time I select text.
However, I also see an interesting thing when I switch to the browser. You can see multiple copies of the Activity in the list. What?!

multiple copies of grabtext

ListView For Run-time View

Notice also that I implemented the MainActivity as a ListView (scrollable) so I could see the entries even without logcat (for running on real device). That made something else apparent: that the ListView was being updated on each newly shown Activity. But really, it should just be appending to the original Activity's ListView. Why does it create a new app / MainActivity every time I share text?

Creates Numerous GrabText Activities

Yes, now it creates a new GrabText Activity window each time I select text. I thought maybe that was because I had the singleTop set so I removed it but they still appear even after removing singleTop on API LEVEL 21.

Works On API Level 21, Try On API Level 15

Now that I saw it work -- provide a different text each time on API 21 -- I decided to switch back to API Level 15 emulator and try it.

API Level 15: Test Again

I started my other emulator running API Level 15 again and ran the app and even with singleTop set the value is never updated.

You can see this in the logcat and on the updated ListView:

logcat onResume

append to ListView via api15

You can also see that the code acts completely different, though I've not changed anything since it appends to the ListView of the one running Activity on API level 15.

Workaround and Solution

After thinking about this for a while, I came up with an idea.

Pass-Thru Activity

I decided to create a pass-thru activity which would grab the text off of the Intent and then use it to start my MainActivity. I would then add override onPause() in this pass-thru activity and call finish() so that the pass-thru activity would be destroyed and never seen by the user.

I've named this activity Transit in my code and here is the entire listing for that code. It's very simple.

Java
public class transit extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_transit);

        Intent intent = getIntent();
        if (intent != null) {
            String action = intent.getAction();
            String type = intent.getType();

            if (Intent.ACTION_SEND.equals(action) && type != null) {
                if ("text/plain".equals(type)) {
                    handleReceivedText(intent); // Handle text being sent
                }
            }
        }
    }

    void handleReceivedText(Intent intent) {
        String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
        if (sharedText != null) {
            Log.d("MainActivity", "sharedText : " + sharedText);
            Intent i = new Intent(getApplicationContext(), MainActivity.class);

            i.putExtra("SHAREDTEXT", sharedText);
            startActivity(i);
        }
    }

    @Override
    public void onPause(){
        super.onPause();
        finish();
    }
}

Basic Flow of How It Works

  1. onCreate() grabs the text the user has shared and passes it to handleReceivedText()
  2. handleReceivedText() retrieves the text from the incoming intent (see getStringExtra()) and creates a new Intent which is my MainActivity(). It places the shared text on the MainActivity Intent and starts MainActivity.
  3. Since the MainActivity will become the front most Activity, onPause() will be called in the Transit Activity and that's when I call finish() which destroys the Transit Activity.

Manifest Change

There is a manifest change you have to make also to insure that the Transit Activity only ever has one instance in memory. This ensures that each time the user shares text from the calling app (web browser in our case) that it will display the GrabText app in the share menu each time.

XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="us.raddev.grabtext">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity  android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="us.raddev.grabtext.transit" 
        android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Here It Is In Action

Share Text from web browser (share menu appears)

android web browser

Choose the GrabText app to receive the text.

Share menu appears

Notice the text the first time through.

choose GrabText app.

Choose different text to share and select GrabText app again.

Notice the text is different the second time.

first time text

Now the text is different, but there is somewhat of an issue. For some reason, even though the ListView's ArrayAdapter is still in memory, we only see the one item in the ListView.

Solving that is for another day.

Using the Code

You can download the source from this article, unzip it and drop the main folder on your hard drive and open it with the latest version of Android Studio (December 2015 v1.5.1 build xxx), build and run to see it all work.

History

  • 03/24/2016: First release of code and article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) RADDev Publishing
United States United States
"Everything should be made as simple as possible, but not simpler."

Comments and Discussions

 
-- There are no messages in this forum --