Xamarin: open Google Maps App from a Android WebView App - google-maps

I have created a WebView App with VS2015 / Xamarin.
The WebView loads an external webiste (the pages are not included in the APP).
One of the website pages contains an html link of this kind:
<a href="geo:42.374260,-71.120824">
I would like to make sure Android opens Google Maps App when a user clicks on the link.
I have made use of ShouldOverrideUrlLoading in this way:
public class WebClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(WebView view, string url)
{
//Click on a georeferenced link
if (url.IndexOf("geo:")>-1)
{
var geoUri = Android.Net.Uri.Parse("geo:42.374260,-71.120824");
var mapIntent = new Intent(Intent.ActionView, geoUri);
StartActivity(mapIntent);
return true;
}
//Click on a generic link
view.LoadUrl(url);
return true;
}
}
Now, problem is, the StartActivity line returns an error: An object reference is required for the non-static field, method, or property 'ContextWrapper.StartActivity(Intent)'
Could you suggest which is the right syntax I should use to avoid this error ?

I solved the problem.
The right way of calling StartActivity is the following:
if (url != null && url.IndexOf("geo:")>-1)
{
var geoUri = Android.Net.Uri.Parse(url);
var mapIntent = new Intent(Intent.ActionView, geoUri);
view.Context.StartActivity(mapIntent);
return true;
}
An interesting thread on the topic is to be found here: How to handle geo: links in webview

Related

Verify an image exists at a URL when HEAD is not allowed

Using HttpClient in C#, I'm trying to verify that an image exists at a given URL without downloading the actual image. These images can come from any publicly accessible address. I've been using the HEAD HTTP verb which seems to work for many/most. Google drive images are proving difficult.
Given a public share link like so:
https://drive.google.com/file/d/1oCmOEJp0vk73uYhzDTr2QJeKZOkyIm6v/view?usp=sharing
I can happily use HEAD, get a 200 OK and it appears to be happy. But, that's not the image. It's a page where one can download the image.
With a bit of mucking around, you can change the URL to this to actually get at the image, which is what I really want to check:
https://drive.google.com/uc?export=download&id=1oCmOEJp0vk73uYhzDTr2QJeKZOkyIm6v
But, hitting that URL with HEAD results in a 405 MethodNotAllowed
Luckily, if the URL truly doesn't exist, you get back a 404 NotFound
So I'm stuck at the 405. What is my next step (NOT using Google APIs) when HEAD is not allowed? I can't assume it's a valid image if it simply doesn't 404. I check the Content-type to verify it's an image, which has issues outside the scope of this question.
HttpClient allows us to issue an http request where you can specify that you are interested about only the headers.
The trick is to pass an HttpCompletionOption enum value to the SendAsync or any other {HttpVerb}Async method:
| Enum name | Value | Description |
|---------------------|-------|---------------------------------------------------------------------------------------------------------------------|
| ResponseContentRead | 0 | The operation should complete after reading the entire response including the content. |
| ResponseHeadersRead | 1 | The operation should complete as soon as a response is available and headers are read. The content is not read yet. |
await client.GetAsync(targetUrlWhichDoesNotSupportHead, HttpCompletionOption.ResponseHeadersRead);
Here is an in-depth article that details how does this enum changes the behavior and performance of the HttpClient.
The related source code fragments:
in case of .NET Framework
in case of .NET Core
Brilliant, Peter! Thank you.
Here's my full method for anyone who may find it useful:
public async Task<bool> ImageExists(string urlOrPath)
{
try
{
var uri = new Uri(urlOrPath);
if (uri.IsFile)
{
if (File.Exists(urlOrPath)) return true;
_logger.LogError($"Cannot find image: [{urlOrPath}]");
return false;
}
using (var result = await Get(uri))
{
if (result.StatusCode == HttpStatusCode.NotFound)
{
_logger.LogError($"Cannot find image: [{urlOrPath}]");
return false;
}
if ((int)result.StatusCode >= 400)
{
_logger.LogError($"Error: {result.ReasonPhrase}. Image: [{urlOrPath}]");
return false;
}
if (result.Content.Headers.ContentType == null)
{
_logger.LogError($"No 'ContentType' header returned. Cannot validate image:[{urlOrPath}]");
return false;
}
if(new[] { "image", "binary"}.All(v => !result.Content.Headers.ContentType.MediaType.SafeTrim().Contains(v)))
{
_logger.LogError($"'ContentType' {result.Content.Headers.ContentType.MediaType} is not an image. The Url may point to an HTML download page instead of an actual image:[{urlOrPath}]");
return false;
}
var validTypes = new[] { "jpg", "jpeg", "gif", "png", "bmp", "binary" };
if(validTypes.All(v => !result.Content.Headers.ContentType.MediaType.SafeTrim().Contains(v)))
{
_logger.LogError($"'ContentType' {result.Content.Headers.ContentType.MediaType} is not a valid image. Only [{string.Join(", ", validTypes)}] accepted. Image:[{urlOrPath}]");
return false;
}
return true;
}
}
catch (Exception e)
{
_logger.LogError($"There was a problem checking the image: [{urlOrPath}] is not valid. Error: {e.Message}");
return false;
}
}
private async Task<HttpResponseMessage> Get(Uri uri)
{
var response = await _httpCli.SendAsync(new HttpRequestMessage(HttpMethod.Head, uri));
if (response.StatusCode != HttpStatusCode.MethodNotAllowed) return response;
return await _httpCli.SendAsync(new HttpRequestMessage() { RequestUri = uri }, HttpCompletionOption.ResponseHeadersRead);
}
Edit: added a Get() method which still uses HEAD and only uses ResponseHeadersRead if it encounters MethodNotAllowed. Using a live scenario I found it was much quicker. Not sure why. YMMV

