Intents in mvvmcross on Mono for Android - c#

I've recently upgraded to Xamarin Studio running on Windows. I have a simple implementation of the mvvmcross TipCalculator tutorial that ran nicely on Android and the various Windows flavors. After I upgraded, the Android application started throwing NullReferenceExceptions in the Main activity (below):
[Activity(Label = "TipCalculator.Android", MainLauncher = true, Icon = "#drawable/icon")]
public class TipCalculatorActivity : MvxBindingActivityView<TipCalculatorViewModel>
{
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.TipView);//Throws NullReferenceException
}
}
After doing some digging into the mvvmcross code, I found this method in the MvxAndroidViewsContainer class:
public virtual IMvxViewModel Load(Intent intent, Type viewModelTypeHint)
{
if (intent == null)
{
// TODO - some trace here would be nice...
return Activator.CreateInstance(viewModelTypeHint) as IMvxViewModel;
//return null;
}
if (intent.Action == Intent.ActionMain)
{
// TODO - some trace here would be nice...
return Activator.CreateInstance(viewModelTypeHint) as IMvxViewModel;
}
if (intent.Extras == null)
{
// TODO - some trace here would be nice...
return Activator.CreateInstance(viewModelTypeHint) as IMvxViewModel;
//return null;
}
IMvxViewModel mvxViewModel;
if (TryGetEmbeddedViewModel(intent, out mvxViewModel))
return mvxViewModel;
return CreateViewModelFromIntent(intent);
}
In the original code, there are two cases that return null. In each of these I replaced that with a call to Activator.CreateInstance().
I'm not sure what the rationale behind the original implementation is, and I'm a bit concerned I've broken something. Can anybody shed some light on why there are cases this method returns null and whether I've broken something fundamental in mvvmcross?

I have no idea what your current issue has to do with the new Xamarin tools. Your description of NullReferenceExceptions, changed files and blog posts lost me...
I'd guess that quite a few things may have changed in your development setup and maybe in your app as well. If you can work back out one step at a time maybe then you'll be able to work out what the key change is.
It does sound like your code change might fix might fix your current problem - but it's definitely a patch applied after the problem (whatever it is) has occurred, so it's not something I'd want to do in the core code right now.
In terms of the code you've asked about:
if (intent == null)
{
// TODO - some trace here would be nice...
return null;
}
This first null will only occur if the Activity has somehow been created without an Intent - which I guess isn't happening? (Unless maybe somewhere in your current tooling setup this is somehow being set as null?)
if (intent.Action == Intent.ActionMain)
{
// TODO - some trace here would be nice...
return Activator.CreateInstance(viewModelTypeHint) as IMvxViewModel;
}
This is the normal path for a directly launched activity - this activity will have no extra parameters for constructing the ViewModel.
Generally this path doesn't happen in many MvvmCross apps - most apps launch via a splashscreen activity.
if (intent.Extras == null)
{
// TODO - some trace here would be nice...
return null;
}
For any other activity, Mvx should have inserted some special ViewModel information into the Extras - so there is no way Extras should be null... If this is happening, then what code is creating the activity?
IMvxViewModel mvxViewModel;
if (TryGetEmbeddedViewModel(intent, out mvxViewModel))
return mvxViewModel;
return CreateViewModelFromIntent(intent);
This is the normal launch path for an activity that has been navigated to.
If it helps, here's the latest v3 code - which has some trace added (but also adds the confusion of savedState - ignore this for now!):
public virtual IMvxViewModel Load(Intent intent, IMvxSavedState savedState, Type viewModelTypeHint)
{
if (intent == null)
{
MvxTrace.Trace(MvxTraceLevel.Error, "Null Intent seen when creating ViewModel");
return null;
}
if (intent.Action == Intent.ActionMain)
{
MvxTrace.Trace("Creating ViewModel for ActionMain");
return Activator.CreateInstance(viewModelTypeHint) as IMvxViewModel;
}
if (intent.Extras == null)
{
MvxTrace.Trace(MvxTraceLevel.Error, "Null Extras seen on Intent when creating ViewModel - this should not happen - have you tried to navigate to an MvvmCross View directly?");
return null;
}
IMvxViewModel mvxViewModel;
if (TryGetEmbeddedViewModel(intent, out mvxViewModel))
{
MvxTrace.Trace("Embedded ViewModel used");
return mvxViewModel;
}
MvxTrace.Trace("Loading new ViewModel from Intent with Extras");
return CreateViewModelFromIntent(intent, savedState);
}

