Facebook deep linking on Android - android

I'm trying to implement Facebook's Deep Linking feature on my app and encountered the following scenario:
I have an activity called MainActivity which is declared like so:
<activity
android:name="com.mypackage.android.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This activity + my package name are also declared in my app's settings on facebook developer website.
Once a link gets clicked on Facebook's app, I'm supposed to handle this event via the onCreate method of my activity.
The following code handle the event:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri target = getIntent().getData();
if (target != null){
// got here via Facebook deep link
// once I'm done parsing the URI and deciding
// which part of my app I should point the client to
// I fire an intent for a new activity and
// call finish() the current activity (MainActivity)
}else{
// activity was created in a normal fashion
}
}
All goes according to plan except for the following scenario:
User launched my app
MainActivity created
SecondaryActivity created
MainActivity finished
App goes to background via the device home button
Deep link gets clicked on Facebook's app
In this case my app goes to foreground again, but MainActivity's onCreate / onNewIntent
don't get called, instead SecondaryActivity's onResume() gets called and restored to it's
last state.
Note: I've tested this issue on a Samsung Nexus with Android 4.2.1 and got to this result, though when tested on Galaxy S1 with Android 2.3.5 it worked as I initially expected.
Any help would be greatly appreciated,
Thank you.

Facebook is starting your app from their own app by explicitly start your "MainActivity" (the one your provided them in the developer page).
by that - Android's default behavior is: if the application already runs, then calling again to startActivity() won't start new task from scratch, but only restore to foreground the already running task.
but the good news are that you can change this default behavior by adding to your MainActivity the android:launchMode="singleTask". it definition is:
the system creates a new task and instantiates the activity at the root of the new task. However, if an instance of the activity already exists in a separate task, the system routes the intent to the existing instance through a call to its onNewIntent() method, rather than creating a new instance. Only one instance of the activity can exist at a time.
from this point you could always respond to the starting intent, and from that point you can always navigate back to the task that already was in background(if exists) by restarting activity with both flags Intent.FLAG_ACTIVITY_SINGLE_TOP && Intent.FLAG_ACTIVITY_CLEAR_TOP combination

See http://developer.android.com/guide/topics/manifest/activity-element.html
You can play with:
android:clearTaskOnLaunch
android:noHistory
android:launchMode

You need to have more information in your intent filter:
<intent-filter>
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<category android:name="android.intent.category.BROWSABLE"></category>
<data android:host="www.yoursite.com" android:scheme="http"></data>
</intent-filter>
This will capture links going to your site (make sure to change the URL), and direct them to whatever Activity you define this intent filter under.

Related

Android: Sharing Data and Receiving text from 3rd party app. Why doesn't my app receive new text?

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
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 whatever it is -- 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
With code like the following in my MainActivity
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();
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:
<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 insure that I don't only get the Intent when onCreate() is called (only one time when the app is started).
Start up browser and grab the text "hurricane".
Choose the app to share with (our test app).
View the log and notice that onCreate() and onResume() are called and value is 'hurricane'
Go back to browser again to share more text...
Select a new word, Atlantic, to share.
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.
Attempted Workaround Solutions
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, but isn't there some other way to reset that Intent or the associated text or something?
Do you know of any other way to insure that the new data is retrieved?
I appreciate any help.
UPDATE
Note:I'm updating because there's not a great way to show additional code tried, however, I wouldn't have know to try this without input from other SO User, CommonsWare.
The first answer I received was that I should add an #Override onNewIntent() so I added the following code to my MainActivity:
#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:
It doesn't even look as if the onNewItent() method is even called.
EDIT 2
I altered the emulator Settings...Developer Options... and turned off the "Don't keep activities" setting. It was previously turned on (checked).
After that, I ran the app which contains the onNewIntent() override but now it shows just the one onCreate() gone (which makes sense because the activity is still loaded) but still does not show the onNewIntent() call.
In this sample, I captured the word "remnants".
Edit 3
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 just looked up onNewIntent in Google docs 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 haven't set singleTop so I will try it now. Hmm....
EDIT 4
I have now tried the singleTop variation. I was previously testing on API 15 (v4.0.4)on an emulator so I switched to API 21 (v5.0) to see if there'd be any different.
Here's what the addition of singleTop did to my AndroidManifest.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>
Notice I also collapsed the two intent-filter nodes into the one.
Selected Text Did Change
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. I don't ever see it 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?!
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 be the original Activity being appended to.
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.
Now that I saw it work -- provide different text each time on API 21 I decided to switch back to API Level 15 emulator and try it.
I will report back after I try some things back on API Level 15 again.
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:
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.
I've written a book on this terribly documented thing. I hope this helps someone and that a Google Android dev sees this and explains it.
If your activity already exists, it will be called with onNewIntent() instead of onCreate(). onNewIntent() will be passed the Intent that you need to use for your message.
Try changing the android:launchMode of your activity in the manifest.xml to
singleTop
this way if the activity is already launched, new intents will be received in onNewIntent() method
There is only one answer that actually works, but it could cause other problems.
You just have to decide to call finish() whenever the Activity goes into onPause().
Here's the exact code I implemented which works on all API LEVELS.
#Override
public void onPause(){
super.onPause();
finish();
}
Destroy the Activity
When you add that code then every time you switch back to the app you are sharing from (the web browser in our case) then the onPause will fire on your MainActivity and the finish() method will set the Activity for destruction.
Share Menu Displayed Every Time After This
With this solution every time you select text from your sharing app (web browser) then the Share menu will be displayed and will display GrabText as one of the choices (instead of automatically forcing GrabText to the front again).
Shared Text Is Always the New Text
Since the MainActivity is completely destroyed it then has to be completely loaded (onCreate()) again and so it receives the new Intent text which was sent.
Not A Great Workaround
This isn't a great workaround however, because I believe dialog boxes in your app would also create onPause() to be called and your Activity would be destroyed. Obviously destroying your Activity onPause() just isn't great either because you are beginning to manage "memory" in a way that really should be left to the OS. However, in this case it seems to be the only way around the issue.