Passing data to SignalR Hub using Post method to Web Api returned: The remote server returned an error: (404) Not Found

Recently, I just started to learn on SignalR and I had been testing on one project that I found on GitHub. However I did stuck when trying to Post data to Web api part.
I just get everything done yet I cannot really make this project to work somehow. This is basically the program for the project. It is a console app and did send the data(Json) to Web Api
// Get the stuff we need to send
GetMetrics(out cpuTime, out memUsage, out totalMemory);
// Send the data
var postData = new
{
MachineName = System.Environment.MachineName,
Processor = cpuTime,
MemUsage = memUsage,
TotalMemory = totalMemory
};
var json = JsonConvert.SerializeObject(postData);
// Post the data to the server http://localhost:80/api/cpuinfo
var serverUrl = new Uri(ConfigurationManager.AppSettings["ServerUrl"]);
var client = new WebClient();
client.Headers.Add("Content-Type", "application/json");
client.UploadString(serverUrl, json);
Moving to web part. I did have the Asp.net MVC and did create the RouteConfig inside the App_Start to route HTTP request to controller.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
And this is the controller class.
public class CpuInfoController : ApiController
{
public void Post(CpuInfoPostData cpuInfo)
{
var context = GlobalHost.ConnectionManager.GetHubContext<CpuInfo>();
context.Clients.All.cpuInfoMessage(cpuInfo.MachineName, cpuInfo.Processor, cpuInfo.MemUsage, cpuInfo.TotalMemory);
}
}
I also had it registered inside Global.asax as below
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
After done all this, I still cant get this done and my console application pop up some errors as in the image here. It seems like the api/cpuinfo was not found.
Please advice me if anything that I had done wrong here.
The full version of this project can be found here.
You have to modify the File App.config in "CpuInfoClient" project. (the value of the Key)
Use "http" instead of "https"
Change the port number to the actual port number (instead of 44300), that uses the web application after starting. The exact port for the substitution you can see , when the web app starts in IE or Firefox. The port is also in "WcfCpuApp -> Properties -> Web -> Project-URL
Be sure that your web application is running, when you start "CpuInfoClient"

ProgressEvent.load is always the same as ProgressEvent.Total which causes the progress to fake