Related

C# Xamarin.iOS property access returns null while it is not in object

I am facing a weird and disturbing problem in Xamarin app.
My piece of code below is in a project library which is shared with Xamarin.Android and Xamarin.iOS apps.
On the Android app, everything goes well and the code produces the exact expected behaviour.
But for strange reasons, it differs on the iOS app.
The value of dbData.Status is always null.
The odd thing is when I do a "QuickWatch" on the dbData object, the value of the Status property is not null and well set.
namespace MyApp.Services
{
public class MyPersistedObject : RealmObject
{
public static MyPersistedObject GetDbData()
{
var dbData = Ctx.Db.All<MyPersistedObject>().FirstOrDefault();
if (dbData == null)
{
dbData = new MyPersistedObject();
Ctx.Db.Write(() => Ctx.Db.Add(dbData));
}
return dbData;
}
public static void SetLocationEnabled(bool isEnabled = true)
{
var dbData = GetDbData();
if (isEnabled && dbData.Status == "disabled_status")
{
Ctx.Db.Write(() => dbData.Status = "enabled_status");
}
else if (!isEnabled && dbData.Status == "enabled_status")
{
Ctx.Db.Write(() => dbData.Status = "disabled_status");
}
Ctx.Db.Write(() => dbData.Enabled = isEnabled);
}
public string Status { get; set; }
}
}
Am I doing something wrong?
Anybody have an idea?
Without access to the entire solution and running it in Debug it's difficult to say exactly what the problem is (for example what class is the Ctx.Db object?), however my guess is that you have an initialization issue in your data store (Ctx.Db object) for iOS.
The most likely scenario you are seeing is that when you call GetDbData() it is returning an instantiated object but the Status property is null on iOS (which is the default for a string). Then neither of the if statements in SetLocationEnabled() are true, as the status property is not set, and remains null.
There are two possibilities to cause this, either a new MyPersistedObject() is not instantiated correctly for iOS, which could only really happen if its different for the two platforms and is injected, which seems very unlikely as its probably just common code; or there is a problem with how you have initialized the entry in your data store for iOS.
Set some breakpoints when you initialize your data store with the default values on both platforms and you should be able to tack down where there issue is being caused and how to correct it.

VS2017 doesn't give details for an exception, just crashed with null

