App.OnResume error in Xamarin forms on Android and IOS devices - xamarin

We are using xamarin forms. After an Android or IOS device resumes from background, we are making a REST call in .net that is being triggered by a timer. The first attempt on IOS returns a "The Descriptor is not a socket" error and the Android returns a "Connection refused" error. The same code works fine in Windows. Future attempts (every few seconds) in all 3 platforms work fine. Has anyone seen this and have a fix?
Code
//app on resume event
protected async override void OnResume()
{
// Handle when your app resumes
if (MainPage is RootPage)
{
RootPage mainPage = MainPage as RootPage;
if (mainPage.Detail is NavigationPage)
{
NavigationPage nvPage = mainPage.Detail as NavigationPage;
if(nvPage.CurrentPage is ThingsPage)
{
ThingsPage thPage = nvPage.CurrentPage as ThingsPage;
thPage.TurnOnTimer();
}
}
}
}
//code on the page
public void TurnOnTimer()
{
if (viewModel != null)
{
viewModel.ContinueTimer = true;
viewModel.StartAnotherTimer();
}
}
//code in view model
public async void StartAnotherTimer()
{
while (ContinueTimer)
{
try
{
DevicesUpdate devicesUpdate = await DataSource.GetDevices(LocationID, ControllerID, lastDevicesUpdateReceivedAt);
}
catch (Exception ex)
{
}
// Update the UI (because of async/await magic, this is still in the UI thread!)
if (ContinueTimer)
{
await Task.Delay(TimeSpan.FromSeconds(3));
}
}
}
public static async Task<DevicesUpdate> GetDevices(Guid locationID, Guid controllerID, DateTime lastUpdateReceivedAt)
{
DevicesUpdate devicesUpdate = await GetLastUpdatedDevices(controllerID, lastUpdateReceivedAt);
}
//code in view model
public static async Task<DevicesUpdate> GetLastUpdatedDevices(Guid controllerID,
DateTime lastUpdate)
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
string url = string.Format("http://appname.azurewebsites.net/api/devices?controllerid={1}&lastUpdate={2}"
, Constants.WebServerURL, controllerID, lastUpdate);
System.Net.Http.HttpResponseMessage response = await client.GetAsync(new Uri(url));
string result = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
DevicesUpdate devices = JSONHelper.Deserialize<DevicesUpdate>(result);
return devices;
}
else
{
if (response.ReasonPhrase == "UserException")
{
throw new UserException(result);
}
else
{
//throw error because the response from rest api is not a success
throw new System.Net.Http.HttpRequestException(result);
}
}
}

You might have a few things happening here that's causing problems.
GetDevices doesn't return anything. (I hope you just left out the return for brevity sake)
You are never setting ContinueTimer to false.
What iOS version are you on? In later versions, you HAVE to use HTTPS or explicitly allow non-secure connections. This shouldn't be a problem because Azure has ssl.
If you plan on running this in the background, you need to register your app as a background process.
If you don't plan on running this in the background, you might have issues with previous attempts being ran (or still trying to execute, or just have failed) and then calling more.
What is the reason for calling the 3 second timer for the network calls? What if the call takes more than 3 seconds (then you are making duplicate calls even though the first might succeed).
If you want to make your network calls more robust, check out this Blog Post by Rob Gibbons about resilient network calls.
First thing I would do is remove it from the timer because it seems like the underlying sockets are having issues cross-thread.

Related

Xamarin InAppBilling showing ItemUnavailable for purchase on Android

