I am new to mobile cross platform development. I am using Xamarin and Mvvmcross to create an application.
The problem I am currently faced with is that when I want to make a request to turn on a Bluetooth, calling StartActivityForResult(), my active activity is closing and after clicking on the dialog activity is not shown back.
When I used this method before on a simple Xamarin.Android applicaiton it worked as expected, showing a dialog request for turning on bluetooth while activity is on the background still active.
The similar problem is also happens when I am using an Intent to start an activity for sending an e-mail via built-in mail app. After sending an e-mail I am not redirected to my application back and my application is being suspended.
Here is my method:
[Activity(NoHistory = true, ScreenOrientation = ScreenOrientation.Portrait)]
public class MainView : MvxAppCompatActivity
{
...
protected override void OnViewModelSet()
{
base.OnViewModelSet();
...
var bluetoothAdapter = BluetoothAdapter.DefaultAdapter;
if(!bluetoothAdapter.IsEnabled)
RequestEnableBluetooth();
...
}
public void RequestEnableBluetooth()
{
Intent turnOnBtIntent = new
Intent(BluetoothAdapter.ActionRequestEnable);
StartActivityForResult(turnOnBtIntent, 0);
}
...
}
MvvmCross does nothing like that. It is Android that does this. It does not give you any guarantee that your Activity lives on when it goes into background, it may kill it off whenever it likes.
However, your problem is that you are using NoHistory = true on your Activity this way no one can return to this Activity when navigated away from it.
Related
I've been facing a problem for a while regarding startActivity(intent). What i'm developing is a kind of key word detection like "ok google" that triggers an alert when the user says the word. To achieve that when the user is not using the app i have a LifecycleService that runs in foreground and listens to the user. When the user says the word and the app is killed it opens the activity i need using startActivity from the service, but the problem is that if i keep listening and change to other activity (using the app normally) the detection works (because i hear the sound i put when the word is recognized) but the app doesn't start the activity it should (although the startActivity(intent) is called). I'm pretty sure that the problem has to be with the context that maybe is not the correct one when i open the app with startActivity from the service, but i don't know how to fix it. I tried to user some other variables like applicationContext or the androidContext() from Koin but it is not working.
class SpeechRecognitionService : LifecycleService() {
...
//onStartCommand starts the audio recognizer and startAlert() is triggered when the alert is recognized. It is always correctly called
private fun startAlert() {
//This is not showing MainActivity although i execute it
startActivity(MainActivity.getDialogIntent(this))
//I always hear this audio when the app detects word
val audio = MediaPlayer.create(this, R.raw.alert_detected_audio)
audio.start()
}
}
The MainActivity.getDialogIntent(this) is just a common Intent
fun getDialogIntent(context: Context): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra(SHOW_ALERT_DIALOG_KEY, true)
return intent
}
The problem only happens if the app is started with the voice recognition (after being killed). If i for example kill the app but open the app again pressing the app icon it works correctly. If i start the app with voice (so, from the startActivity i have above) i worked that time and open the app but when i change to other activity it fails to start.
You need to use a proper "launcher Intent". The easiest way to get one is to call
PackageManager pm = getPackageManager()
Intent intent = pm.getLaunchIntentForPackage("my.package.name")
Use this Intent to start your MainActivity instead of calling MainActivity.getDialogIntent(this)
I created Android app with Xamarin Forms. I use Xamarin UI Test for testing life cycle app. I need events for app - OnStart, OnSleep, OnResume.
My algorithm:
1. Start test, check UI (OnStart).
2. App goes to the background(OnSleep). For this I use this code:
in Activity
[Export("GoOut")]
public void GoOut()
{
var uri = Uri.Parse("http://www.google.ru");
var intent = new Intent(Intent.ActionView, uri);
StartActivity(intent);
}
in Test
app.Invoke("GoOut");
It work. Now I need to go back to the app. I want to trigger an event OnResume and testing my app. How to do it?
I tried this:
app.Invoke("GoOut");
app.Back();
and this
//in Activity
[Export("Back")]
public void Back()
{
this.OnBackPressed();
}
//in my test
app.Invoke("GoOut");
app.Invoke("Back");
But it not work for me.how Can I go back to the application from browser?
It was easy:
ConfigureApp.Android.StartApp(AppDataMode.DoNotClear);
I have an application that uses Urban Airship for push notification. When a notification arrives and the user clicks on it, activity A in my application should open and do something.
I've installed the BroadcastReceiver as is shown in the docs, and it's almost working.
When my app is in the foreground I don't let the user see the notification at all, and just handle it automatically.
When my app is not running at all, the activity opens up just fine.
When my app is in the background (which always happens when A is the top activity), a second instance of Activity A is created.
This is, of course, a problem. I don't want two A activities, I just want one of them. Here's the relevant BroadcastReceiver code:
#Override
public void onReceive(Context ctx, Intent intent)
{
Log.i(tag, "Push notification received: " + intent.toString());
String action = intent.getAction();
int notificationId = intent.getIntExtra(PushManager.EXTRA_NOTIFICATION_ID, -1);
if(action.equals(PushManager.ACTION_NOTIFICATION_OPENED))
{
Intent intentActivity = new Intent(ctx, ActivityA.class);
intentActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
UAirship.shared().getApplicationContext().startActivity((intentActivity);
}
}
UPDATE:
I tried to bypass this bug by calling System.exit(0) when the user presses Back on Activity A. The process ended, but then it was restarted immediately! My BroadcastReceiver is not called again in the second instance. What's happening?
UPDATE 2:
#codeMagic asked for more information about the app and activity A.
This app lets its user review certain items and comment on them. Activity A is started when the app is launched. If the user's session isn't valid any more, a Login activity is started. Once the user logs in, activity A becomes active again. A only has a "No items to review" message and a "Try now" button.
When the user logs in, the server starts sending push notifications whenever a new item is available for review. When the app gets the notification, activity A accesses the server and gets the next item to review. The item is shown in activity B. Once the review is submitted to the server, activity B finishes and activity A is again the top activity.
The server knows when a user is reviewing an item (because activity A fetched it), and doesn't send push notifications until the review is submitted - meaning a notification can't come if the user isn't logged in or if the user is viewing activity B.
While I agree there is a subtle race condition here, it is not causing the problem I'm seeing - in testing I am 100% positive there's no race condition - the push notification is only sent after Activity A becomes active again.
The solution was to add a launchMode='singleTask' to the activity in AndroidManifest.xml . As a result, instead of a new activity, onNewIntent of the same activity instance is called.
You can use one of several Intent Flags. FLAG_ACTIVITY_REORDER_TO_FRONT being one of them. This will bring the Activity to the front of the stack if it is already in the stack and if not then it will create a new instance. I believe you will still need FLAG_ACTIVITY_NEW_TASK if you aren't calling it from an Activity
Intent.FLAG_ACTIVITY_CLEAR_TOP should also work. But this will clear any other Activities on the stack. It just depends on what other functionality you need. Look through the Intent Flags and see which of these will work best for you
There are multiple scenarios when this could happen. One of them can be handled this way. Please see my answer here: https://stackoverflow.com/a/44117025/2959575
Ok, two notes on this :
You can register a broadcast receiver via the manifest so it is independent of any parts of your app. and use a Singleton pattern (keep a static reference to your activity somewhere in your app) that way you can check if their is an activity viewing or not and process accordingly.
// your activity A
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
myActivityReference = this;
}
public void onPause() {
super.onPause();
if (isFinishing()) {
myActivityReference = null;
}
}
or you can keep everything as it is and use activity lunching modes flags in your manifest such as singleTop, singleInstance ... etc. take a look here android activity lunch modes
I'm new to Android development. I am trying to monetize a live wallpaper that I built and the ad delivery company wants me to call their code from the onCreate of an activity.
The live wallpaper didn't have an activity before I started to monetize it, being an extension to WallpaperService, so I've added one. I've managed to create the activity and make it translucent, but it doesn't close when the dialog closes. I cannot edit the dialog code since it is being created by a call into a .jar, so I thought I could setup a listener for when the dialog is dismissed, but I wasn't able to find any practical examples that might help with the code below.
LWP.java
public class SBLiveWallpaper extends WallpaperService {
super.onCreate();
Intent i = new Intent();
// i.setClass(this, MainActivity.class);
i.setComponent(new ComponentName("appname", "appname.MainActivity"));
// i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
}
activity_main.xml has no elements (just the RelativeLayout)
MainActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppBucksAPI.initialize(this, APPID, "APIKEY", true, null, null);
AppBucksAPI.userOptOutDialog(this, "marketname");
}
I could make the activity be non-transparent, and just add a close button, but that is ugly and confuses users.
Edit for clarification: I had tried originally to call the dialog directly from the service's onCreate(). It causes the LWP to crash in the screen where you can make it the active LWP. The error I get is android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application.
I contacted AppBucks support before making the original post here. Their response (pasted below) prompted me to create the translucent activity.:
I believe this error means that there is a problem with the first parameter you are passing to the AppBucksAPI.userOptOutDialog method… the call which looks like this from the docs:
AppBucksAPI.userOptOutDialog(this, "<App Name>");
This call expects an Activity or Activity context as the first parameter. It needs this because our default opt out dialog uses an AlertDialog call, which requires an active Activity for it to display correctly. If you are already creating an Activity along with your service, you should pass that activity as the first parameter instead of “this” (or you could move this call to the onCreate of that activity instead of onCreate for the service).
If you don’t have an Activity in your app, I found this StackOverflow question which has an answer that may help (in a nutshell, you can create a transparent activity when your service starts up, and make the userOptOutDialog call from that instead of your service’s onCreate method):
Display AlertDialog as system overlay window from Service
Unfortunately, the above article covers creating the activity and closing the dialog under the assumption that the person reading it has access to the dialog's code. Since I do not have access to that, because it is imported into my project as a library, I need to know how to listen, from the parent activity, for the child to finish.
I did some digging and it looks like either of these could work, depending on how the activity is started from the dialog call my code makes:
http://developer.android.com/reference/android/app/Activity.html#finishActivityFromChild(android.app.Activity, int)
or
http://developer.android.com/reference/android/app/Activity.html#finishFromChild(android.app.Activity)
I'll give those a try tonight.
The AppBucks SDK also exposes the following functions:
setIconAdsEnabledForUser
setPushAdsEnabledForUser
The AppBucksAPI.userOptOutDialog is basically a convenience function that wraps calls to these in an AlertDialog. For your app, it probably makes more sense to forego the convenience function and write your own AlertDialog that calls the enable functions directly. That way you will have full control over what happens when the dialog is dismissed and can close the new activity you created when you need to.
Looking at the AppBucks API and documentation, I don't think using an Activity is mandatory. It is just the most common way.
I think you can call AppBucks method in your service onCreate as well?
When dismissing your dialog, send an intent to your activity for it to close itself.
For instance
Put this in the dialog dismiss method:
sendBroadcast(new Intent(MainActivity.ACTION_TERMINATE));
Then in the MainActivity add and register a BroadcastReceiver:
Add fields for the receiver and the filter in the activity:
private ActivityBroadcastReceiver mReceiver;
static final IntentFilter mFilter = new IntentFilter();
static {mFilter.addAction(ACTION_TERMINATE);}
Instantiate it in onCreate():
mReceiver = new ActivityBroadcastReceiver();
Register it in onResume():
registerReceiver(mReceiver, mFilter);
Unregister it in onPause():
unregisterReceiver(mReceiver);
And the broadcast receiver's inner class in the activity would look like this
private class ActivityBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
try {
String action = intent.getAction();
if (ACTION_TERMINATE.equals(action)) {
finish();
}
} catch (Exception e) {
Log.w(mTag, "Oops: " + e, e);
}
}
}
I have an annoying issue with the foreground dispatch behavior. Sometimes instead of calling onNewIntent(), it completely recreates the activity, which breaks the app's workflow.
My concrete situation: Activity A is the MainActivity, which uses the foreground dispatch. Everything works as it should. However, in my activity B, which is launched from the browser (VIEW action), the foreground dispatch doesn't work under some circumstances anymore.
The workflow:
I start the MainActivity, switch to the browser (without closing
the MainActivity), launch activity B and attach my NFC device --> it
creates a new activity B.
I start the MainActivity and close it again. After that I switch
to the browser, launch activity B and attach my NFC device -->
everything works with onNewIntent()
The code is correct, e.g. if I attach the NFC device in the first scenario twice, it works as it should at the second time, but not at the first time. In the MainActivity and activity B I definitively call the disableForegroundDispatch() method in the activity's onPause() method.
Is there a solution for my specific problem? For me it sounds like a bug.
Edit:
public void resume(Activity targetActivity) {
if (nfc != null && nfc.isEnabled()) {
// nfc is the default NFC adapter and never null on my devices
Intent intent = new Intent(targetActivity, targetActivity.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(targetActivity, 0, intent, 0);
nfc.enableForegroundDispatch(targetActivity, pendingIntent, null, new String[][] { new String[] { IsoDep.class.getName() } });
}
}
public void pause(Activity targetActivity) {
if (nfc != null && nfc.isEnabled()) {
nfc.disableForegroundDispatch(targetActivity);
}
}
These methods are called in the corresponding methods in each activity. Thanks for the help!
Solution: After a very long research I finally found the issue. Logcat printed:
startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: Intent
I found other issues at Stackoverflow, where people had have the same issue with the NotificationManager, but all the hints didn't help me. Adding the flag singleTask to my activity B did the trick for me, but to be honest I don't understand it, because the context is always an activity.
I removed all the code from the MainActivity and the first scenario still didn't work. I romved the MainActivity from the manifest and after that everything was fine. Maybe it is a problem, that an app instance is running and activity B is launched from the browser? I don't know.
Anyway, thanks for the help NFC guy!
The IntentFilter[] that you pass to enableForegroundDispatch is empty. So your NFC intent probably arrive at your Activity due to the IntentFilter(s) in the manifest file. This explains the behaviour you observe, as an NFC intent always creates a new instance when delivered this way.
Add something like this instead to your code for enabling foreground dispatch:
IntentFilter[] iFilters = new IntentFilter[2];
iFilters[0] = new IntentFilter();
iFilters[0].addAction("android.nfc.action.TECH_DISCOVERED");
iFilters[1] = new IntentFilter();
iFilters[1].addAction("android.nfc.action.TAG_DISCOVERED");
iFilters[1].addCategory(Intent.CATEGORY_DEFAULT);
And pass that as parameter to enableForegroundDispatch.
UPDATE:
I recently learned more about this specific problem. It is caused by the way Android determines in which task a new Activity should be launched. I don't know or understand the specific details of how that works, but the effect is that:
When Activity B is launched from the Browser, it is created in the Browser's task
When the NFC intent arrives, the system determines that a new Activity B is to be created in Activity A's task
Because of 2., the SINGLE_TOP is not ignored: there is only one instance of Activity B at the top of A's task. When Activity A is closed, it's task has disappeared, so Activity B will always be created in the Browser's task, as you have observed.
You may feel that this is an Android bug in this case (I do, I think), but this behaviour of how to create activities in which task is so fundamental to Android that many apps rely on it. So it is very unlikely that this will ever change.
Possible work-around: declare Activity B with launchMode "singleTask" (or "singleInstance"). Then a new (3rd) task will be created when B is launched.
I guess your workflow is as follow : Main --> Detect tag --> Reader Activity --> Writer activity Detect tag --> Write tag
This problem seems to arise when your writer activity (the one with foreground enabled, I suppose that's for writing purposes) belongs to an Activity Stack (e.g. a Task) that was called from a previous tag discovery.
In particular, it doesn't arise if your workflow is as follow :
Main Writer activity Detect tag --> Write tag
My workaround is to call the writer activity in a new task to begin with.
In the activity that laucnhes the writer, just add the new task flag in the intent that starts the writer.
startActivity(new Intent(this,MyTagWriterActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Main --> Detect tag --> Reader Activity -NEW_TASK-> Writer activity Detect tag --> Write tag
It does mess with the activity history, but makes the writer activity more predictable.