Unable to resume activity when getting users response for permission - xamarin.android

I'm trying to get user's permission to access their GPS. I'm not sure whether I have used it correctly because when i visit to that screen I get a crash with the error:
java.lang.RuntimeException: Unable to resume activity {com.SuppShoesTest.SuppShoesTest/md5056fe371b3902b2cc984ad5dda4f920b.gMaps}: android.util.SuperNotCalledException: Activity {com.SuppShoesTest.SuppShoesTest/md5056fe371b3902b2cc984ad5dda4f920b.gMaps} did not call through to super.onResume()
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3103)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3134)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2481)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.app.ActivityThread.-wrap11(ActivityThread.java)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.os.Handler.dispatchMessage(Handler.java:102)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.os.Looper.loop(Looper.java:148)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.app.ActivityThread.main(ActivityThread.java:5417)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at java.lang.reflect.Method.invoke(Native Method)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
01-18 23:57:20.419 E/AndroidRuntime( 2712): Caused by: android.util.SuperNotCalledException: Activity {com.SuppShoesTest.SuppShoesTest/md5056fe371b3902b2cc984ad5dda4f920b.gMaps} did not call through to super.onResume()
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.app.Activity.performResume(Activity.java:6314)
01-18 23:57:20.419 E/AndroidRuntime( 2712): at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3092)
Here is my code
{
private GoogleMap maps;
string dman;
string date;
string time;
public static readonly EndpointAddress EndPoint = new EndpointAddress("http://192.168.1.3:9608/SuppShoes.svc");
private SuppShoesClient _client;
ArrayList postal;
double mylat;
double mylong;
Location _currentLocation;
LocationManager _locationManager;
LinearLayout layout;
string _locationProvider;
PolylineOptions rect = new PolylineOptions();
readonly string[] PermissionsLocation =
{
Manifest.Permission.AccessCoarseLocation,
Manifest.Permission.AccessFineLocation
};
const int RequestLocationId = 0;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.gMap);
layout = (LinearLayout)FindViewById(Resource.Id.layout);
InitializeHelloWorldServiceClient();
dman = Intent.GetStringExtra("Dman");
date = Intent.GetStringExtra("date");
time = Intent.GetStringExtra("Time");
InitializeLocationManager();
SetUpMap();
// Create your application here
}
private void InitializeHelloWorldServiceClient()
{
BasicHttpBinding binding = CreateBasicHttp();
_client = new SuppShoesClient(binding, EndPoint);
}
private static BasicHttpBinding CreateBasicHttp()
{
BasicHttpBinding binding = new BasicHttpBinding
{
Name = "basicHttpBinding",
MaxBufferSize = 2147483647,
MaxReceivedMessageSize = 2147483647
};
TimeSpan timeout = new TimeSpan(0, 0, 30);
binding.SendTimeout = timeout;
binding.OpenTimeout = timeout;
binding.ReceiveTimeout = timeout;
return binding;
}
public async void OnLocationChanged(Location location)
{
_currentLocation = location;
if (_currentLocation == null)
{
}
else
{
AddMarker();
}
}
public void OnProviderDisabled(string provider) { }
public void OnProviderEnabled(string provider) { }
public void OnStatusChanged(string provider, Availability status, Bundle extras) { }
protected override void OnResume()
{
const string permission = Manifest.Permission.AccessFineLocation;
if (CheckSelfPermission(permission) == (int)Permission.Granted)
{
base.OnResume();
_locationManager.RequestLocationUpdates(_locationProvider, 0, 0, this);
}
if (ShouldShowRequestPermissionRationale(permission))
{
//Explain to the user why we need to read the contacts
Android.Support.Design.Widget.Snackbar.Make(layout,"Location access is required to show coffee shops nearby.", Android.Support.Design.Widget.Snackbar.LengthIndefinite)
.SetAction("OK", v => RequestPermissions(PermissionsLocation, RequestLocationId))
.Show();
return;
}
//Finally request permissions with the list of permissions and Id
RequestPermissions(PermissionsLocation, RequestLocationId);
}
public override async void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
switch (requestCode)
{
case RequestLocationId:
{
if (grantResults[0] == Permission.Granted)
{
//Permission granted
var snack = Android.Support.Design.Widget.Snackbar.Make(layout, "Location permission is available, getting lat/long.", Android.Support.Design.Widget.Snackbar.LengthShort);
snack.Show();
base.OnResume();
}
else
{
//Permission Denied :(
//Disabling location functionality
var snack = Android.Support.Design.Widget.Snackbar.Make(layout, "Location permission is denied.", Android.Support.Design.Widget.Snackbar.LengthShort);
snack.Show();
}
}
break;
}
}
protected override void OnPause()
{
base.OnPause();
_locationManager.RemoveUpdates(this);
}
void InitializeLocationManager()
{
_locationManager = (LocationManager)GetSystemService(LocationService);
Criteria criteriaForLocationService = new Criteria
{
Accuracy = Accuracy.Fine
};
IList<string> acceptableLocationProviders = _locationManager.GetProviders(criteriaForLocationService, true);
if (acceptableLocationProviders.Any())
{
_locationProvider = acceptableLocationProviders.First();
}
else
{
_locationProvider = string.Empty;
}
}
void AddMarker()
{
RunOnUiThread(() =>
{
LatLng current = new LatLng(_currentLocation.Latitude, _currentLocation.Longitude);
MarkerOptions asd = new MarkerOptions();
asd.SetPosition(current);
asd.InvokeIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.green));
maps.AddMarker(asd);
rect.Add(current);
});
}
private void SetUpMap()
{
if(maps==null)
{
FragmentManager.FindFragmentById<MapFragment>(Resource.Id.googlemap).GetMapAsync(this);
}
}
public void OnMapReady(GoogleMap googleMap)
{
this.maps = googleMap;
LatLng latlng = new LatLng(Convert.ToDouble(1.3521), Convert.ToDouble(103.8198));
CameraUpdate camera = CameraUpdateFactory.NewLatLngZoom(latlng,10);
maps.MoveCamera(camera);
_client.getPostalAsync(dman, date, time);
_client.getPostalCompleted += _client_getPostalCompleted;
}
private void _client_getPostalCompleted(object sender, getPostalCompletedEventArgs e)
{
RunOnUiThread(() =>
{
foreach (var item in e.Result)
{
LatLng latlng = new LatLng(item.lat1, item.longi2);
MarkerOptions options = new MarkerOptions()
.SetPosition(latlng)
.SetTitle(item.companyname2)
.SetSnippet(item.DevID.ToString());
maps.AddMarker(options);
rect.Add(latlng);
Polyline polyline = maps.AddPolyline(rect);
}
});
}
}