Lollipop: making my activity stay in the task that launched the share intent

“My First App” has an activity that handles a “share” intent. Its activity in AndroidManifest.xml looks like this:
<activity
android:name="com.example.foo.myfirstapp.MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/jpeg"/>
</intent-filter>
</activity>
In KitKat, sharing an image from the album to “My First App” causes MainActivity to be part of the album’s task. This is the desired behavior.
In Lollipop, sharing an image from the album to “My First App” causes a new instance of “My First App” to be launched. If I look at the overview screen, the album task is there...and then there's a separate entry for "My First App". If I share another image, I wind up with two instances of "My First App"...etc.
Question: How do I make Lollipop process the share intent in the same way as KitKat?
Here's what I've done:
I notice that the intents sent from the Album have different flags set depending on the OS. (I got these using getIntent() and looking at mFlags.)
Kitkat: 0x80001 (524289): FLAG_GRANT_READ_URI_PERMISSION, FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
Lollipop: 0x18080001 (403177473): FLAG_GRANT_READ_URI_PERMISSION, FLAG_ACTIVITY_MULTIPLE_TASK, FLAG_ACTIVITY_NEW_DOCUMENT, FLAG_ACTIVITY_NEW_TASK
From reading http://developer.android.com/reference/android/content/Intent.html, it seems that these last three flags are causing the problem. Specifically
When paired with FLAG_ACTIVITY_MULTIPLE_TASK both of these behaviors (FLAG_ACTIVITY_NEW_DOCUMENT or FLAG_ACTIVITY_NEW_TASK) are modified to skip the search for a matching task and unconditionally start a new task.
I’ve been attempting to “override” these flags by specifying android:launchMode and android:documentLaunchMode in the activity in AndroidManifest.xml without success.
From http://developer.android.com/reference/android/R.attr.html#documentLaunchMode, using documentLaunchMode “never” seems promising, since
This activity will not be launched into a new document even if the Intent contains Intent.FLAG_ACTIVITY_NEW_DOCUMENT. This gives the activity writer ultimate control over how their activity is used.
but this didn't work.
I also considered android:taskAffinity, but there doesn’t seem to be a way to say “please prefer whatever task launched you”.
Afraid you can't do anything about this. It isn't under your control. This is a change in the way the "Album" app is launching its "share" Intent. If it doesn't want your Activity in its task, you can't force it in there.
If you have issues with having multiple instances of your "share" activity, you could declare your "share" activity as launchMode="singleTask" or launchMode="singleInstance" (depending on your needs. This may, however, break other things.

Implementing login activity in Android

I'm developing an Android app which requires the user to be logged in (actually, at the moment they just need to supply a username as no authentication is required... this will change in the future though).
I've managed to implement this, but I'm not sure if it's the best way. I have a LoginActivity which I have declared in the manifest using android:noHistory="true" and:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
In LoginActivity's onCreate method, I check for a Boolean value isFirstStart stored in SharedPreferences. If the value does not exist, it must be the first start of the app, so I let LoginActivity set everything up, perform the login, and then launch MainActivity using:
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.putExtra(MainActivity.EXTRA_ISFIRSTSTART, isFirstStart);
startActivity(intent);
I use the extra so that I can open the navigation drawer in my main activity if it is the first start. At this stage I also store the Boolean value as false in SharedPreferences.
If isFirstStart already exists as false, I perform the launch of MainActivity straight away using the same method.
To me, this somehow doesn't seem like the most efficient method - launching LoginActivity on every startup even though it will not be used most of the time.
Any thoughts?

Share from browser on the second try does not invoke my activity

I have a dictionary-type application that accepts, via an intent filter, arbitrary text shared from other apps, e. g. the browser. The intent filter is attached to a decicated activity ("Lookup") that does some posttranslation of the shared text and invokes my main activity. The lookup activity quietly closes.
I'm testing it with the system browser on Samsung Galaxy with Android 4.0.4, and here's a funny thing I notice. On the first try, it works as expected. I tap "Share", select my app as the target, the lookup activity is invoked, then the main activity is invoked. Now, if I task-switch back to the browser and hit "Share" again on a different piece of text, the menu of sharing targets is not displayed. Instead, my main activity is resumed. Not the lookup one (that'd be reasonable), but the main one. onNewIntent() is not delivered to neither main activity nor Lookup activity.
onResume() is delivered to main, though.
However, if I tap "Back" instead of task-switching back to the browser - that is, explicitly close my main activity - on the next try, the Share operation displays the menu of targets again.
What's up with this behavior? Does the Share functionality remember the target of the last share operation and try to reuse it? If so, why won't it remember the immediate target of the share - my lookup activity? Is this documented?
EDIT: the manifest for the lookup activity goes like this:
<activity android:name=".Lookup"
android:label="#string/IDS_LOOKUPTITLE"
android:theme="#style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
When I start main activity from lookup activity, I'm trying to reuse an existing one (if any), so the flags are FLAG_ACTIVITY_REORDER_TO_FRONT|FLAG_ACTIVITY_SINGLE_TOP.

Android Home activity launching a sub-activity

I am relatively new to Android programming. I have been given a task at work where I need to create a Custom Home activity launcher. I did a bit of research and found the example on the Android developer website (home sample). This was the start for my prototype.
The custom Home activity will need to require the user to authenticate (enter some simple credentials). My thought was to launch a sub-activity from the Home activity and pass back the results in the intent to the Home activity. I need to be able to capture information about these credentials and that information was going to be passed back to the Home activity. However, I have problems when trying this. I get a log in the LogCat that says the following: "Activity is launching as a new task, so canceling activity result."
I am aware of the startActivityForResult method, but that does not seem to be working for me. Here is where I launch the activity from my Home activity:
#Override
protected void onResume() {
super.onResume();
bindRecents();
Intent iii = new Intent(this, Login.class);
startActivityForResult(iii, STATIC_LOGIN_INTEGER_VALUE);
}
When that code executes, I get the above mentioned log from the ActivityManager tag.
My Login activity has a button that the user will hit once they have entered their credentials. If the credentials are good, then I try to do the following (I put in several logs so that I could try to figure out what is going on):
public void onClick(View src) {
// check for authentic credentials
if(IsValid())
{
Intent loginAuth = new Intent("Login");
loginAuth.putExtra("userRole", userRole);
Log.d("LOGIN", "Setting result...");
if (getParent() == null) {
Log.d("LOGIN", "Parent was null");
setResult(Activity.RESULT_OK, loginAuth);
}
else {
Log.d("LOGIN", "setting result on parent...");
getParent().setResult(Activity.RESULT_OK, loginAuth);
}
Log.d("LOGIN", "Finishing the activity");
finish();
}
}
I defined these activities in my manifest file as the following:
<activity android:name="Home"
android:theme="#style/Theme"
android:launchMode="singleInstance"
android:stateNotNeeded="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="Login"
android:label="Login"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</activity>
I was playing around with the intent filter on the Login activity. I had originally had it set to nothing. I also had the launchMode blank (which defaults to standard I believe). I have played around with most of these options and nothing seems to change the fact that the ActivityManager seems to want to launch the activity as a new task and wants to ignore the returned intent (which I need).
The problem is that you declared your activities with launchMode="singleInstance", so Android creates a new task (i.e. a new process) when it launches the Login activity. Activities in different tasks cannot return results to each other. From the Activity.startActivityForResult() docs:
For example, if the activity you are
launching uses the singleTask launch
mode, it will not run in your task and
thus you will immediately receive a
cancel result.
singleInstance is like singleTask but even more restrictive. Try removing the launchMode attribute from your manifest so that the Login activity will launch in the same task, then using FLAG_ACTIVITY_NEW_TASK when you need to launch a different activity in a separate task.
Barry
The documentation says
For example, if the activity you are launching uses the singleTask
launch mode, it will not run in your task and thus you will
immediately receive a cancel result.
THIS IS WRONG.
You can use the singleTask just fine, what you can't use is singleInstance.
Let's return to the problem. What is the problem?
The problem is that when you call startActivityForResult() on an activity, the caller activity and the activity you are launching must be on the same task for the startActivityForResult() to work properly that means that you can't call startActivityForResult() to an activity using the Intent.FLAG_ACTIVITY_NEW_TASK.
why singleInstance isn't working?
Because a "singleInstance" activity stands alone as the only activity in its task. If it starts another activity, that activity will be launched into a different task regardless of its launch mode — as if FLAG_ACTIVITY_NEW_TASK was in the intent. In all other respects, the "singleInstance" mode is identical to "singleTask".
Why singleTask is working?
Because singleTask doesn't require the creation of a new task for the new activities being launched.
I quoted from this answer to explain the difference between the two launch modes.
Have you declared the setResult(int resultCode, Intent data) in your Login activity.
Android Documentation says
"Note that this method should only be used with Intent protocols that are defined to return a result." If it is not or some other conditions matches as in the documentation of Activity.startActivityForResult(), it will not run in your task and thus you will immediately receive a cancel result."

Categories

Resources