In my Application when a Button is clicked it redirects the user to Google Maps App on the mobile using the following code
Uri gmmIntentUri = Uri.parse("google.navigation:q=" + a + "," + b);
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
mapIntent.setPackage("com.google.android.apps.maps");
startActivity(mapIntent);
now What I want is to when the user closes the Google Maps/ returns to My app, it should start a different activity rather than the one it was left on.
Is it possible to do this? I've used a delay to start the secondary activity but it's not giving the result I need as sometimes it runs over the Google Maps app.
Im fairly new to Android studio altogether.
Edit- is onSaveInstanceState is a possible way to overcome this issue?
In Google documentation there is a method: startActivities(Intent[] intents, Bundle options)
I personally have never used it before, but seems to be what you want.
Here is the code. I tried it and it worked. It is in Kotlin, but you can re-write it in Java. Note: Make sure you start the map activity as the second in the array.
val gmmIntentUri = Uri.parse("google.navigation:q=$a,$b")
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
mapIntent.setPackage("com.google.android.apps.maps")
val i = Intent(this, SecondActivity::class.java)
val intents = arrayOf(i, mapIntent)
startActivities(intents, null)
There could be couple approaches to solve that issue. For example by using static Map<K,O> and monitoring Activity lifecycle. But because static member should be handled properly to avoid leaking why not use SharedPreferences.
For example in above part of code where you start Google Maps you can save status in SharedPreferences just call:
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putBoolean("maps", true)
.apply();
And when Google Maps are launched your app will go in onPause state so after user closes the Google Maps your app will be resumed so onResume method will be called there you can check status:
#Override
protected void onResume() {
super.onResume();
boolean isComingFromMaps = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean("maps", false);
if(isComingFromMaps) {
//Launch your activity here
//also don't forget to save value back to false to avoid bug when next time Activity is started
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putBoolean("maps", false)
.apply();
}
}
Also last checking is to make sure if user doesn't return from maps rather he minimises both apps, or kill them you want to set value back to false before super.onDestroy() is called. Like:
#Override
protected void onDestroy() {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putBoolean("maps", false)
.apply();
super.onDestroy();
}
Related
I decided to experiment with MAUI. I am approaching first an Android App, and using Shell for navigation.
My App has 2 ways of opening:
When it's opened by the user tapping on the icon
Through a deep link, triggered by another app.
The issue I'm having is that when the app is triggered through the Deep link, I need to navigate to a specific page. I am trying to do it on the OnNewIntent accessing the Current instance of Shell, but when doing GoToAsync("my_route") it gives an error when trying to navigate to the new page.
This is what I have on my MainActivity:
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var action = intent.Action;
var data = intent.DataString;
if (!string.IsNullOrWhiteSpace(data) && data.Contains("/data/")) {
if(Shell.Current != null)
{
Shell.Current.GoToAsync("myroute)";
// Also tried:
// - Shell.Current.GoToAsync("myroute").Wait();
// - App.Current.Dispatcher.Dispatch(async () => await Shell.Current.GoToAsync("//myroute")); (suggested by #toolmakersteve )
}
}
}
And this is the error:
Java.Lang.IllegalArgumentException: 'No view found for id 0x1
(unknown) for fragment ShellItemRenderer{19d353d}
(6c8560ab-dd58-4cbf-9e8b-2b9e12315f45 id=0x1)'
I'm assuming this has something to do with the fact that what I'm doing is not possible, so I need to find the RIGHT way to navigate to a specific page from OnNewIntent on MAUI, using Shell navigation.
UPDATE: It's also important to note that when the Deep Link triggers the app to open, there are two different behaviours:
If the app was already running, it throws the above mentioned exception
If the app was not already running, it opens regularly on the main screen, with no errors, but I would expect it to navigate to the desired Page.
Thanks!
First, make sure that GoToAsync("myroute") works if you use it somewhere more typical, such as a button press.
Assuming that works, then perhaps the intent code isn't running in the Dispatcher context (previously known as MainThread). Try:
Dispatcher.Dispatch(() => {
Shell.Current.GoToAsync("myroute");
});
VERSION 2
Perhaps deep link logic runs BEFORE App's OnResume.
If so, this might work:
In App.xaml.cs:
public partial class App : Application
{
...
public static bool FromDeepLink;
protected override void OnResume()
{
base.OnResume();
if (FromDeepLink)
{
FromDeepLink = false;
MainPage = new MainPage();
Dispatcher.Dispatch(() =>
{
Shell.Current.GoToAsync("myroute");
});
}
}
}
Then in OnNewIntent:
if (!string.IsNullOrWhiteSpace(data) && data.Contains("/data/")) {
App.FromDeepLink = true;
}
Conceptually #ToolmakerSteve answer is correct, but the OnResume event of the Application class seems not to fire when the app is resumed by intent (seems to be a Maui bug), however Android's native OnResume works and fires correctly even when the app is resumed via intent, all you have to do is in the MainActivity class to override Android's native OnResume method:
protected override void OnResume()
{
base.OnResume();
var fromDeepLink = Preferences.Get("FromDeepLink", false);
if (fromDeepLink)
{
Preferences.Set("FromDeepLink", false);
Shell.Current.GoToAsync("myroute");
}
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var action = intent.Action;
var data = intent.DataString;
if (!string.IsNullOrWhiteSpace(data) && data.Contains("/data/"))
{
Preferences.Set("FromDeepLink", true);
}
}
Signing out or disconnecting the GamesClient is straightforward when it is from your own UI, such as a button on the main menu.
However, users can also sign out from the game from the Google Play UI in the acheivements and leaderboard views displayed by the intents such as getAllLeaderboardsIntent(). (It's a bit hidden, but if you tap the menu in the upper right, it lets you sign out.)
There are a few promising listener interfaces like OnSignOutCompleteListener but they don't seem to work with a sign out via the google UI, only from your own UI calling GamesClient.signOut().
How can I detect that the user has signed out from the leaderboards or achievement intents? Is it possible to have a callback for this?
I want to be able to update my in-game UI to reflect the logged-in status.
Unfortunately GameHelper doesn't detect when you logout from Google play games.
What you need to do is to put this in your onActivityResult() method in your activity.
I encounter a crash error when I tried using aHelper.signOut() when res == RESULT_RECONNECT_REQUIRED is true.
Instead I created a resetAllValues() method which resets back all values to its default in GameHelper.
In my MainActivity.java
protected void onActivityResult(int req, int res, Intent data) {
super.onActivityResult(req, res, data);
if (res == GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED) {
aHelper.resetAllValues();
} else {
aHelper.onActivityResult(req, res, data);
}
}
My method in GameHelper.java
public void resetAllValues()
{
mProgressDialog = null;
mAutoSignIn = true;
mUserInitiatedSignIn = false;
mConnectionResult = null;
mSignInError = false;
mExpectingActivityResult = false;
mSignedIn = false;
mDebugLog = false;
}
Duplicate from:
How can i check if user sign's out from games services default view?
As I see it, there is no elegant solution to that. You can check the response_code in onActivityResult for INCONSISTENT_STATE and cut off the GamesClient, but I'm not sure, if you can potetially get to an inconsistent state in any other manner...
I am trying to do programmatic update to the application I am writing, since it is not a Google Play application and I want to provide a way to do updates.
I've been searching around and found out how to start the Android installer after I download the APK for the update, but I need to get a result from the installer, that tells me if the update succeeded or not, or if it was cancelled by the user.
I saw a bunch of questions on StackOverflow about this, and the answers usually involved using a broadcast receiver. The problem with that is that it can only receive intents about the package being installed, not about canceled installs of fails.
I did some more research and it seems the Intent API provides some extras such as Intent.EXTRA_RETURN_RESULT, which if set to true should return a result from the installer activity - I guess via onActivityResult. Unfortunately this doesn't work. Is there anybody that got this working/does it work like this?
Here is the code preparing the installer activity start, that I currently have:
Intent installApp = new Intent(Intent.ACTION_INSTALL_PACKAGE);
installApp.setData(downloadedApk);
installApp.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
installApp.putExtra(Intent.EXTRA_RETURN_RESULT, true);
installApp.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.getApplicationInfo().packageName);
context.startActivityForResult(installApp, 1);
Do you use Fragments? The onActivityResult will be called from the Activity or Fragment you have called startActivity(...). Fragment#startActivity(...) does exist. Use it to get the Fragment's onActivityResult(...) called.
If you are not using Fragments, this Workaround will work.
Workaround Pseudocode
// CURRENT_VERSION is a const with the current APK version as int
Activity#onStart() {
super.onStart();
checkForUpdaterResult();
/*...*/
}
Activity#checkForUpdaterResult() {
final int updateVersion = preferences.getInt(UPDATE_VERSION, -1);
switch(updateVersion) {
case -1:break;
default:
// updateVersion = oldVersion is smaller than the new currentVersion
boolean success = updateVersion < CURRENT_VERSION;
onUpdaterPerformed(success, updateVersion , CURRENT_VERSION);
break;
}
}
Activity#startUpdate(File pAPK) {
perferences.putInt(UPDATE_VERSION, CURRENT_VERSION);
/*...*/
}
Activity#onUpdaterPerformed(boolean pSuccess, int pFromVersion, int pToVersion) {
Toast.show("Update success: " + pSuccess);
/* e.g. migrate DB */
/*...*/
}
In my application I have used some code from the iosched 2012 app. In specific the starting workflow is the following:
1.The user presses the launcher icon of the app
2.HomeActivity checks if the user is authenticated. If he/she is not, it starts the Authentication activity, passing it intent to it and finishes itself
3.When the login process is successful, the authenction activity starts an activity in order to start the HomeActivity and finishes itself
4.HomeActivity checks again if the user is authenticated and displays the home screen of the application.
The following code works like a charm in API Level > 11. Today, I tried the app in a Gingerbread and it fails. Step 3 works, but although the HomeActivity starts it's not brought to front. You have to use the recent list and choose the application in order to see the homeactivity and its now displayed content.
Here's the code and check from the HomeActivity in the oncCreate method
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!AccountUtils.isSystemAuthenticated(this)) {
AccountUtils.startSystemAuthentication(this, getIntent());
finish();
} else if(!AccountUtils.isAppAuthenticated(this)) {
AccountUtils.startAppAuthentication(this, getIntent());
finish();
}
if(isFinishing()) {
return;
}
setContentView(R.layout.activity_main);
...
}
}
The method invoked in the Authentication activity after the login process is completed
protected void handleLoginSuccess(LoginServiceResponse response, String username, String password) {
if(....) {
if(mFinishIntent != null) {
mFinishIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mFinishIntent.setAction(Intent.ACTION_MAIN);
mFinishIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(mFinishIntent);
}
finish();
} else {
super.handleLoginSuccess(response, username, password);
}
}
Where the mFinishIntent member variable is the intent passed from the HomeActivity (using getIntent())
As I mentioned, in API Level > 11, this works well, and the breakpoint in HomeActivity's onCreted method is hit twice, while in a Gingerbread phone, is hit only once (only when the application starts).
Do I have to use another flag or do you have any other idea of what's going on?
Thanks
What is probably happening is that the activity is only created when the app is started and then when you go back to it from the Authentication activity, it is simply resumed. Try putting the authentication checking code in HomeActivity in the onResume() method.
Here is some more info: http://developer.android.com/reference/android/app/Activity.html#ProcessLifecycle
I have a strange scenario here.
I have this code:
// For checking if the person is logged in.
first_time_check();
setContentView(R.layout.main);
// ...next lines of code
and the first_time_check() function checks if the user is logged in for the first time. If their user_id is not in the SharedPreferences, I redirect them to log in:
public void first_time_check()
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( ProblemioActivity.this);
String user_id = prefs.getString( "user_id", null ); // First arg is name and second is if not found.
String first_time_cookie = prefs.getString( "first_time_cookie" , null );
// About setting cookie. Check for first time cookie
if ( first_time_cookie == null )
{
// This user is a first-time visitor, 1) Create a cookie for them
first_time_cookie = "1";
// 2) Set it in the application session.
prefs.edit().putString("first_time_cookie", first_time_cookie ).commit();
// Make a remote call to the database to increment downloads number
// AND send me an email that it was a new user.
}
else
{
// If not first time, get their id.
// If user_id is empty, make them an account.
// If id not empty, call the update login date and be done.
if ( user_id == null )
{
// User id is null so they must have logged out.
Intent myIntent = new Intent(ProblemioActivity.this, LoginActivity.class);
ProblemioActivity.this.startActivity(myIntent);
}
else
{
// Make a remote call to the database to increment downloads number
}
}
return;
}
So after the code executes the
Intent myIntent = new Intent(ProblemioActivity.this, LoginActivity.class);
ProblemioActivity.this.startActivity(myIntent);
it still executes below the original code that calls this functions.
Any idea how that can happen?
Thanks!!
This is excerpted from the Dev Guide
Shutting Down an Activity
You can shut down an activity by calling its finish() method.
You can also shut down a separate activity that you previously
started by calling finishActivity().
Note: In most cases, you should not explicitly finish an activity
using these methods. As discussed in the following section about the
activity lifecycle, the Android system manages the life of an
activity for you, so you do not need to finish your own activities.
Calling these methods could adversely affect the expected user
experience and should only be used when you absolutely do not want
the user to return to this instance of the activity.
Calling finish() on the activity seems appropriate here as you do not want the user to return to this activity.