For Xamarin forms, base.OnResume() need to be called to avoid this exception. As always, this need to be at the top of overridden OnResume() method.

As clear by your Exception Caused by: android.util.SuperNotCalledException, you must add super.onResume() to perform resume life cycle because your are #Overriding the method.
Call super inside onResume();:
super.onResume();

Related

Swagger 2 Feign client code oAuth flow throwing error url values must be not be absolute

I exposed Rest APIs, and I generated client code using Swagger 2 Java language with Feign library. The code gen generated the below OAuth RequestInterceptor. I am getting the below error when I use the oAuth as auth.
Error
feign.RetryableException: url values must be not be absolute.
at com.sam.feign.auth.OAuth.updateAccessToken(OAuth.java:95)
at com.sam.feign.auth.OAuth.apply(OAuth.java:83)
at feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:161)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:110)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
at com.sun.proxy.$Proxy9.getUser(Unknown Source)
at com.sam.feign.clients.UserApiTest.getUserTest(UserApiTest.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: java.lang.IllegalArgumentException: url values must be not be absolute.
at feign.RequestTemplate.uri(RequestTemplate.java:434)
at feign.RequestTemplate.uri(RequestTemplate.java:421)
at feign.RequestTemplate.append(RequestTemplate.java:388)
at com.sam.feign.auth.OAuth$OAuthFeignClient.execute(OAuth.java:163)
at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:65)
at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:55)
at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:71)
at com.sam.feign.auth.OAuth.updateAccessToken(OAuth.java:93)
... 34 more
Swagger Generated oAuth supporting file
public class OAuth implements RequestInterceptor {
static final int MILLIS_PER_SECOND = 1000;
public interface AccessTokenListener {
void notify(BasicOAuthToken token);
}
private volatile String accessToken;
private Long expirationTimeMillis;
private OAuthClient oauthClient;
private TokenRequestBuilder tokenRequestBuilder;
private AuthenticationRequestBuilder authenticationRequestBuilder;
private AccessTokenListener accessTokenListener;
public OAuth(Client client, TokenRequestBuilder requestBuilder) {
this.oauthClient = new OAuthClient(new OAuthFeignClient(client));
this.tokenRequestBuilder = requestBuilder;
}
public OAuth(Client client, OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
this(client, OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes));
switch(flow) {
case accessCode:
case implicit:
tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE);
break;
case password:
tokenRequestBuilder.setGrantType(GrantType.PASSWORD);
break;
case application:
tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS);
break;
default:
break;
}
authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl);
}
public OAuth(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
this(new Client.Default(null, null), flow, authorizationUrl, tokenUrl, scopes);
}
#Override
public void apply(RequestTemplate template) {
// If the request already have an authorization (eg. Basic auth), do nothing
if (template.headers().containsKey("Authorization")) {
return;
}
// If first time, get the token
if (expirationTimeMillis == null || System.currentTimeMillis() >= expirationTimeMillis) {
updateAccessToken(template);
}
if (getAccessToken() != null) {
template.header("Authorization", "Bearer " + getAccessToken());
}
}
public synchronized void updateAccessToken(RequestTemplate template) {
OAuthJSONAccessTokenResponse accessTokenResponse;
try {
accessTokenResponse = oauthClient.accessToken(tokenRequestBuilder.buildBodyMessage());
} catch (Exception e) {
throw new RetryableException(400, e.getMessage(), template.request().httpMethod(), e, null, template.request());
}
if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) {
setAccessToken(accessTokenResponse.getAccessToken(), accessTokenResponse.getExpiresIn());
if (accessTokenListener != null) {
accessTokenListener.notify((BasicOAuthToken) accessTokenResponse.getOAuthToken());
}
}
}
public synchronized void registerAccessTokenListener(AccessTokenListener accessTokenListener) {
this.accessTokenListener = accessTokenListener;
}
public synchronized String getAccessToken() {
return accessToken;
}
public synchronized void setAccessToken(String accessToken, Long expiresIn) {
this.accessToken = accessToken;
this.expirationTimeMillis = System.currentTimeMillis() + expiresIn * MILLIS_PER_SECOND;
}
public TokenRequestBuilder getTokenRequestBuilder() {
return tokenRequestBuilder;
}
public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) {
this.tokenRequestBuilder = tokenRequestBuilder;
}
public AuthenticationRequestBuilder getAuthenticationRequestBuilder() {
return authenticationRequestBuilder;
}
public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) {
this.authenticationRequestBuilder = authenticationRequestBuilder;
}
public OAuthClient getOauthClient() {
return oauthClient;
}
public void setOauthClient(OAuthClient oauthClient) {
this.oauthClient = oauthClient;
}
public void setOauthClient(Client client) {
this.oauthClient = new OAuthClient( new OAuthFeignClient(client));
}
public static class OAuthFeignClient implements HttpClient {
private Client client;
public OAuthFeignClient() {
this.client = new Client.Default(null, null);
}
public OAuthFeignClient(Client client) {
this.client = client;
}
public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
String requestMethod, Class<T> responseClass)
throws OAuthSystemException, OAuthProblemException {
RequestTemplate req = new RequestTemplate()
.append(request.getLocationUri())
.method(requestMethod)
.body(request.getBody());
for (Entry<String, String> entry : headers.entrySet()) {
req.header(entry.getKey(), entry.getValue());
}
Response feignResponse;
String body = "";
try {
feignResponse = client.execute(req.request(), new Options());
body = Util.toString(feignResponse.body().asReader());
} catch (IOException e) {
throw new OAuthSystemException(e);
}
String contentType = null;
Collection<String> contentTypeHeader = feignResponse.headers().get("Content-Type");
if(contentTypeHeader != null) {
contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";");
}
return OAuthClientResponseFactory.createCustomResponse(
body,
contentType,
feignResponse.status(),
responseClass
);
}
public void shutdown() {
// Nothing to do here
}
}
}
ApiClient.java have the below absolute URL which configured in swagger spec
public ApiClient() {
objectMapper = createObjectMapper();
apiAuthorizations = new LinkedHashMap<String, RequestInterceptor>();
feignBuilder = Feign.builder()
.encoder(new FormEncoder(new JacksonEncoder(objectMapper)))
.decoder(new JacksonDecoder(objectMapper))
.logger(new Slf4jLogger());
}
public ApiClient(String[] authNames) {
this();
for(String authName : authNames) {
RequestInterceptor auth = null;
if ("client-credentils-oauth2".equals(authName)) {
auth = new OAuth(OAuthFlow.application, "", "http://localhost:8080/app/oauth/token", "user.create");
} else if ("password-oauth2".equals(authName)) {
auth = new OAuth(OAuthFlow.password, "", "http://localhost:8080/app/oauth/token", "openid");
} else {
throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names");
}
addAuthorization(authName, auth);
}
}
Used the below dependencies
swagger-codegen-maven-plugin v2.4.28
feign-version 11.6
feign-form-version 3.8.0
oltu-version 1.0.1
Java 8
I am invoking the client by using below code
UserApi api = new ApiClient("client-credentils-oauth2","admin", "admin", null, null).buildClient(UserApi.class);
api.getUser(login, tenant)
I made the few changes in the generated oAuth.java file to make it work. Expecting the client generated code should work without making any manual changes.
public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
String requestMethod, Class<T> responseClass)
throws OAuthSystemException, OAuthProblemException {
// Added the below 3 lines
URI targetUri = URI.create(uri);
String target = targetUri.getScheme() + "://" + targetUri.getAuthority() ;
String path = targetUri.getPath();
RequestTemplate req = new RequestTemplate()
.uri(path)
.method(requestMethod)
.body(request.getBody())
.target(target); // Added this line
for (Entry<String, String> entry : headers.entrySet()) {
req.header(entry.getKey(), entry.getValue());
}
req = req.resolve(new HashMap<String, Object>()); // Added this line
Response feignResponse;
String body = "";
try {
feignResponse = client.execute(req.request(), new Options());
body = Util.toString(feignResponse.body().asReader());
} catch (IOException e) {
throw new OAuthSystemException(e);
}
String contentType = null;
Collection<String> contentTypeHeader = feignResponse.headers().get("Content-Type");
if(contentTypeHeader != null) {
contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";");
}
return OAuthClientResponseFactory.createCustomResponse(
body,
contentType,
feignResponse.status(),
responseClass
);
}
Appreciate if someone can assist me with this issue