I'm trying to implement progress bar on a website.
The Problem:
ProgressEvent.load is always the same as ProgressEvent.Total which prevent the progress to show the real state of the upload. At the first second the xhr request does sent it looks like it finished but actually the server is still getting parts of the file.
JS:
My js code(the part of the progress) looks like that:
xhr.upload.onprogress = function (event) {
var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
that._onProgressItem(item, progress);
};
the property lengthComputable is true.
the event.loaded is 4354707 as the event.total which is 4354707.
C# Server Side:
public async Task<FileResultViewModel> Upload(string type)
{
string ServerUploadFoler = "...";
// Verify that this is an HTML Form file upload request
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.UnsupportedMediaType));
}
// Create a stream provider for setting up output streams
var streamProvider = new MultipartFormDataStreamProvider(ServerUploadFolder);
// Read the MIME multipart asynchronously content using the stream provider we just created.
await Request.Content.ReadAsMultipartAsync(streamProvider);
string guid = String.Empty;
if (serverUploadMoveFolder != ServerUploadFolder)
{
foreach (MultipartFileData fileData in streamProvider.FileData)
{
guid = Guid.NewGuid().ToString();
string newFileName = serverUploadMoveFolder + guid + GetExtension(uploadType);
FileInfo fi = new FileInfo(fileData.LocalFileName);
fi.MoveTo(newFileName);
}
}
// Create response
return new FileResultViewModel
{
FileName = guid
};
}
Chrome debug after 1 second of upload with a file of 4.2MB:
In fiddler after the request has completed:
My questions are:
How does the browser knows the loaded size? How does it split the file to parts and based on what params?
How do the xhr.upload.onprogress function event get updated with the progress? Does it the server which report about his progress and if it is so where is it on the code because I didn't handle it.
Why doesn't the loaded property show the real size of part?

How to handle user enable location & return to app

My app need to access location service, for that I am asking user to whether to enable location, if user says yes, then I am opening location settings. Upto this is working. But how to detect/handle when user coming from location page to my application page.
Here is my code
private async Task<Geoposition> getCurrentLocation()
{
Geoposition position = null;
Geolocator locator = new Geolocator() { DesiredAccuracyInMeters = 10 };
var flag = true;
try
{
position = await locator.GetGeopositionAsync(TimeSpan.FromHours(1), TimeSpan.FromSeconds(30));
flag = false;
}
catch (Exception uae)
{
}
if (flag)
{
await ShowLocationPage();
}
}
getCurrentLocation method called when page is loaded. If it didnt get user location then ShowLocationPage method get called.
private static async Task ShowLocationPage()
{
ContentDialog cd = new ContentDialog(){
Content = "Application want to access your location. Would you like to turn on Location Services?",
PrimaryButtonText = "Yes",
SecondaryButtonText = "No"
};
var result = await cd.ShowAsync(); if (result == ContentDialogResult.Primary)
{
var x = await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings-location:"));
}
}
My problem is how to detect that user return from location page, so I can check for geo-information again.
Best way to get this going is to check if your app get's focus again ( or is resumed ).
With the new wp RT this has changed a bit against wp SL.
A bit to long to explain here in StackO answer, but a very compleet explanation is up here http://www.interact-sw.co.uk/iangblog/2013/07/24/return-xaml-store-app
Some disscussion about it was up on twitter few days back: https://twitter.com/rschu/status/498836593269305344

background Agent works fine in local environment but failed after app submission to app store

I have an wp8 app using PeriodicTask background Agent.
The task update the information of multiple live tiles,
using POST Client to get title and image url from my server to update the live tile.
Background agent works just fine in debugging and releasing mode. When the .xap file was deployed into my device using XAPDeployement tool, the background Agent also works perfectly.
However, it won't work after submitted to wp app store, no matter it's beta version or not.
If the app is downloaded from store, the background agent has never worked, and it is blocked by system after a few minutes.
How come it goes wrong since the XAP files are the same?
part of code:
public static Task<string> jsonPostClientTask(Dictionary<string, object> parameters, string url)
{
var results = new TaskCompletionSource<string>();
PostClient proxy = new PostClient(parameters);
try
{
proxy.DownloadStringCompleted += (sender, e) =>
{
if (e.Error == null)
{
string response = e.Result.ToString();
results.TrySetResult(response);
}
else
{
results.TrySetResult("");
results.TrySetException(e.Error);
}
};
proxy.DownloadStringAsync(new Uri(url));
}
catch
{
results.TrySetResult("");
}
return results.Task;
}
ScheduledAgent class:
protected override void OnInvoke(ScheduledTask task)
{
foreach (var tile in tileList)
{
string dataString = jsonPostClientTask(parameters, url);
//update tile in used
FlipTileData tileData = new FlipTileData()
{
BackContent = "string content",
WideBackContent = "string back content",
BackBackgroundImage = new Uri("http://xxxx.xxx/xxx.png", UriKind.RelativeOrAbsolute),
WideBackBackgroundImage = new Uri("http://xxxx.xxx/xxx.png", UriKind.RelativeOrAbsolute),
};
ShellTile primaryTile = ShellTile.ActiveTiles.First();
if (primaryTile != null)
primaryTile.Update(tileData);
}
}