I'm implementing the InAppBilling plugin in my Xamarin Forms 5 app for auto-renewing subscriptions.
I have the "subscriptions" set up on Google Play and they're active. When I ask for a list of subscription items, I get the list fine but when I try make a purchase, I get the following error that indicates the item is not available.
I'm running this on a real device connected to my laptop via USB. Any idea what I'm doing wrong?
Here's my purchase subscription method which is directly from documentation here:
public async Task<bool> Subscribe(string productId)
{
var billing = CrossInAppBilling.Current;
try
{
var connected = await billing.ConnectAsync();
if (!connected)
return false;
//check purchases
var purchase = await billing.PurchaseAsync(productId, ItemType.Subscription);
//possibility that a null came through.
if (purchase == null)
{
//did not purchase
return false;
}
else
{
//purchased!
if (Device.RuntimePlatform == Device.Android)
{
// Must call AcknowledgePurchaseAsync else the purchase will be refunded
//await billing.AcknowledgePurchaseAsync(purchase.PurchaseToken);
}
return true;
}
}
catch (InAppBillingPurchaseException purchaseEx)
{
//Billing Exception handle this based on the type
throw new Exception("Error: " + purchaseEx);
}
catch (Exception ex)
{
//Something else has gone wrong, log it
throw new Exception();
}
finally
{
await billing.DisconnectAsync();
}
}
As I mentioned before, I see the subscription items available and active on Google Play. I also made sure, I'm getting them from "Subscriptions" and NOT "In-App Products". I'm using the ID that I copy and paste from the "Product ID" column on Google Play Console -- see below:
Any idea what the issue here may be?

Fetching data from the internet after a page opens in Xamarin forms