How to inject cancellation token in a HostedService timer with Simple Injector

I'm trying to create a console application that run ahosted service with Simple Injector and I looked to the example at generichostintegration.
Now I would like to change IProcessor.DoSomeWork to be an async function with a cancellation token as parameter so DoSomeWork can be canceled:
public async void DoSomeWork(CancellationToken cancellationToken)
{
await Task.Delay(_settings.Delay, cancellationToken);
}
How can i inject the cancellation token from HostedService
private void DoWork()
{
try
{
using (AsyncScopedLifestyle.BeginScope(this.container))
{
var service = this.container.GetInstance<TService>();
this.settings.Action(service);
}
}
catch (Exception ex)
{
this.logger.LogError(ex, ex.Message);
}
}
and configurate in the right way the container
container.RegisterInstance(new TimedHostedService<IProcessor>.Settings(
interval: TimeSpan.FromSeconds(10),
action: processor => processor.DoSomeWork()));
I'm a bit stack with that. Maybe i'm thinking wrong?
*** Updated ***
This is what I did in the end. I kept it as simple as possible.
class Program
{
public static async Task Main(string[] args)
{
var container = new Container();
IHost host = CreateHostBuilder(args, container)
.Build()
.UseSimpleInjector(container);
ConfigureContainer(container);
await host.RunAsync();
}
private static void ConfigureContainer(Container container)
{
container.Register<IWorkScheduler, WorkScheduler>(Lifestyle.Singleton);
// Sets the schedule timer interval
container.RegisterInstance(new WorkSchedulerSettings(TimeSpan.FromSeconds(1)));
container.Register<DoSomethingWorker>();
container.RegisterInstance(new DoSomethingSettings(new TimeSpan(0, 0, 5)));
container.Register<DoSomethingElseWorker>();
container.RegisterInstance(new DoSomethingElseSettings(new TimeSpan(0, 0, 10)));
container.Verify();
}
public static IHostBuilder CreateHostBuilder(string[] args, Container container) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddSimpleInjector(container, options =>
{
// Registers the hosted service as singleton in Simple Injector
// and hooks it onto the .NET Core Generic Host pipeline.
options.AddHostedService<BackgroundHostedService>();
services.AddLogging();
});
})
.UseConsoleLifetime();
}
the hosted service
public class BackgroundHostedService
: BackgroundService
{
private readonly IWorkScheduler _scheduler;
private readonly Container _container;
private readonly ILogger _logger;
public BackgroundHostedService(IWorkScheduler scheduler, Container container, ILogger<BackgroundHostedService> logger)
{
_scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler));
_container = container ?? throw new ArgumentNullException(nameof(container));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public override Task StartAsync(CancellationToken cancellationToken)
{
LoadWorkers();
return base.StartAsync(cancellationToken);
}
protected override Task ExecuteAsync(CancellationToken cancellationToken)
{
try
{
_scheduler.Start();
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
return Task.CompletedTask;
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
await _scheduler.Stop();
}
private void LoadWorkers()
{
// Hook up triggers and specify span period and if they have to run once at time.
WorkTrigger trigger1 = new WorkTrigger(_container.GetInstance<DoSomethingWorker>(), new TimeSpan(0, 0, 2), false);
_scheduler.AddTrigger(trigger1);
WorkTrigger trigger2 = new WorkTrigger(_container.GetInstance<DoSomethingElseWorker>(), new TimeSpan(0, 0, 5), true);
_scheduler.AddTrigger(trigger2);
}
public override void Dispose()
{
_scheduler.Dispose();
base.Dispose();
}
the "pseudo" scheduler
public interface IWorkScheduler : IDisposable
{
void Start();
Task Stop();
void AddTrigger(WorkTrigger trigger);
}
public class WorkSchedulerSettings
{
public readonly TimeSpan Interval;
public WorkSchedulerSettings(TimeSpan interval)
{
Interval = interval;
}
}
public class WorkScheduler
: IWorkScheduler, IDisposable
{
private readonly Timer _timer;
private readonly WorkSchedulerSettings _settings;
private readonly ILogger<WorkScheduler> _logger;
private readonly List<Task> _tasks;
private readonly List<WorkTrigger> _triggers;
private readonly CancellationTokenSource _cancTokenSource;
public WorkScheduler(WorkSchedulerSettings settings, ILogger<WorkScheduler> logger)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timer = new Timer(callback: _ => DoWork());
_tasks = new List<Task>();
_triggers = new List<WorkTrigger>();
_cancTokenSource = new CancellationTokenSource();
}
public void Start()
{
_logger.LogInformation("Scheduler started");
_timer.Change(dueTime: TimeSpan.Zero, period: _settings.Interval);
}
public async Task Stop()
{
_timer.Change(Timeout.Infinite, Timeout.Infinite);
_cancTokenSource.Cancel();
await Task.WhenAll(_tasks);
_tasks.Clear();
_logger.LogInformation("Scheduler stopped");
}
public void AddTrigger(WorkTrigger trigger)
{
if (trigger == null) throw new ArgumentNullException(nameof(trigger));
_triggers.Add(trigger);
}
private void DoWork()
{
foreach (var trigger in _triggers)
{
if (trigger.CanExecute(DateTime.Now))
{
var task = trigger
.Execute(_cancTokenSource.Token)
.ContinueWith(x => HandleError(x));
_tasks.Add(task);
}
}
_tasks.RemoveAll(x => x.IsCompleted);
}
private void HandleError(Task task)
{
if (task.IsFaulted)
_logger.LogError(task.Exception.Message);
}
public void Dispose()
{
_timer?.Dispose();
_cancTokenSource?.Dispose();
}
}