I'm working on a UWP project and there's something funky going on with how errors are being presented to me. I don't know if it's VS2017 or how UWP is set up.
I have a piece of code that goes online and retrieves json content, sometimes the code works and sometimes it doesn't. It works when I use Expander control from UWP Community toolkit, and fails when I want to switch to GridView. When it doesn't work, it fails on GetStringAsync method of HttpClient. The strange behavior is that the exception isn't thrown in the method where the problem occurs, the code actually redirects me back without giving an error and as soon as it gets to the property that's supposed to have a value that isn't null, I get a null exception.
This is where the problem happens:
string httpContent = "";
using (HttpClient httpClient = new HttpClient())
{
try
{
httpContent = await httpClient.GetStringAsync(uri);
}
catch (Exception e)
{
// TODO: handle errors
var x = "";
}
}
This piece of code is called from within the view model. It starts with a constructor and RefreshServerKanesWrathDataAsync is the method where json is parsed.
public CncOnlinePageViewModel()
{
cnconline = new CncOnline();
cnconline.RefreshServerKanesWrathDataAsync();
}
The second I get to GetStringAsync, the code just goes back to the constructor like nothing happened, however the method never completes, it just exits back to the constructor, and therefore fails to update observable collections with data. I then get a null exception.
I wanted to test this with VS2015, but I updated some controls that are apparently only supported withing VS2017, so I can't run the code in other versions.
I also ran into an issue with the code prior to this problem, where I tried to access files in a directory without using a token. The behavior was exactly the same, the code wasn't telling me that I didn't have access to the directory I wanted to read, it was just throwing me out of the method back into the location that made the call to read the directory. Just like with the current problem, I would then run into a null exception, which wasn't where the main problem was.
I added Template10 and UWP community toolkit to the project, if that matters.
You shouldn't call an async method from a constructor unless you're willing to provide a callback.
public CncOnlinePageViewModel()
{
cnconline = new CncOnline();
var t = cnconline.RefreshServerKanesWrathDataAsync(); // assuming returns Task<string>
t.ContinueWith(OnCompleted);
}
private void OnCompleted(Task<string> task)
{
if (task.IsFaulted)
{
// Check error
var exception = task.Exception;
}
else if (task.IsCanceled)
{
// User hit cancel?
}
else
{
// All good!
var result = task.Result;
}
}
Here's a sample where RefreshServerKanesWrathDataAsync() returns just Task (not Task<result>)
public CncOnlinePageViewModel()
{
cnconline = new CncOnline();
var t = cnconline.RefreshServerKanesWrathDataAsync(); // assuming returns Task
t.ContinueWith(OnCompleted);
}
private void OnCompleted(Task task)
{
if (task.IsFaulted)
{
// Check error
var exception = task.Exception;
}
else if (task.IsCanceled)
{
// User hit cancel?
}
else
{
// All good!
}
}
On a side note, you may also need to have Visual Studio 2017 break when any exception is thrown. In VS2017, go to Debug->Windows->Exception Settings and make sure Common Language Runtime Exceptions has a check. If it has a filled box, click the box until it turns into a checkmark.
Also..., you can tap into an event raised when any task has an unobserved exception. You can do so in the constructor of App.xaml.cs
public App()
{
TaskScheduler.UnobservedTaskException += OnUnobservedException;
}
private static void OnUnobservedException(object sender, UnobservedTaskExceptionEventArgs e)
{
// Put break point here.
var ex = e.Exception;
// This will keep your app alive, but only do it if it's safe to continue.
e.SetObserved();
}

Determine if CoreApplicationView.GetCurrentView() will throw exception?

Early in uwp app startup, CoreApplicationView.GetCurrentView() may throw an exception, presumably because there isn't a current view yet.
Is there a way to tell if that will happen or not, without actually calling it?
I haven't tested it, but CoreApplication.Views returns a list of all existing views. It should be possible to do something like that:
public static bool HasCurrentView() {
return CoreApplication.Views.Count > 0;
}
I couldn't test it cause I don't know when this exactly throws an exception.
The CoreApplicationView.GetCurrentView() method returns the active view for the app. I think you need to add a judgement before calling it.
For example like this:
if (Window.Current != null)
{
if (Window.Current.Content != null)
{
Window.Current.Activate();
var view = CoreApplication.GetCurrentView();
}
}

Issues with Nancy Framework for MVC (and NewRelic)