I am having some performance issues with my code. I am working with basic MVVM in Xamarin forms project, and I want to fetch data from the internet when someone navigates to another page. Below is what I have done;
This is how I am navigating to another page via Command; (To be honest I don't really know if this method of navigation has some performance penalties)
if (Application.Current.MainPage.Navigation.NavigationStack.Last().GetType() != typeof(SubcategoryPage))
{
await Application.Current.MainPage.Navigation.PushAsync(new SubcategoryPage());
}
Here, I am passing Id of a particular category to get its corresponding subcategories
public SubcategoryPage(int id)
{
InitializeComponent();
this.BindingContext = new SubcategoryPageViewModel(id);
}
In the constructor of the SubcategoryPageViewModel, I used the Id to fetch the data online like so;
public SubcategoryPageViewModel(int id)
{
SubcategoryLoader(id);
}
Below is the method that is fetching the data from the internet through my DataService class. The code below work well in getting the data from the internet;
private async Task SubcategoryLoader(int id)
{
try
{
var subCategories = await SubcategoryDataService.GetSubcategories(id);
if (subCategories.code == 1) // StatusCode = Successful
{
SubCategories = subCategories.document;
}
else
{
await Application.Current.MainPage.DisplayAlert("Oops!","Something went wrong", "Ok");
}
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Oops!", ex.Message, "Ok");
}
}
Now my problem is that the SubcategoryPage doesn't open until the online service is over which leads to a serious lags. So what I want to have happen is to open the SubcategoryPage before the internet services happen.
Please any assistance is appreciated.
Move it to OnAppearing method .
Add a simple check .
protected override void OnAppearing()
{
base.OnAppearing();
if(BindingContext == null)
{
BindingContext = new SubcategoryPageViewModel(id);
}
}

IntentService in Xamarin PCL Solution

I am busy writing an application where the user needs to capture a lot of images and then they get packaged together with some text data and then they get uploaded to a local server. I want to implement the uploading on the Android platform through an Intent Service but I cannot find a good Xamarin Forms PCL example to show me how.
This is the method where I initialize the Intent to pass to the IntentService:
public async Task<bool> UploadAsync(Uri serviceAddress,
CaptureEntity capture,
List<ImageEntity> images)
{
try
{
Intent uploadIntent = new Intent();
uploadIntent.PutExtra("serviceAddress", serviceAddress.ToString());
uploadIntent.PutExtra("captureId", capture.WorkflowId.ToString());
StartService(uploadIntent);
return true;
}
catch (Exception exc)
{
App.logger.LogError(DateTime.Now, "Uploader", exc.ToString());
throw exc;
}
}
And this is the IntentService itself.
[Service]
public class ServiceIntent : IntentService
{
public ServiceIntent() : base("ServiceIntent")
{
}
//[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
return base.OnStartCommand(intent, flags, startId);
}
public override void OnCreate()
{
base.OnCreate();
}
protected override void OnHandleIntent(Intent intent)
{
Uri serviceAddress = new Uri(intent.GetStringExtra("serviceAddress"));
Guid captureId = Guid.Parse(intent.GetStringExtra("captureId"));
CaptureEntity capture = new DatabaseConnection_Android().CreateConnection().Query<CaptureEntity>("SELECT * FROM [CaptureEntity]").Single(c => c.WorkflowId == captureId);
var images = new DatabaseConnection_Android().CreateConnection().Query<ImageEntity>("SELECT * FROM [ImageEntity]").Where(i => i.CaptureEntityId == capture.Id);
try
{
MultipartFormDataContent content = new MultipartFormDataContent();
StringContent strContent = new StringContent(
capture.XmlData,
Encoding.UTF8,
"text/xml");
IImageHandler handler = new ImageHandler_Droid();
HttpRequestMessage request = new HttpRequestMessage();
request.Headers.Add("workflow", capture.WorkflowId.ToString());
request.Method = HttpMethod.Post;
request.RequestUri = serviceAddress;
foreach (var image in images)
{
byte[] imageByte = handler.ReadAllBytes(image.ImagePath);
ByteArrayContent byteContent = new ByteArrayContent(imageByte);
byteContent.Headers.Add("Content-Type", "image/jpeg");
content.Add(byteContent, "file", image.ImageName);
}
content.Add(strContent, "text/xml");
request.Content = content;
using (HttpClient client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(180);
var response = client.SendAsync(
request,
HttpCompletionOption.ResponseContentRead).Result;
var readResponse = response.Content.ReadAsStringAsync().Result;
if (readResponse == "File uploaded.")
MessagingCenter.Send<CaptureEntity, string>(
capture,
"Completed",
"Success");
else if (readResponse.Contains("An error has occurred."))
MessagingCenter.Send<CaptureEntity, string>(
capture,
"Uploader",
String.Format(CultureInfo.InvariantCulture,
"Failed: {0}",
readResponse));
else
MessagingCenter.Send<CaptureEntity, string>(
capture,
"Uploader",
String.Format(CultureInfo.InvariantCulture,
"Failed: {0}",
readResponse));
}
}
catch (WebException webExc)
{
MessagingCenter.Send<string, string>("Uploader", "Failed",
String.Format(CultureInfo.InvariantCulture,
"{0} upload failed.\n{1}",
capture.DisplayName,
webExc.Message));
}
catch (TimeoutException timeExc)
{
MessagingCenter.Send<string, string>("Uploader", "Failed",
String.Format(CultureInfo.InvariantCulture,
"{0} upload failed.\n{1}",
capture.DisplayName,
timeExc.Message));
}
catch (Exception exc)
{
MessagingCenter.Send<string, string>("Uploader", "Failed",
String.Format(CultureInfo.InvariantCulture,
"{0} upload failed.\n{1}",
capture.DisplayName,
exc.Message));
}
}
}
Can anyone tell me what I am doing wrong as I am getting the following error when I want to start the service:
Java.Lang.NullPointerException: Attempt to invoke virtual method 'android.content.ComponentName android.content.Context.startService(android.content.Intent)' on a null object reference
In your Intent declaration you need to tell the service you want to call
Something like this:
var uploadIntent = new Intent(this, typeof(ServiceIntent));
Note: this represents the Context.
Update:
As mentioned in the comments your interface implementation cannot derive from Activity class. In order to have access to the Context to be able to call the StartService method and also create your Intent you can make it in two ways:
Using the Xamarin.Forms.Forms.Context:
public async Task<bool> UploadAsync(Uri serviceAddress,
CaptureEntity capture,
List<ImageEntity> images)
{
try
{
var context = Xamarin.Forms.Forms.Context;
var uploadIntent = new Intent(context, typeof(ServiceIntent));
uploadIntent.PutExtra("serviceAddress", serviceAddress.ToString());
uploadIntent.PutExtra("captureId", capture.WorkflowId.ToString());
context.StartService(uploadIntent);
return true;
}
catch (Exception exc)
{
App.logger.LogError(DateTime.Now, "Uploader", exc.ToString());
throw exc;
}
}
If you are using latest versions of Xamarin.Forms this global context was deprecated and they suggest to you local context instead. You can still use it though but in future updates of XF your app might break.
using CurrentActivity plugin:
public async Task<bool> UploadAsync(Uri serviceAddress,
CaptureEntity capture,
List<ImageEntity> images)
{
try
{
var context = CrossCurrentActivity.Current.Activity;
var uploadIntent = new Intent(context, typeof(ServiceIntent));
uploadIntent.PutExtra("serviceAddress", serviceAddress.ToString());
uploadIntent.PutExtra("captureId", capture.WorkflowId.ToString());
context.StartService(uploadIntent);
return true;
}
catch (Exception exc)
{
App.logger.LogError(DateTime.Now, "Uploader", exc.ToString());
throw exc;
}
}
This plugin can be installed from nugget and the setup is very straight forward. Basically it gives you access to the current activity and you can use it as your context to call the IntentService
Hope this helps.-
Here is the IntentService.
IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
In Android, we usually use IntentService to do asynchronous operator. As we all know, thread is also used to do asynchronous operator. The difference between IntentService and Thread is IntentService is Service which belongs to Android Component. So, the priority of IntentService is higher than Thread.
For example, there is a ActivityA which has a IntentService, and there is a ActivityB which has a Thread, both IntentService and Thread are working, and both ActivityA and ActivityB are al background Activity. Now, if your phone's system doesn't have extra resources, your ActivityB will be killed firstly.
About the Exception:
Java.Lang.NullPointerException: Attempt to invoke virtual method 'android.content.ComponentName android.content.Context.startService(android.content.Intent)' on a null object reference
That means you should use android.content.Context to call the StartService method. In Android, there are three kinds of Context. Application, Activity and Service. So you can call the StartService method in these three classes directly. If you are not in these three classes, you need pass the Context to your class, and then use the Context to call StartService.
I added Activity for this class' inheritance.
If you do this, your class will be a Activity, and you need to register it in your manifiest, add layout for your class, and it should have the lifecycle, and etc. It will not be what you want to get class. In Android, Activity is a Component, not normal class, so you can't inherit it unless you want your class to be a Activity.
Demo:
I have made a demo for you,