NSUrlSession photo upload task to AzureFunction inside BGTaskScheduler does not get executed when iOS charger cable is unplugged on iOS 14.4

We are working on a Xamarin Forms app that is supposed to upload photos to API in the background. The app is being custom-made for a client by their request, so they will set their phones to whatever permissions need to be set.
Below works fine if the charging cable is plugged in.
I am using BGTaskScheduler (iOS13+) and queuing both types of tasks (BGProcessingTaskRequest and BGAppRefreshTaskRequest) so that if the cable plugged in it would fire off BGProcessingTaskRequest and if not it would wait for BGAppRefreshTaskRequest to get its processing time.
I have added RefreshTaskId and UploadTaskId to Info.plist
AppDelegate.cs in iOS project looks following
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
BGTaskScheduler.Shared.Register(UploadTaskId, null, task => HandleUpload(task as BGProcessingTask));
BGTaskScheduler.Shared.Register(RefreshTaskId, null, task => HandleAppRefresh(task as BGAppRefreshTask));
return base.FinishedLaunching(app, options);
}
public override void HandleEventsForBackgroundUrl(UIApplication application, string sessionIdentifier, Action completionHandler)
{
Console.WriteLine("HandleEventsForBackgroundUrl");
BackgroundSessionCompletionHandler = completionHandler;
}
public override void OnActivated(UIApplication application)
{
Console.WriteLine("OnActivated");
}
public override void OnResignActivation(UIApplication application)
{
Console.WriteLine("OnResignActivation");
}
private void HandleAppRefresh(BGAppRefreshTask task)
{
HandleUpload(task);
}
public override void DidEnterBackground(UIApplication application)
{
ScheduleUpload();
}
private void HandleUpload(BGTask task)
{
var uploadService = new UploadService();
uploadService.EnqueueUpload();
task.SetTaskCompleted(true);
}
private void ScheduleUpload()
{
var upload = new BGProcessingTaskRequest(UploadTaskId)
{
RequiresNetworkConnectivity = true,
RequiresExternalPower = false
};
BGTaskScheduler.Shared.Submit(upload, out NSError error);
var refresh = new BGAppRefreshTaskRequest(RefreshTaskId);
BGTaskScheduler.Shared.Submit(refresh, out NSError refreshError);
if (error != null)
Console.WriteLine($"Could not schedule BGProcessingTask: {error}");
if (refreshError != null)
Console.WriteLine($"Could not schedule BGAppRefreshTask: {refreshError}");
}
The mechanism that does the upload UploadService is using NSUrlSession, it also writes a temporary file to use CreateUploadTask(request, NSUrl.FromFilename(tempFileName)) that is supposed to work in the background, whole mechanism looks following:
public NSUrlSession uploadSession;
public async void EnqueueUpload()
{
var accountsTask = await App.PCA.GetAccountsAsync();
var authResult = await App.PCA.AcquireTokenSilent(App.Scopes, accountsTask.First())
.ExecuteAsync();
if (uploadSession == null)
uploadSession = InitBackgroundSession(authResult.AccessToken);
var datastore = DependencyService.Get<IDataStore<Upload>>();
var uploads = await datastore.GetUnuploaded();
foreach (var unUploaded in uploads)
{
try
{
string folder = unUploaded.Description;
string subfolder = unUploaded.Category;
if (string.IsNullOrEmpty(folder) || string.IsNullOrEmpty(subfolder))
continue;
var uploadDto = new Dtos.Upload
{
FolderName = folder,
SubFolderName = subfolder,
Image = GetImageAsBase64(unUploaded.ImagePath)
};
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var fileName = Path.GetFileName(unUploaded.ImagePath);
var tempFileName = Path.Combine(documents, $"{fileName}.txt");
string stringContent = await new StringContent(JsonConvert.SerializeObject(uploadDto), Encoding.UTF8, "application/json").ReadAsStringAsync();
await File.WriteAllTextAsync(tempFileName, stringContent);
using (var url = NSUrl.FromString(UploadUrlString))
using (var request = new NSMutableUrlRequest(url)
{
HttpMethod = "POST",
})
{
request.Headers.SetValueForKey(NSObject.FromObject("application/json"), new NSString("Content-type"));
try
{
uploadSession.CreateUploadTask(request, NSUrl.FromFilename(tempFileName));
}
catch (Exception e)
{
Console.WriteLine($"NSMutableUrlRequest failed {e.Message}");
}
}
}
catch (Exception e)
{
if (e.Message.Contains("Could not find a part of the path"))
{
await datastore.DeleteItemAsync(unUploaded.Id);
Console.WriteLine($"deleted");
}
Console.WriteLine($"uploadStore failed {e.Message}");
}
}
}
private string GetImageAsBase64(string path)
{
using (var reader = new StreamReader(path))
using (MemoryStream ms = new MemoryStream())
{
reader.BaseStream.CopyTo(ms);
return Convert.ToBase64String(ms.ToArray());
}
}
public NSUrlSession InitBackgroundSession(string authToken = null, IDataStore<Upload> dataStore = null)
{
Console.WriteLine("InitBackgroundSession");
using (var configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration(Identifier))
{
configuration.AllowsCellularAccess = true;
configuration.Discretionary = false;
configuration.AllowsConstrainedNetworkAccess = true;
configuration.AllowsExpensiveNetworkAccess = true;
if (string.IsNullOrWhiteSpace(authToken) == false)
{
configuration.HttpAdditionalHeaders = NSDictionary.FromObjectsAndKeys(new string[] { $"Bearer {authToken}" }, new string[] { "Authorization" });
}
return NSUrlSession.FromConfiguration(configuration, new UploadDelegate(dataStore), null);
}
}
}
public class UploadDelegate : NSUrlSessionTaskDelegate, INSUrlSessionDelegate
{
public IDataStore<Upload> Datastore { get; }
public UploadDelegate(IDataStore<Upload> datastore)
{
this.Datastore = datastore;
}
public override void DidCompleteWithError(NSUrlSession session, NSUrlSessionTask task, NSError error)
{
Console.WriteLine(string.Format("DidCompleteWithError TaskId: {0}{1}", task.TaskIdentifier, (error == null ? "" : " Error: " + error.Description)));
if (error == null)
{
ProcessCompletedTask(task);
}
}
public void ProcessCompletedTask(NSUrlSessionTask sessionTask)
{
try
{
Console.WriteLine(string.Format("Task ID: {0}, State: {1}, Response: {2}", sessionTask.TaskIdentifier, sessionTask.State, sessionTask.Response));
if (sessionTask.Response == null || sessionTask.Response.ToString() == "")
{
Console.WriteLine("ProcessCompletedTask no response...");
}
else
{
var resp = (NSHttpUrlResponse)sessionTask.Response;
Console.WriteLine("ProcessCompletedTask got response...");
if (sessionTask.State == NSUrlSessionTaskState.Completed && resp.StatusCode == 201)
{
Console.WriteLine("201");
}
}
}
catch (Exception ex)
{
Console.WriteLine("ProcessCompletedTask Ex: {0}", ex.Message);
}
}
public override void DidBecomeInvalid(NSUrlSession session, NSError error)
{
Console.WriteLine("DidBecomeInvalid" + (error == null ? "undefined" : error.Description));
}
public override void DidFinishEventsForBackgroundSession(NSUrlSession session)
{
Console.WriteLine("DidFinishEventsForBackgroundSession");
}
public override void DidSendBodyData(NSUrlSession session, NSUrlSessionTask task, long bytesSent, long totalBytesSent, long totalBytesExpectedToSend)
{
}
}
Everything works if the iOS charger cable is plugged in, however, if it isn't nothing fires. I have a network debugging set up with plenty of logging into the console, and I can see that nothing happens on iPhone.
"Low power mode" setting on iOS is off.
I have watched Background execution demystified and I am setting session configuration.Discretionary = false;
How do I make the NSUrlSession upload task to fire when iOS charger cable is unplugged on iOS 14.4?
Following works without charging cable:
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public Action BackgroundSessionCompletionHandler { get; set; }
public static string UploadTaskId { get; } = "XXX.upload";
public static NSString UploadSuccessNotificationName { get; } = new NSString($"{UploadTaskId}.success");
public static string RefreshTaskId { get; } = "XXX.refresh";
public static NSString RefreshSuccessNotificationName { get; } = new NSString($"{RefreshTaskId}.success");
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
BGTaskScheduler.Shared.Register(UploadTaskId, null, task => HandleUpload(task as BGProcessingTask));
BGTaskScheduler.Shared.Register(RefreshTaskId, null, task => HandleAppRefresh(task as BGAppRefreshTask));
return base.FinishedLaunching(app, options);
}
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
return true;
}
public override void HandleEventsForBackgroundUrl(UIApplication application, string sessionIdentifier, Action completionHandler)
{
Console.WriteLine("HandleEventsForBackgroundUrl");
BackgroundSessionCompletionHandler = completionHandler;
}
public override void OnActivated(UIApplication application)
{
Console.WriteLine("OnActivated");
var uploadService = new UploadService();
uploadService.EnqueueUpload();
}
public override void OnResignActivation(UIApplication application)
{
Console.WriteLine("OnResignActivation");
}
private void HandleAppRefresh(BGAppRefreshTask task)
{
task.ExpirationHandler = () =>
{
Console.WriteLine("BGAppRefreshTask ExpirationHandler");
var refresh = new BGAppRefreshTaskRequest(RefreshTaskId);
BGTaskScheduler.Shared.Submit(refresh, out NSError refreshError);
if (refreshError != null)
Console.WriteLine($"BGAppRefreshTask ExpirationHandler Could not schedule BGAppRefreshTask: {refreshError}");
};
HandleUpload(task);
}
public override void DidEnterBackground(UIApplication application) => ScheduleUpload();
private void HandleUpload(BGTask task)
{
Console.WriteLine("HandleUpload");
var uploadService = new UploadService();
uploadService.EnqueueUpload();
task.SetTaskCompleted(true);
}
private void ScheduleUpload()
{
Console.WriteLine("ScheduleUpload");
var upload = new BGProcessingTaskRequest(UploadTaskId)
{
RequiresNetworkConnectivity = true,
RequiresExternalPower = false
};
BGTaskScheduler.Shared.Submit(upload, out NSError error);
var refresh = new BGAppRefreshTaskRequest(RefreshTaskId);
BGTaskScheduler.Shared.Submit(refresh, out NSError refreshError);
if (error != null)
Console.WriteLine($"Could not schedule BGProcessingTask: {error}");
if (refreshError != null)
Console.WriteLine($"Could not schedule BGAppRefreshTask: {refreshError}");
}
}
then Upload service:
public class UploadService : IUploadService
{
private const string uploadUrlString = "https://Yadyyadyyada";
public async void EnqueueUpload()
{
var accountsTask = await App.PCA.GetAccountsAsync();
var authResult = await App.PCA.AcquireTokenSilent(App.Scopes, accountsTask.First())
.ExecuteAsync();
try
{
var uploadDto = new object();
var message = new HttpRequestMessage(HttpMethod.Post, uploadUrlString);
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
message.Content = new StringContent(JsonConvert.SerializeObject(uploadDto), Encoding.UTF8, "application/json");
var response = await httpClient.SendAsync(message);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
}
}
catch (Exception e)
{
Console.WriteLine($"EnqueueUpload {e.Message}");
}
}
}

