I have a very simple activity, that redirects the user to the app's Play Store page, when the button is clicked:
public class MyActivity extends AppCompatActivity {
private static final String PLAY_STORE_URI =
"market://details?id=" + BuildConfig.APPLICATION_ID;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
findViewById(R.id.go_to_play_store).setOnClickListener(this::goToPlayStore);
}
public void goToPlayStore(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(PLAY_STORE_URI));
startActivity(intent);
}
}
Is it possible to write a test to check that the PlayStore is launched when the button is clicked? Better, is it possible to verify it shows the expected page?
I know that by using ActivityMonitors transitions between Activities can be tested. I also know that I can verify the Intents being sent using Espresso Intents. But can I actually check that a user action launches another app?
I would click the button, then use:
intended(allOf(
hasAction(Intent.ACTION_VIEW),
hasData("https://play.google.com/store/apps/...your app...")
))
I would suggest a slightly different question - is it you app's job to verify that? You'd be testing not your own app but Android OS and Google's Play Store App.
The way I've been approaching it is by being explicit about the boundaries, and aware of the possible scenarios.
What I mean by that is, extract the Intent manipulation and invocation logic into a thin service (that's the being explicit about boundaries part) and test your Activity correctly interacts with it (by constructing the Intent appropriately).
The part about possible scenarios is being aware of what can happen. For example what does your app do (if anything) if the user doesn't have Play Store on their phone.
Related
I'm building an app which has an Intro page with many slides. Once a first time user has gone through the intro, he'll be directed to a login screen. Once he logged in (or registered), he'll be taken into the app home page. As long as the user doesn't sign out, if he clicks on the app icon he'll be directly taken to the home screen.
I'm using the Intro page intent as the LAUNCHER activity and using sharedpreference to save 'first usage' and logged in states. By testing if the user has logged in or a first time user, I'm directing him to different intents.
So my question is, where is the most suitable position to have this intent redirection? Because Intro page has so many fragments and components, setting it as the LAUNCHER activity and having all the if else statements there to decide where the user should go, have I wasted system resources? Because if the user has already logged in, he'll taken into the home page without showing any app intro stuff which are loaded.
Or is it a good practice to create an empty activity and set that as the LAUNCHER activity and put all the if else statements in that. So the app doesn't need to go to the 'heavy' app intro page.
PS: I've declared those intent direction if else statements in the onCreate right after super.onCreate();
#Override
protected void onCreate(Bundle savedInstanceState) {
// Fullscreen
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
// activity_first_usage is the container for all frames
setContentView(R.layout.activity_first_usage);
logger = new Logger(this);
if (!logger.isFirstUsage()) {
if (logger.hasTOKEN()) {
// If user didn't log out, then he can stay in the app
Intent home = new Intent(getApplicationContext(), Home.class);
startActivity(home);
finish();
} else {
// If this is not the first time user login in, no need to show the intro
Intent directToSignIn = new Intent(getApplicationContext(), SignIn.class);
startActivity(directToSignIn);
finish();
}
} else {
// If not, continue with the Intro and set usage status to used
logger.setFirstUsage(false);
}
...
}
ill tell you the concept
use a splash and there use a condition to check user's state eg: already registered , new one , registered but still did not go through intro like wise
identify it
now you use shared preff
can write a file
can keep a enum value
or get from a server etc..
more : you can think about what happens when user uninstall your app and re install it.Then what you need to do ? up to you.
once you identify the state of the user in the splash
the write different intents to each of them
if a new one - display your intro
if not - load to your main menu
you need to decide cuz you knows the requirement
Hope this helps a bit :)
Thanks in advance for the help.
I have an app that can be started by either the user physically starting the app (like you would any normal app) or by a repeating service. Depending on what starts the app (the user or the service) I want to preform different initialization actions. How might I be able to detect if an user starts the app without doing anything custom (I imagine that there has to be some kind of built in setting in android for me to determine this)?
If service, that starts your Activity, is yours service, you can put some custom information (using Intent#putExtra for example) in Intent you use to start Activity from Service.
In Activity you can use Activity#getIntent(), that returns the intent that started this activity.
If you started Activity from Service, that Intent will be the one you passed in Service#startActivity, and will have your custom information. Otherwise, that was not your Service, that started your Activity.
That could look somehow like that, for example:
//in Activity
public static final String EXTRA_STARTED_FROM_MY_SERVICE = "com.example.extra_started_from_sevice";
private boolean wasActivityStartedFromService() {
Intent startingIntent = getIntent();
//assuming you will use Intent#putExtra in your service when starting activity
return startingIntent.getBooleanExtra(EXTRA_STARTED_FROM_MY_SERVICE, false);
}
//...
//in Service
//...
Intent startingIntent = new Intent(this, MainActivity.class);
startingIntent.putExtra(MainActivity.EXTRA_STARTED_FROM_MY_SERVICE, true);
startActivity(startingIntent);
The situation:
You have an extensive mobile website, m.somewhere.com
On Google Play you have an Android App that duplicates the key features of m.somewhere.com, but not all of them.
Your Client/Employer/Investor has asked you to implement deep-linking for those urls that can be handled by the app.
TL;DR - how do you implement this?
My Approach So Far:
First instinct: match only certain urls and launch for them. Problem: paucity of expression in the AndroidManifest intent-filter prevents this (e.g. http://weiyang.wordpress.ncsu.edu/2013/04/11/a-limitation-in-intent-filter-of-android-application/).
As a subset of the problem, suppose the server at m.somewhere.com knows that any url that ends in a number goes to a certain page on the site, and the marketing guys are constantly futzing with the seo, so e.g.
I want to launch the app for:
http://m.somewhere.com/find/abc-12345
https://m.somewhere.com/shop/xyz-45678928492
But not for
http://m.somewhere.com/find/abc-12345-xyz
https://m.somewhere.com/about-us
no combination of path, pathPrefix, or pathPattern will handle this.
Best practice on stackoverflow (Match URIs with <data> like http://example.com/something in AndroidManifest) seems to be to catch everything, and then handle the situation when you get to onCreate() and realize you shouldn't have handled this particular url:
Android Manifest:
...
<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="http"
android:host="m.somewhere.com"
android:pathPattern=".*"/>
</intent-filter>
...
Activity onCreate():
Intent i = getIntent()
String action = i.getAction();
Uri uri = i.getData();
if (Intent.ACTION_VIEW.equals(action) && cantHandleUrl(uri)) {
// TODO - fallback to browser.
}
I have programmed something similar to the above that is working, but it leads to a very bad end-user experience:
While browsing m.somewhere.com, there is a hiccup on every url click
while the app is launched and then falls back.
There is a nasty habit for a Chooser screen to popup for each and every link click on m.somewhere.com, asking the user which they would like to use (and the Android App is listed along with the browsers, but clicking on the Android App just launches the chooser screen again). If I'm not careful I get in an infinite relaunch loop for my app (if the user selects "Always"), and even if I am careful, it appears to the user that their "Always" selection is being ignored.
What can be done?
(EDIT: Displaying the site in a WebView in the app for unhandled pages is NOT an option).
Late answer, but for future readers: if you're supporting a minimum of API level 15 then there's a more direct (less hacky) way of falling back to a browser for URLs you realize you don't want to handle, without resorting to disabling/re-enabling URL catching components.
nbarraille's answer is creative and possibly your only option if you need to support APIs lower than 15, but if you don't then you can make use of Intent.makeMainSelectorActivity() to directly launch the user's default browser, allowing you to bypass Android's ResolverActivity app selection dialog.
Don't do this
So instead of re-broadcasting the URL Intent the typical way like this:
// The URL your Activity intercepted
String data = "example.com/someurl"
Intent webIntent = new Intent(Intent.ACTION_VIEW, data);
webIntent.addCategory(Intent.CATEGORY_BROWSABLE);
startActivity(webIntent);
Do this
You would broadcast this Intent instead:
Intent defaultBrowser = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER);
defaultBrowser.setData(data);
startActivity(defaultBrowser);
This will tell Android to load the browser app and data URL. This should bypass the chooser dialog even if they have more than one browser app installed. And without the chooser dialog you don't have to worry about the app falling into an infinite loop of intercepting/re-broadcasting the same Intent.
Caveat
You have to be okay with opening the URL (the one you didn't want to handle) in the user's browser. If you wanted to give other non-browser apps a chance to open the link as well, this solution wouldn't work since there is no chooser dialog.
Pitfalls
As far as I can tell, the only quirk from using this solution is that when the user clicks one of your deep links, they'll get to choose to open in your app or their browser, etc. When they choose your app and your internal app logic realizes it's a URL it doesn't want to intercept, the user gets shown the browser right away. So they choose your app but get shown the browser instead.
NOTE: when I say "broadcast" in this answer, I mean the general term, not the actual Android system feature.
There is a somewhat hacky way of doing this:
In the manifest, create an intent-filter for m.somewhere.com, to open a specific deeplink handler activity.
In that Activity, figure out if your app supports that URL or not.
If it does, just open whatever activity
If it doesn't, send a non-resolved ACTION_VIEW intent to be opened by your browser. The problem here, is that your app will also catch this intent, and this will create an infinite loop if your app is selected as the default handler for that URL. The solution is to use PackageManager.setComponentEnabledSetting() to disable your deeplink handler Activity before you send that intent, and re-enable it after.
Some example code:
public class DeepLinkHandlerActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
Uri uri = intent.getData();
Intent intent = makeInternallySupportedIntent(uri);
if (intent == null) {
final PackageManager pm = getPackageManager();
final ComponentName component = new ComponentName(context, DeepLinkHandlerActivity.class);
pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
Intent webIntent = new Intent(Intent.ACTION_VIEW);
webIntent.setData(uri);
context.startActivity(webIntent);
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void[] params) {
SystemClock.sleep(2000);
pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
return null;
}
};
task.execute();
} else {
startActivity(intent);
}
finish();
}
}
Hope that helps.
Note: It looks like you need to delay the re-enabling by a couple of seconds for this to work.
Note 2: For a better experience, using a Transparent theme for your activity will make it look like your app didn't even open.
Note 3: If for some reason your app crashes or gets killed before the component re-registers, you're loosing deep link support forever (or until next update/reinstall), so I would also do the component re-enabling in App.onCreate() just in case.
URX provides a free tool (urxlinks.js) that automatically redirects mobile web users into an app if the app is installed. The documentation is available here: http://developers.urx.com/deeplinks/urx-links.html#using-urx-links-js
If two apps are using same scheme then the chooser screen will be popped as android wont know which app the link is intended for. Using custom scheme for your app might solve this issue. But still you can't be sure no one else will use that scheme.
It sounds like you're trying to treat your mobile app and mobile website as extensions of the same experience. That's good practice, generally speaking, but at this point the two are simply not at parity. At least until they reach parity I would not recommend automatically pushing the end user into your mobile app because users who are deliberately using the mobile site in order to find the content your app is missing will find this incredibly frustrating.
Instead, it might make sense to use a smart banner to encourage users on the mobile website pages that do have an in-app equivalent to open the app instead. Those banners would be your deeplinks. You could create them yourself or integrate a tool like Branch ( https://branch.io/universal-app-banner/ ) that handles deep linking and smart banners both.
That last part of your question has to do with where to place the deep links. One advantage to using smart banners instead of redirects is that you can embed them into the appropriate templates on your CMS instead of needing to rely on url detection.
Good luck!
This was my solution to your second problem. PackageManager.queryIntentActivities() will give you the list of apps/activities that would appear in the chooser. Iterate through the list (which should at least include the browser) and find an activity whose package name doesn't match the current app, and set the intent class name to it, then launch an Activity with that intent and call finish();
public Intent getNotMeIntent(Uri uri) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
PackageManager manager = context.getPackageManager();
List<ResolveInfo> infos = manager.queryIntentActivities(intent, 0);
for (int i = 0; i < infos.size(); i++) {
ResolveInfo info = infos.get(i);
// Find a handler for this url that isn't us
if (!info.activityInfo.packageName.equals(context.getPackageName())) {
intent.setComponent(null);
intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
return intent;
}
}
// They have no browser
return null;
}
The Transparent theme (mentioned above) should be a good solution for the first problem.
In destination activity in onCreate set this code for Kotlin:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIntent(intent)
}
private fun handleIntent(intent: Intent?) {
val appLinkAction: String? = intent?.action
val appLinkData: Uri? = intent?.data
showDeepLinkData(appLinkAction, appLinkData)
}
private fun showDeepLinkData(appLinkAction: String?, appLinkData: Uri?) {
if (Intent.ACTION_VIEW == appLinkAction && appLinkData != null) {
val promotionCode = appLinkData.getQueryParameter("exampleQueryString")
Log.e("TAG", "Uri is: $appLinkData")
}
}
I have added intent-filter to my activity, so I can receive send intents that contain text, I have set data to text/plain, everything works fine, I can choose my application from the picker, it is opening, showing me the text, but, when I minimalize my app, and then click again on ColorNote application (I am using it to take my notes) it is opening my app instead of ColorNote, anyone ever had similiar issue? And know how to resolve it?
#Edit
Do not know why I am getting - points, if you think it is easy question you could write how to solve it instead of giving -.
By the way, I don't think any code is neccessary in here right now.
That's the way it should behave. For e.g. if an application starts another and you leave the application without closing it's task you will return to the intent it started. I can reproduce this with apps which take me to the play store:
What I did:
First test:
Start application
Let it start the Play Store
Press Home
Start the application again
welcome back to the app store
Second test:
Start application
Let it start the Play Store
Close application (Task Manager)
Start the application again
welcome back to the application
Okay now on how to solve your problem. I would check if my application was started by another intent -> http://developer.android.com/training/basics/intents/filters.html
boolean wasStartedByOtherApp = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
if (intent.getType().equals("text/plain")) {
wasStartedByOtherApp = true;
}
}
#Override
protected void onPause() {
super.onPause();
if(wasStartedByOtherApp){
finish();
}
}
I have not tested this. This should just give you an idea on how to solve the issue you are facing. But be warned as an android user I would be angry if an application closes itself just when I want to check my calendar if I have time and would like to return to the intent the app just started.
I created an app to simply open a web browser and go to a specific website. However, after it's run for the first time, if I go to another app and run the app again, I will get nothing except the black screen. I knew I can find the page from web browser, however I wonder if there is a way to bring the content back when I launch the app and the user could get back to work from where he/she stopped.
here is my code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/** setup the url and start a browser with
* the hotmial.com and return to the user
* */
String url = "http://www.hotmail.com";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
}
Thanks guys
Take a look at the activity lifecycle. You may want to move your intent to OnStart() for when the application comes back to the foreground after being stopped.
The activity may still exist when you run your app a second time; perhaps onCreate is not called.
After you throw the Intent to run the browser, shut down the activity so that onCreate happens every time.
If your application is here only to give a link to a website, this is much overhead in my opinion. Android users can make a shortcut to any website they want on the desktop, the shortcut icon being shown with the site's favicon in its corner.
Try this if you want to open the browser OUTSIDE your app. I know it seems like a simple(and trivial) edit, but I've never had issues setting it up this way.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/** setup the url and start a browser with
* the hotmial.com and return to the user
* */
String url = "http://www.hotmail.com";
Uri uri = Uri.parse(url);
Intent i= new Intent(Intent.ACTION_VIEW, uri);
startActivity(i);
}
Why don't you use the android.webkit.WebView which will allow you to directly browse web pages in your app. Here the web content will remain persistent as long as the application is running. On the chance that your application stops you could save the url using WebView.getURL() which would allow you to continue from where you left of the next time you start your app. If this is what you want to do let me know and I will post some sample code.