How to check internet connection continuously in Xamarin.Android Native?

My application is completely based on internet and it does not work without it, when the internet is not available or it is slow my application is getting stopped unfortunately.
I tried to implement try, catch but it is not helping me out as it is not throwing any exception, then I thought that I have to check the internet connectivity continuously till the app is running and stop any activity from performing and set a popup to connect to the internet.
I am able to get the popup whenever I call a method which has the following code inside it,
ConnectivityManager connectivityManager = (ConnectivityManager)GetSystemService(ConnectivityService);
NetworkInfo networkInfo = connectivityManager.ActiveNetworkInfo;
if (networkInfo == null)
{
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.SetTitle("Network");
alert.SetMessage("Please turn of your Wifi or Mobile Data From Settings");
alert.SetPositiveButton("OK", (senderAlert, args) => {
Intent intent = new Intent(Android.Provider.Settings.ActionSettings);
StartActivity(intent);
});
alert.SetNegativeButton("Cancel", (senderAlert, args) => {
Toast.MakeText(this, "Cancelled!", ToastLength.Short).Show();
Finish();
});
Dialog dialog = alert.Create();
dialog.Show();
But I am unable to get the connection checked continuously, So Can some one Please help me to complete get this functionality in my application.
You need to use a BroadcastReceiver to monitor network changes.
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { "android.net.conn.CONNECTIVITY_CHANGE" })]
[Android.Runtime.Preserve(AllMembers = true)]
public class ConnectivityChangeBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action != ConnectivityManager.ConnectivityAction)
return;
//Check if is connected and raise a custom event or store
//the current in a static global variable
}
}
}
In Android 7.0, you need to remove IntentFilter from the class and register the receiver.
var receiver = new ConnectivityChangeBroadcastReceiver();
Application.Context.RegisterReceiver(receiver, new IntentFilter(ConnectivityManager.ConnectivityAction));
Another option is to use the ConnectivityPlugin https://github.com/jamesmontemagno/ConnectivityPlugin , which is easier to use.
CrossConnectivity.Current.ConnectivityChanged += HandleConnectivityChanged;
void HandleConnectivityChanged (object sender, ConnectivityChangedEventArgs e)
{
// You can check the network status in
//e.IsConnected
}
Note that The ACCESS_NETWORK_STATE and ACCESS_WIFI_STATE permissions are required.