Xamarin.Auth - Google OAuth don't close over UWP when IsUsingNativeUI is set to true

I just implemented Xamarin.Auth on UWP, and I have a weird problem.
I begin by creating my OAuth object :
public class OAuth
{
private Account account;
private AccountStore store;
public string Scope;
public string AuthorizeUrl;
public string AccessTokenUrl;
public string UserInfoUrl;
string clientId;
string clientSecret;
string redirectUri;
private bool isUsingNativeUI;
private Func<JObject, User> OAuthParser;
private Action<User> OnCompleted;
private Action<string> OnError;
public OAuth()
{
account = null;
store = null;
Scope = "";
AuthorizeUrl = "";
AccessTokenUrl = "";
UserInfoUrl = "";
clientId = "";
clientSecret = null;
redirectUri = "";
}
public OAuth Facebook()
{
// These values do not need changing
Scope = "email";
AuthorizeUrl = "https://www.facebook.com/v2.8/dialog/oauth";
AccessTokenUrl = "https://graph.facebook.com/oauth/access_token";
UserInfoUrl = "https://graph.facebook.com/me?fields=email,name,gender,picture";
clientId = "xxxx";
clientSecret = "xxxxx";
redirectUri = "http://www.facebook.com/connect/login_success.html";
isUsingNativeUI = false;
OAuthParser = ParseFacebookResponse;
return this;
}
public OAuth GooglePlus()
{
// These values do not need changing
Scope = "https://www.googleapis.com/auth/userinfo.email";
AuthorizeUrl = "https://accounts.google.com/o/oauth2/auth";
AccessTokenUrl = "https://www.googleapis.com/oauth2/v4/token";
UserInfoUrl = "https://www.googleapis.com/oauth2/v2/userinfo";
clientId = "xxxxx";
redirectUri = "xxxxxx";
isUsingNativeUI = true;
OAuthParser = ParseGooglePlusResponse;
return this;
}
public OAuth2Authenticator Authenticator(Action<User> onCompleted, Action<string> onError)
{
OAuth2Authenticator authenticator = new OAuth2Authenticator(
clientId,
clientSecret,
Scope,
new Uri(AuthorizeUrl),
new Uri(redirectUri),
new Uri(AccessTokenUrl),
null,
isUsingNativeUI);
authenticator.Completed += OnAuthCompleted;
authenticator.Error += OnAuthError;
OnCompleted = onCompleted;
OnError = onError;
return authenticator;
}
private async void OnAuthCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
User user = null;
try
{
OAuth2Authenticator OAuth2Authenticator = sender as OAuth2Authenticator;
if (OAuth2Authenticator != null)
{
OAuth2Authenticator.Completed -= OnAuthCompleted;
OAuth2Authenticator.Error -= OnAuthError;
}
//User user = null;
if (e.IsAuthenticated)
{
var request = new OAuth2Request("GET", new Uri(UserInfoUrl), null, e.Account);
var response = await request.GetResponseAsync();
if (response != null)
{
user = OAuthParser(JObject.Parse(await response.GetResponseTextAsync()));
}
if (account != null)
{
store.Delete(account, App.AppName);
}
await store.SaveAsync(account = e.Account, App.AppName);
}
} catch (Exception ex) {
Debug.WriteLine(ex);
}
(Application.Current.MainPage as LoginPage).OAuthCompleted(user);
}
private void OnAuthError(object sender, AuthenticatorErrorEventArgs e)
{
OAuth2Authenticator OAuth2Authenticator = sender as OAuth2Authenticator;
if (OAuth2Authenticator != null)
{
OAuth2Authenticator.Completed -= OnAuthCompleted;
OAuth2Authenticator.Error -= OnAuthError;
}
(Application.Current.MainPage as LoginPage).OAuthError("Authentication error: " + e.Message);
}
private static User ParseGooglePlusResponse(JObject jobject)
{
try
{
User user = new User()
{
Email = jobject["email"].ToString(),
Pseudo = jobject["name"].ToString(),
Firstname = jobject["given_name"].ToString(),
Surname = jobject["family_name"].ToString(),
Image = jobject["picture"].ToString(),
Password = "girafe"
};
return user;
}
catch (Exception e)
{ Debug.WriteLine(e.ToString()); }
return null;
}
private static User ParseFacebookResponse(JObject jobject)
{
try
{
Debug.WriteLine(jobject);
User user = new User()
{
Email = jobject["email"].ToString(),
Pseudo = jobject["name"].ToString(),
Image = jobject["picture"]["data"]["url"].ToString(),
Password = "girafe"
};
return user;
}
catch (Exception e)
{ Debug.WriteLine(e.ToString()); }
return null;
}
}
And I use it like that:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LoginPage : ContentPage, INotifyPropertyChanged
{
private OAuth OAuthService;
private OAuthLoginPresenter Presenter;
// ...
private void FacebookAuthConnection()
{
AuthenticationState.Authenticator = OAuthService.Facebook().Authenticator(OAuthCompleted, OAuthError);
Presenter.Login(AuthenticationState.Authenticator);
}
private void GooglePlusAuthConnection()
{
AuthenticationState.Authenticator = OAuthService.GooglePlus().Authenticator(OAuthCompleted, OAuthError);
Presenter.Login(AuthenticationState.Authenticator);
}
// ...
}
On Android, either if isUsingNativeUI is set to true or false, after I connect myself, the webview/oauth get closed. Over UWP however, it doesn't work when isUsingNativeUI is set to true, the webview/oauth don't close, so I'm navigating on google and I am not able to come back on the app (UWP Desktop)...
Do you have any idea?

