I have written IBeacon App and my goal is to send Http request when the app is in background. I use the DidDetermineState event to determine the entrance/leave of a region. The thing is that when the event is called Http request unexpectedly stops somewhere. No exception is thrown and can not finish the request.
The strange thing is that the call stops randomly on different lines.
[Export ("locationManager:didDetermineState:forRegion:")]
public void DidDetermineState (CLLocationManager manager, CLRegionState state, CLRegion region)
{
string message = "";
if (state == CLRegionState.Inside) {
message = "Inside";
var url = String.Format ("{0}About", "https://myAPIURl/");
try {
var _httpClient = new HttpClient ();
_httpClient.Timeout = TimeSpan.FromMilliseconds (30000);
lock (_httpClient) {
try {
var getResult = _httpClient.GetAsync (url).Result;
if (getResult.IsSuccessStatusCode) {
var contentString = getResult.Content.ReadAsStringAsync ().Result;
var contentObject = JsonConvert.DeserializeObject<AboutUsModel> (contentString);
message = "result ok";
}
}
//ncrunch: no coverage start
catch (Exception e) {
message = e.Message;
}
}
} catch (Exception ex) {
message = ex.Message;
}
}
if (state == CLRegionState.Outside) {
message = "Outside";
}
}
P.S The request is successfully sent when the app is in foreground mode.
Any help is appreciated!
Apple only gives you about five seconds of background execution time after getting a beacon callback in the background. If the web service does not respond quickly enough, the app will simply suspend.
A typical way to solve this problem is to request extra background running time -- you can get up to 3 minutes worth just by asking. I wrote a blog post about the process here. The blog post is specifically about extending background ranging time, but the same technique applies to getting extended time for web service callbacks.
Related
I have referred to this question already : Stopping and restarting foreground service scanning with altbeacon
My requirement is to restart the beacon scan depending on API response containing scanTime and waitTime. I would have started a scan with default config and once i receive API response from server i need to restart the scan with new config. So this is the code i have. Is this valid to just stop and then start the scan again? Will this have any adverse effect?
public void reconnect(int scanTimeInMills, int waitTimeInMillis) {
try {
if (beaconManager != null) {
beaconManager.stopRangingBeaconsInRegion(ALL_REGION);
beaconManager.unbind(this);
}
beaconManager = null;
beaconManager = BeaconManager.getInstanceForApplication(MyApplication.getInstance());
beaconManager.getBeaconParsers().add(new BeaconParser()
.setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
beaconManager.setForegroundScanPeriod(scanTimeInMills);
beaconManager.setBackgroundScanPeriod(scanTimeInMills);
beaconManager.setBackgroundBetweenScanPeriod(waitTimeInMillis);
beaconManager.setForegroundBetweenScanPeriod(waitTimeInMillis);
if (!beaconManager.isBound(this)) {
String channelName = "App Notification Service";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel chan = new NotificationChannel(
String.valueOf(Constant.APP_CLOCKIN_NOTIIFICATION_ID), channelName,
NotificationManager.IMPORTANCE_DEFAULT);
chan.setLightColor(Color.BLUE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager manager = (NotificationManager) WrkspotApplication.getInstance()
.getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
}
Intent intent = new Intent(MyApplication.getInstance(), HomeScreenActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
MyApplication.getInstance(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
);
NotificationCompat.Builder notificationBuilder = new Builder(
MyApplication.getInstance())
.setLargeIcon(BitmapFactory
.decodeResource(MyApplication.getInstance().getResources(),
R.mipmap.ic_launcher))
.setSmallIcon(R.drawable.ic_logo_notification)
.setContentTitle(getApplicationContext().getString(R.string.app_name))
.setContentText(
getApplicationContext().getString(R.string.you_are_clocked_in))
.setStyle(new BigTextStyle().bigText(
getApplicationContext().getString(R.string.you_are_clocked_in)))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setChannelId(String.valueOf(Constant.APP_CLOCKIN_NOTIIFICATION_ID));
beaconManager.enableForegroundServiceScanning(notificationBuilder.build(),
Constant.APP_CLOCKIN_NOTIIFICATION_ID);
beaconManager.bind(this);
}
} catch (Exception e) {
Log.e(getClass().getSimpleName(), String.valueOf(e.getMessage()));
}
}
This really does not require all that code. Just use:
beaconManager.setForegroundScanPeriod(scanTimeInMills);
beaconManager.setForegroundBetweenScanPeriod(waitTimeInMillis);
beaconManager.setBackgroundMode(false);
beaconManager.updateScanPeriods();
You do not need to set both foreground and background scan periods. Just do not use BackgroundPowerSaver, and you can leave it in foreground mode all the time.
All the complex logic binding and unbinding shown in the question can cause trouble because it starts and stops the scanning process. This is an asynchronous operation, and it can lead to race conditions. Best to keep it simple.
I am have Xamarin Forms cross platform application for iOS, Android and UWP. I use the Xam.Plugin.Geolocator to get the location from each of the devices. My challenge with iOS is on the first launch of the app on a device. My code runs through and detects that IsGeolocationEnabled for the Plugin.Geolocator.Abstractions.IGeolocator object is false before the use is ever presented with the option to allow the application to use the device's location. This causes my app to inform the user that Location Services are not enabled for the application.
Basically I am hitting the line of code below before the use is ever asked about location services:
if (!App.gobj_RealGeoCoordinator.IsGeolocationEnabled)
ls_ErrorMessage = resourcestrings.GetValue("NoLocationServicesMessage");
On the other platforms, UWP at least, it seems that the app is paused while waiting for the user to respond to the request to use location services. Android just seems to automatically allow access to location if an app uses it.
Any idea how I can have the iOS detect if the request to use location services has been answered or not on the first run? Any suggestions would be greatly appreciated.
UPDATE(1):
I have all the correct items in my info.plist as seen below. I do eventually get the request to use the location just after my app has already checked IsGeolocationEnabled and decided the user has not enabled location services for the app.
UPDATE (2):
So I made a little progress using the following code.
try
{
while (!App.gobj_RealGeoCoordinator.IsGeolocationEnabled)
{
await Task.Delay(1000);
}
ViewModelObjects.AppSettings.CanAccessLocation = App.gobj_RealGeoCoordinator.IsGeolocationEnabled;
}
catch (Exception ex)
{
XXXXXXX
}
The challenge is that the plugin appears to provide me no way of knowing in the user has not responded to the location services dialog (i.e. IsGeolocationEnabled == false) versus the user said no to the location services dialog (also IsGeolocationEnabled == false). Any suggestions?
The way this type of permission request occurs on iOS is through an asynchronous dialog prompt, which is only shown if needed (and not until it is needed). Basically, you need to set up a callback from the CLLocation API. I have a helper class that I use for this purpose, which makes it even easier. Just call GetCurrentDeviceLocation() and pass it a callback function. The callback will only be invoked once the user has granted permission to the app, or if they previously granted permission:
public class GeoLocationService
{
readonly CLLocationManager _locationManager;
WeakReference<Action<Position>> _callback;
public GeoLocationService()
{
_locationManager = new CLLocationManager ();
_locationManager.AuthorizationChanged += AuthorizationChanged;
}
void AuthorizationChanged (object sender, CLAuthorizationChangedEventArgs e)
{
Action<Position> callback;
if (_callback == null || !_callback.TryGetTarget (out callback)) {
return;
}
if (IsAuthorized(e.Status)) {
var loc = _locationManager.Location;
var pos = new Position(loc.Coordinate.Latitude, loc.Coordinate.Longitude);
callback (pos);
}
}
static bool IsAuthorized(CLAuthorizationStatus status)
{
return
status == CLAuthorizationStatus.Authorized
|| status == CLAuthorizationStatus.AuthorizedAlways
|| status == CLAuthorizationStatus.AuthorizedWhenInUse;
}
public void GetCurrentDeviceLocation (Action<Position> callback)
{
_callback = new WeakReference<Action<Position>> (callback);
if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) {
if (_locationManager.Location == null) {
_locationManager.RequestWhenInUseAuthorization ();
return;
}
}
AuthorizationChanged (null, new CLAuthorizationChangedEventArgs (CLAuthorizationStatus.Authorized));
}
}
I am currently using the connectivity plugin to detect network status. What I am trying to do is when I want to send data and the device is offline I start a background task using the messaging service and respective platform specific code as shown here.
The problem I am having is the Android service has a callback that continues being called even after I have stopped the service. The code below assigns a delegate to CrossConnectivity.Current.ConnectivityChanged and should stop the service after it sends the required data when network connection is restored.
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
...
Task.Run(() =>
{
CrossConnectivity.Current.ConnectivityChanged += delegate
{
if (CrossConnectivity.Current.IsConnected)
{
if (!App.ServerManager.IsUserLoggedIn())
App.ServerManager.LoginUserAsync(Constants.__User);
//send our data
foreach (SampleItem s in sList)
{
try
{
//query to see if it exists on the server
App.ServerManager.GetSamplesAsync();
Task t = App.ServerManager.SaveSampleAsync(s);
}
catch (Exception e)
{
Console.WriteLine(#" ERROR {0}", e.Message);
}
}
// Instantiate the builder and set notification elements:
Notification.Builder builder = new Notification.Builder(this)
.SetContentTitle("Jarver Industries")
.SetContentText("Samples Successfuly Sent!")
.SetDefaults(NotificationDefaults.Sound)
.SetSmallIcon(Resource.Drawable.banner);
// Build the notification:
Notification notification = builder.Build();
// Get the notification manager:
NotificationManager notificationManager =
GetSystemService(Context.NotificationService) as NotificationManager;
// Publish the notification:
const int notificationId = 19900203;
notificationManager.Notify(notificationId, notification);
MessagingCenter.Send<BackgroundDataFinishedMessage>(this, "Done");
StopSelf();
}
};
});
return StartCommandResult.Sticky;
}
I am sure I am doing something wrong on a .NET level, but I am unsure what alternatives there are.
I have a WCF service running in a server and a Windows Application which has a timer each 30 seconds checking by WCF some news values from database.
Everything is going well but if my server (when WCF is running) get offline or out for some reason, I get the Exception System.Reflection.TargetInvocationException or System.Net.WebExceptionStatus.ConnectFailure.
What I want is, well, my timer will check every 30 seconds and I want reestablish the connection when my server come back. Actually the only way to do it is close and open the WinForm app.
How can I check if the connection is back or reconnect without close my app?
public MyClass()
{
proxy = new TaskService.Service1Client();
proxy.GetTarefasCompleted += new EventHandler<GetTarefasCompletedEventArgs>
(proxy_GetTarefasCompleted);
timer = new System.Threading.Timer(Callback, null, 0, 30000);
}
private void Callback(Object state)
{
timer.Change(30000, Timeout.Infinite);
proxy.GetTarefasAsync(Environment.UserDomainName, Environment.UserName);
}
void proxy_GetTarefasCompleted(object sender, GetTarefasCompletedEventArgs e)
{
try
{
tarefas = e.Result.OrderByDescending(t => t.Id).ToList();
//code code code
}
catch (Exception ex)
{
if (ex.GetType().ToString() == "System.Net.WebExceptionStatus.ConnectFailure" ||
ex.GetType().ToString() == "System.Reflection.TargetInvocationException")
{
//treat the error
}
}
}
if (proxy.State != System.ServiceModel.CommunicationState.Opened)
{
proxy.Abort();
proxy.Open();
}
I believe this should do the trick.
I want to store position coords (latitude, longitude) in a table in my MySQL DB querying a url in a way similar to this one: http://locationstore.com/postlocation.php?latitude=var1&longitude=var2 every ten seconds. PHP script works like a charm. Getting the coords in the device ain't no problem either. But making the request to the server is being a hard one. My code goes like this:
public class LocationHTTPSender extends Thread {
for (;;) {
try {
//fetch latest coordinates
coords = this.coords();
//reset url
this.url="http://locationstore.com/postlocation.php";
// create uri
uri = URI.create(this.url);
FireAndForgetDestination ffd = null;
ffd = (FireAndForgetDestination) DestinationFactory.getSenderDestination
("MyContext", uri);
if(ffd == null)
{
ffd = DestinationFactory.createFireAndForgetDestination
(new Context("MyContext"), uri);
}
ByteMessage myMsg = ffd.createByteMessage();
myMsg.setStringPayload("doesnt matter");
((HttpMessage) myMsg).setMethod(HttpMessage.POST);
((HttpMessage) myMsg).setQueryParam("latitude", coords[0]);
((HttpMessage) myMsg).setQueryParam("longitude", coords[1]);
((HttpMessage) myMsg).setQueryParam("user", "1");
int i = ffd.sendNoResponse(myMsg);
ffd.destroy();
System.out.println("Lets sleep for a while..");
Thread.sleep(10000);
System.out.println("woke up");
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("Exception message: " + e.toString());
e.printStackTrace();
}
}
I haven't run this code to test it, but I would be suspicious of this call:
ffd.destroy();
According to the API docs:
Closes the destination. This method cancels all outstanding messages,
discards all responses to those messages (if any), suspends delivery
of all incoming messages, and blocks any future receipt of messages
for this Destination. This method also destroys any persistable
outbound and inbound queues. If Destination uses the Push API, this
method will unregister associated push subscriptions. This method
should be called only during the removal of an application.
So, if you're seeing the first request succeed (at least sometimes), and subsequent requests fail, I would try removing that call to destroy().
See the BlackBerry docs example for this here
Ok so I finally got it running cheerfully. The problem was with the transport selection; even though this example delivered WAP2 (among others) as an available transport in my device, running the network diagnostics tool showed only BIS as available. It also gave me the connection parameters that I needed to append at the end of the URL (;deviceside=false;ConnectionUID=GPMDSEU01;ConnectionType=mds-public). The code ended up like this:
for (;;) {
try {
coords.refreshCoordinates();
this.defaultUrl();
this.setUrl(stringFuncs.replaceAll(this.getUrl(), "%latitude%", coords.getLatitude() + ""));
this.setUrl(stringFuncs.replaceAll(this.getUrl(), "%longitude%", coords.getLongitude() + ""));
cd = cf.getConnection(this.getUrl());
if (cd != null) {
try {
HttpConnection hc = (HttpConnection)cd.getConnection();
final int i = hc.getResponseCode();
hc.close();
} catch (Exception e) {
}
}
//dormir
Thread.sleep(15000);
} catch (Exception e) {
} finally {
//cerrar conexiones
//poner objetos a null
}
Thanks for your help #Nate, it's been very much appreciated.