SmtpClient.SendAsync blocking my ASP.NET MVC Request

I have a Action that sends a simple email:
[HttpPost, ActionName("Index")]
public ActionResult IndexPost(ContactForm contactForm)
{
if (ModelState.IsValid)
{
new EmailService().SendAsync(contactForm.Email, contactForm.Name, contactForm.Subject, contactForm.Body, true);
return RedirectToAction(MVC.Contact.Success());
}
return View(contactForm);
}
And a email service:
public void SendAsync(string fromEmail, string fromName, string subject, string body, bool isBodyHtml)
{
MailMessage mailMessage....
....
SmtpClient client = new SmtpClient(settingRepository.SmtpAddress, settingRepository.SmtpPort);
client.EnableSsl = settingRepository.SmtpSsl;
client.Credentials = new NetworkCredential(settingRepository.SmtpUserName, settingRepository.SmtpPassword);
client.SendCompleted += client_SendCompleted;
client.SendAsync(mailMessage, Tuple.Create(client, mailMessage));
}
private void client_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
Tuple<SmtpClient, MailMessage> data = (Tuple<SmtpClient, MailMessage>)e.UserState;
data.Item1.Dispose();
data.Item2.Dispose();
if (e.Error != null)
{
}
}
When I send a email, I am using Async method, then my method SendAsync return immediately, then RedirectToAction is called. But the response(in this case a redirect) isnĀ“t sent by ASP.NET until client_SendCompleted is completed.
Here's what I'm trying to understand:
When watching the execution in Visual Studio debugger, the SendAsync returns immediately (and RedirectToAction is called), but nothing happens in the browser until email is sent?
If i put a breakpoint inside client_SendCompleted, the client stay at loading.... until I hit F5 at debugger.
This is by design. ASP.NET will automatically wait for any outstanding async work to finish before finishing the request if the async work was kicked off in a way that calls into the underlying SynchronizationContext. This is to ensure that if your async operation tries to interact with the HttpContext, HttpResponse, etc. it will still be around.
If you want to do true fire & forget, you need to wrap your call in ThreadPool.QueueUserWorkItem. This will force it to run on a new thread pool thread without going through the SynchronizationContext, so the request will then happily return.
Note however, that if for any reason the app domain were to go down while your send was still in progress (e.g. if you changed the web.config file, dropped a new file into bin, the app pool recycled, etc.) your async send would be abruptly interrupted. If you care about that, take a look at Phil Haacks WebBackgrounder for ASP.NET, which let's you queue and run background work (like sending an email) in such a way that will ensure it gracefully finishes in the case the app domain shuts down.
This is an interesting one. I've reproduced the unexpected behaviour, but I can't explain it. I'll keep digging.
Anyway the solution seems to be to queue a background thread, which kind of defeats the purpose in using SendAsync. You end up with this:
MailMessage mailMessage = new MailMessage(...);
SmtpClient client = new SmtpClient(...);
client.SendCompleted += (s, e) =>
{
client.Dispose();
mailMessage.Dispose();
};
ThreadPool.QueueUserWorkItem(o =>
client.SendAsync(mailMessage, Tuple.Create(client, mailMessage)));
Which may as well become:
ThreadPool.QueueUserWorkItem(o => {
using (SmtpClient client = new SmtpClient(...))
{
using (MailMessage mailMessage = new MailMessage(...))
{
client.Send(mailMessage, Tuple.Create(client, mailMessage));
}
}
});
With .Net 4.5.2, you can do this with ActionMailer.Net:
var mailer = new MailController();
var msg = mailer.SomeMailAction(recipient);
var tcs = new TaskCompletionSource<MailMessage>();
mailer.OnMailSentCallback = tcs.SetResult;
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
msg.DeliverAsync();
await tcs.Task;
Trace.TraceInformation("Mail sent to " + recipient);
});
Please read this first: http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx
I sent the bug to Microsoft Connect https://connect.microsoft.com/VisualStudio/feedback/details/688210/smtpclient-sendasync-blocking-my-asp-net-mvc-request

Resources