Updating image control in Windows Phone 8

I have a HTML5 web app I can view through my mobile devices.
I have an img control that would download an image using an ashx asp.net handler.
I updated via a timer.
I am trying to port this over to a Windows Phone 8.1 app instead.
The image seems to take ages to update (if at all). This is my code:
long tick = DateTime.Now.Ticks;
BitmapImage bmp =new BitmapImage(new Uri("http://my url/Mobile/NewFrame.ashx?b=1a=9A5C3-E1945-3D315-BB43C&c=3&m=1&t=" + tick));
imgFrame1.Source = bmp;
Is this the correct way?
this is the full code:
private async void LogIn()
{
using (var client = new HttpClient())
{
var resp = await client.PostAsJsonAsync("http://my url/UserManagement/Login.aspx/Test",
new { username = "", password = "", hubuserid = hubuserid });
var str = await resp.Content.ReadAsStringAsync();
var jsonObj = JsonConvert.DeserializeObject<UserLogIn>(str);
if (jsonObj.d.Success)
{
UpdateConnectionState("Logged In");
}
else
{
UpdateConnectionState("Not Logged In");
}
}
}
public class D
{
public string __type { get; set; }
public bool Success { get; set; }
}
public class UserLogIn
{
public D d { get; set; }
}
private string hubuserid = "";
public string Uptime { get; set; }
private byte ImageIsLoaded = 1;
private async void UpdateTime(int data)
{
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
try
{
txtInfo.Text = data.ToString();
if (ImageIsLoaded == 1)
{
ImageIsLoaded = 0;
long tick = DateTime.Now.Ticks;
BitmapImage bi = new BitmapImage(new Uri("http://www.informedmotion.co.uk/Mobile/NewFrame.ashx?b=1a=9A5C3-E1945-3D315-BB43C&c=3&m=1&t=" + tick, UriKind.Absolute));
bi.DownloadProgress += bi_DownloadProgress;
bi.ImageOpened += bi_ImageOpened; }
}
catch (Exception ex)
{
txtInfo.Text = ex.ToString();
}
});
}
void bi_DownloadProgress(object sender, DownloadProgressEventArgs e)
{
//throw new NotImplementedException();
}
void bi_ImageOpened(object sender, RoutedEventArgs e)
{
ImageIsLoaded = 1;
imgFrame1.Source = (BitmapImage)sender;
}
private void imgFrame1_ImageOpened(object sender, RoutedEventArgs e)
{
ImageIsLoaded = 1;
}
private void imgFrame1_ImageFailed(object sender, ExceptionRoutedEventArgs e)
{
ImageIsLoaded = 1;
}
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
imgFrame1.ImageFailed += imgFrame1_ImageFailed;
imgFrame1.ImageOpened += imgFrame1_ImageOpened;
ConnectToHub();
}
private void ConnectToHub()
{
proxy.On<int>("broadcastMessage", data =>
{
UpdateTime(data);
});
connection.Start().ContinueWith(task =>
{
if (task.IsFaulted)
{
UpdateConnectionState("Not Connected");
ConnectToHub();
}
else
{
UpdateConnectionState(string.Format("Success! Connected with client connection id {0}", connection.ConnectionId));
hubuserid = connection.ConnectionId;
LogIn();
}
});
connection.Error += ex =>
{
UpdateConnectionState(string.Format("An error occurred {0}", ex.Message));
};
connection.Closed += () =>
{
UpdateConnectionState(string.Format("Connection with client id {0} closed", connection.ConnectionId));
ConnectToHub();
};
connection.Reconnected += () =>
{
//LogIn();
UpdateConnectionState("The connection was re-established");
};
}
Windows.UI.Core.CoreDispatcher dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;
async void UpdateConnectionState(string state)
{
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
try{
txtInfo.Text = state;
}
catch (Exception ex)
{
txtInfo.Text = ex.ToString();
}
});
}
static HubConnection connection = new HubConnection("http://www.informedmotion.co.uk/");
IHubProxy proxy = connection.CreateHubProxy("ChatHub");
If you're going to download the image, then you probably want to hooked the
Image.DownloadProgress event
Image.ImageOpened event
ImageOpened will fire once the download is complete, so at that moment you can set the .Source to it.
While it is downloading (if it's a huge image) you can either show the previous image or a place holder image (with progress bar maybe?)
BitmapImage bi = new BitmapImage(new Uri("http://www.google.com/myimage.bmp", UriKind.Absolute));
bi.DownloadProgress += bi_DownloadProgress;
bi.ImageOpened += bi_ImageOpened;
hiddenImage.Source = bi; // we need to set it to an element in the visual tree so the
// events will fire, we're going to use the hiddenImage
void bi_DownloadProgress(object sender, DownloadProgressEventArgs e)
{
throw new NotImplementedException();
}
void bi_ImageOpened(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
<!-- myImage is your image that you use to show stuff -->
<!-- hiddenImage is the image we use to fire the event -->
<Image x:Name="myImage"></Image>
<Image x:Name="hiddenImage" Visibility="Collapsed"></Image>

Resources