Search code examples
xamarinmvvmcross

Is it possible to pass null reference to Init view model?


I have this call on one view model

ShowViewModel<MyViewModel>(
    new MyParams { ... }
);

On MyViewModel I have this Init method which works perfect

public void Init(MyParams params)
{
    if (params != null)
    {
        // some logic
    }
    else 
    {
        // some other logic
    }
}

On another view model I have

ShowViewModel<MyViewModel>();

I expect to receive null on MyViewModel init method, instead of that I get an instance of 'MyParams'. That's generating problems since I have specific logic to handle the call with no parameters

I have custom presenter logic that might responsible for this, but at first sight I couldn't identify any custom logic as responsible. Is this the standard behavior for complex params?


Solution

  • Unfortunately, no there isn't a way to pass null using a parameters object.

    The reason is that when Mvx creates the ViewModel and attempts to call the Init method, it will first convert your object instance into a simple dictionary (key/value pairs). If you use the no arg version, then it creates an empty dictionary. At this point, it creates an MvxBundle which includes the dictionary.

    When Mvx is finally ready to call your Init method, it takes this dictionary and attempts to create an actual object.

    It's this method that creates the instance to pass to Init.

    MvxSimplePropertyDictionaryExtensionMethods.Read()

    https://github.com/MvvmCross/MvvmCross/blob/8a824c797747f74716fc64c2fd0e8765c29b16ab/MvvmCross/Core/Core/Platform/MvxSimplePropertyDictionaryExtensionMethods.cs#L54-L72

    public static object Read(this IDictionary<string, string> data, Type type)
    {
        var t = Activator.CreateInstance(type);
        var propertyList =
            type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy).Where(p => p.CanWrite);
    
        foreach (var propertyInfo in propertyList)
        {
            string textValue;
            if (!data.TryGetValue(propertyInfo.Name, out textValue))
                continue;
    
            var typedValue = MvxSingletonCache.Instance.Parser.ReadValue(textValue, propertyInfo.PropertyType,
                                                                         propertyInfo.Name);
            propertyInfo.SetValue(t, typedValue, new object[0]);
        }
    
        return t;
    }
    

    Notice how it calls Activator.CreateInstance(type) which will always return an instance.

    So that is why you'll never get an null value in Init.

    My recommendation is to simply add a property to your MyParams object and set that in your no-arg version. Then in Init you can check the property to determine what to do.

    Something like:

    ShowViewModel<MyViewModel>(new MyParams { HasNoParams = true });
    
    public void Init(MyParams myParams)
    {
        if (myParams.HasNoParams)
        {
            // do null flow here
        }
        else 
        {
            // do non-null flow here
        }
    }