I just took over a bunch of C# code from another company, and I'm having big trouble getting the first build to work. The code uses a framework called Nancy, instead of MVC. I have never used this framework before, and there might be a real simply answer to my question, and I apllogize if I missed some basic understanding of Nancy, before posting here.
The problem is boiled down to a single class, handling the initialization of the application (I THINK) From what I've read, it's pretty standard Nancy:
using System;
using Nancy;
using NewRelicAgent = NewRelic.Api.Agent.NewRelic;
using Nancy.Bootstrapper;
using Nancy.Routing;
public class NewRelicStartup : IApplicationStartup
{
private readonly IRouteResolver routeResolver;
public NewRelicStartup (IRouteResolver routeResolver)
{
this.routeResolver = routeResolver;
}
public void Initialize(IPipelines pipelines)
{
pipelines.BeforeRequest.AddItemToStartOfPipeline(
context =>
{
var route = routeResolver.Resolve(context);
if (route == null || route.Item1 == null || route.Item1.Description == null) // probably not necessary but don't want the chance of losing visibility on anything
{
NewRelicAgent.SetTransactionName(
context.Request.Method,
context.Request.Url.ToString());
}
else
{
NewRelicAgent.SetTransactionName(
route.Item1.Description.Method,
route.Item1.Description.Path);
}
return null;
});
pipelines.OnError.AddItemToEndOfPipeline(
(context, ex) => {
NewRelicAgent.NoticeError(ex);
return null;
}
);
}
}
When this code is being build, I get several errors, some of which being:
Delegate 'System.Func<Nancy.NancyContext,System.Threading.CancellationToken,System.Threading.Tasks.Task<Nancy.Response>>' does not take 1 arguments
Cannot convert lambda expression to type 'Nancy.PipelineItem<System.Func<Nancy.NancyContext,System.Threading.CancellationToken,System.Threading.Tasks.Task<Nancy.Response>>>' because it is not a delegate type
Here is a screenshot of the kind of error I get:
https://www.dropbox.com/s/cigcfc4sfj8batg/Nancy%20Error.PNG
I am 100 % certain, that this is some kind of interpretation issue from Visual Studio's side, since the code is live atm. I just can't build it in VS.
Do any of you have any idea what I am missing, or doing wrong? Remember; the code is working and live atm.
Try changing "return null;" to "return (Nancy.Response)null;"
Edit: Sorry, just looked at the screenshot - it's using some properties that have changed in 0.20, so you will either have to manually fix up the code (it's now async at the core), or roll back to 0.19 for now, and re-write that bit of code at a later date.
Edit again: Give this ago:
pipelines.BeforeRequest.AddItemToStartOfPipeline(
context =>
{
var route = routeResolver.Resolve(context);
if (route == null || route.Route == null || route.Route.Description == null) // probably not necessary but don't want the chance of losing visibility on anything
{
NewRelicAgent.SetTransactionName(
context.Request.Method,
context.Request.Url.ToString());
}
else
{
NewRelicAgent.SetTransactionName(
route.Route.Description.Method,
route.Route.Description.Path);
}
return null;
});

Workaround for HttpContext.HideRequestResponse being internal? Detect if HttpContext.Request is really available?

We're migrating an application to use IIS7 integrated mode. In library code that is designed to work either within the context of an HTTP request or not, we commonly have code like this:
if (HttpContext.Current != null &&
HttpContext.Current.Request != null) {
// do something with HttpContext.Current.Request
} else {
// do equivalent thing without HttpContext..
}
But in IIS7 integrated mode the check for HttpContext.Current.Request throws an exception whenever this code is called from Application_Start.
protected void Application_Start(object sender, EventArgs e)
{
SomeLibrary.DoSomethingWithHttpContextCurrentDetection();
}
Results in:
System.Web.HttpException: Request is not available in this context
How can I detect whether the request is really available without wrapping these calls in an exception handler and taking action based on whether an exception is generated or not.
Looking at HttpContext in Reflector I see it has an internal bool HideRequestResponse field but it's internal so I can only get to it with reflection and that's fragile. Is there a more official/approved way to determine if it's ok to call HttpContext.Request?
This blog post about the subject says not to use HttpContext, but how, in generic library code, can you determine if it's ok to use HttpContext?
http://mvolo.com/iis7-integrated-mode-request-is-not-available-in-this-context-exception-in-applicationstart/
I'm using the work-around mentioned there which is to use Application_BeginRequest and an initialized field to only initialize once as part of BeginRequest, but that has to be done in every calling application whereas I'd prefer to make the library code more robust and handle this situation regardless of where it's called from.
I would refactor your code to this:
if (IsRequestAvailable())
{
// do something with HttpContext.Current.Request...
}
else
{
// do equivalent thing without HttpContext...
}
public Boolean IsRequestAvailable()
{
if (HttpContext.Current == null)
return false;
try
{
if (HttpContext.Current.Request == null)
return false;
}
catch (System.Web.HttpException ex)
{
#if DEBUG
// Testing exception to a magic string not the best practice but
// it works for this demo.
if (ex.Message == "Request is not available in this context")
return false;
throw;
#else
return false;
#endif
}
return true;
}
Your question asked not to use exception handling (I assume for performance reasons) and my answer does. However, by changing your code from using "If (HttpContext.Current != null && HttpContext.Current.Request != null)" to "If (IsRequestAvailable())" you only have one place to change the code when you find an answer how not to use exception handling.
I'm afraid the answer is that you can't get what you want - Microsoft sees this case as an 'exceptional circumstance' and so it will throw an exception.
You can use reflection as you describe in your answer but you don't want to and so are limited by the API that Microsoft have provided, for better or for worse.
If you do decide to use reflection, of note is the HttpApplication.InitInternal method which is what sets the HideRequestResponse flag.
Hope that helps. I would suggest you file a report with Microsoft Connect.
You should not even use Request (or Response) in the Application_Start since application could be started without a request. So in the future your application won't even run when other parts of framework stop providing the Request object.
If you want to just hack it temporarily, you could use Reflection (if you have above-medium trust) or catching an exception (even though you don't want to) and store the result in a static variable or possibly use a static HttpContext wrapper:
Also you could use HttpRuntime.UsingIntegratedPipeline.
So the best approach is remove the dependance of your classes on HttpContext when they are being initialized or not initalize them in appstart.
What is your reasoning to use Request in the app start anyway? For statistics? Or just telling the user he woke the application?
Edited with code to explain better:
public static class ContextWrapper
{
public static HttpRequest Request
{
get
{
HttpContext context = HttpContext.Current;
if (context == null) return null;
if (HttpRuntime.UsingIntegratedPipeline)
{
try { return context.Request; }
catch (HttpException e) { /* Consume or log e*/ return null; }
// Do not use message comparison - .NET translates messages for multi-culture environments.
}
return context.Request;
}
}
}
And in code:
if (ContextWrapper.Request != null) //...
Or a user-controlled faster way:
public static class ContextWrapper2
{
public static bool IsIis7IntegratedAppStart { get; set; }
public static HttpRequest Request
{
get
{
if (ContextWrapper2.IsIis7IntegratedAppStart) return null;
HttpContext context = HttpContext.Current;
if (context == null) return null;
return context.Request;
}
}
}
And in app start:
protected void Application_Start(object sender, EventArgs e)
{
yourLibraryNamespace.ContextWrapper2.IsIis7IntegratedAppStart = true;
//...
yourLibraryNamespace.yourClass.Init();
//...
yourLibraryNamespace.ContextWrapper2.IsIis7IntegratedAppStart = false;
}
You could note this behaviour in your documentation and all should be well. AppStart-like context should be the only place where you get such an exception.
You could also implement IDisposable on a member and use it in appStart with the using statement so you do not forget to set IsIis7IntegratedAppStart = false.
I think I have the solution for you. I maintain a logging library and have the same issue as you. If it is a web request I am grabbing some data from the HttpContext. But depending on how the logging library is used this same scenario can happen. So here is my solution. The key fix for me was checking if the Handler was null or not.
if (System.Web.Hosting.HostingEnvironment.IsHosted
&& System.Web.HttpContext.Current != null
&& System.Web.HttpContext.Current.Handler != null
&& System.Web.HttpContext.Current.Request != null)
{
//access the Request object here
}
Depending on what you are trying to accomplish, you may be able to get some of the properties and settings around the web app from System.Web.Hosting.HostingEnvironment
I added a comment, but it gets auto-hidden.
I think it's more important to have an idea of what it is that you need from the request.
For instance, the link you provided which provides a workaround is looking for Request.ApplicationPath.
If that's actually what you're looking for (for, say, loading the web.config vs the app.config), you could do this:
if (HttpRuntime.AppDomainAppId != null)
return WebConfigurationManager.OpenWebConfiguration(HttpRuntime.AppDomainAppVirtualPath);
else
return ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
If this (or HttpRuntime.ApplicationPath) isn't what you're actually looking for, it would be helpful to know which properties of the Request you are actually looking for. Maybe there's a better, safer way to get there.

Categories

Resources