Search code examples
xamarin.androidmvvmcross

MvvmCross App Won't Start after upgrading to version 6.0


I upgraded my app to MvvmCross version 6.0. Now it get's to the splash screen and does nothing else. I can see the the Services are starting up by looking at the console. Here is my app.cs:

public class App : MvxApplication
{
    public override void Initialize()
    {
        MPS.ApplicationName = Settings.ApplicationName;
        EventLog.ApplicationName = Settings.ApplicationName;
        BlobCache.ApplicationName = Settings.ApplicationName;

        CreatableTypes()
            .EndingWith("Service")
            .AsInterfaces()
            .RegisterAsLazySingleton();

        CreatableTypes()
            .EndingWith("Singleton")
            .AsInterfaces()
            .RegisterAsSingleton();

        //RegisterAppStart(new CustomAppStart());
        //RegisterNavigationServiceAppStart<LoginViewModel>();
        RegisterAppStart<LoginViewModel>();
    }

}

It is pretty basic. I had converted to the new navigation system hence the RegisterNavigationServiceAppStart. That would no longer resolve so I went back to a straight RegisterAppStart. The splash screen comes up and then it stops. In case it matters, splashscreen.cs is as follows:

[Activity(
    Label = "@string/ApplicationName"
    , MainLauncher = true
    , Icon = "@drawable/icon"
    , Theme = "@style/Theme.Splash"
    , NoHistory = true)]
    //, ScreenOrientation = ScreenOrientation.Landscape)]
public class SplashScreen : MvxSplashScreenActivity
{
    public SplashScreen()
        : base(Resource.Layout.SplashScreen)
    {
    }
}

It's pretty vanilla, but I know things have changed along the way. My setup.cs is as follows:

public class Setup : MvxAndroidSetup
{
    //public Setup(Context applicationContext)
    //    : base(applicationContext)
    //{
    //}

    protected override IMvxApplication CreateApp()
    {
        return new App();
    }

    //protected override IMvxTrace CreateDebugTrace()
    //{
    //    return new DebugTrace();
    //}

    protected override IMvxAndroidViewPresenter CreateViewPresenter()
    {
        return new MvxAppCompatViewPresenter(AndroidViewAssemblies);
    }

    protected override void FillValueConverters(IMvxValueConverterRegistry registry)
    {
        base.FillValueConverters(registry);
        registry.AddOrOverwrite("DateToStringConverter", new DateToStringConverter());
        registry.AddOrOverwrite("FloatToStringConverter", new FloatToStringConverter());
        registry.AddOrOverwrite("DecimalToStringConverter", new DecimalToStringConverter());
        registry.AddOrOverwrite("BoolToViewStatesConverter", new BoolToViewStatesValueConverter());
        registry.AddOrOverwrite("ShipmentToOriginConverter", new ShipmentToOriginConverter());
        registry.AddOrOverwrite("ShipmentToDestinationConverter", new ShipmentToDestinationConverter());
        //registry.AddOrOverwrite("CustomName2", new AnotherVerySpecialValueConverter("Summer"));
    }

    protected override void FillTargetFactories(MvvmCross.Binding.Bindings.Target.Construction.IMvxTargetBindingFactoryRegistry registry)
    {
        base.FillTargetFactories(registry);
        registry.RegisterCustomBindingFactory<EditText>("FocusText",
                                                  textView => new MvxEditTextFocusBinding(textView));
        registry.RegisterCustomBindingFactory<TextView>("FocusChange",
                                                  textView => new MvxTextViewFocusChangeBinding(textView));
        //registry.RegisterCustomBindingFactory<MvxSpinner>("ItemSelected",
        //                                          spinner => new MvxSpinnerItemSelectedBinding(spinner));
    }

    protected override IEnumerable<Assembly> AndroidViewAssemblies => new List<Assembly>(base.AndroidViewAssemblies)
    {
        typeof(MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView).Assembly
    };

}

The only change I made to it was to remove the constructor. Per the documentation for version 6.0, the constructor no longer has parameters so I saw no reason to call it. Can anyone help?

** Update **

I added a MainApplication.cs as follows:

[Application]
public class MainApplication : MvxAppCompatApplication<Setup, App>
{
    public MainApplication(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
    {
    }
}

This got me past the splash screen, but hung up on the Initialize of the LoginViewModel.

* Logs *

Maybe this will help. Here are the event log entries:

2018-04-17 12:17:06 [TRACE] (MvvmCross.Core.MvxSetup) Setup: Primary start
2018-04-17 12:17:06 [TRACE] (MvvmCross.Core.MvxSetup) Setup: FirstChance start
2018-04-17 12:17:06 [TRACE] (MvvmCross.Core.MvxSetup) Setup: PlatformServices start
2018-04-17 12:17:06 [TRACE] (MvvmCross.Core.MvxSetup) Setup: MvvmCross settings start
2018-04-17 12:17:06 [TRACE] (MvvmCross.Core.MvxSetup) Setup: Singleton Cache start
2018-04-17 12:17:06 [TRACE] (MvvmCross.Core.MvxSetup) Setup: ViewDispatcher start
2018-04-17 12:17:06 [TRACE] (MvvmCross.Core.MvxSetup) Setup: Bootstrap actions
2018-04-17 12:17:07 [TRACE] (MvvmCross.Logging.MvxLog) No view model type finder available - assuming we are looking for a splash screen - returning null
2018-04-17 12:17:07 [TRACE] (MvvmCross.Core.MvxSetup) Setup: StringToTypeParser start
2018-04-17 12:17:07 [TRACE] (MvvmCross.Core.MvxSetup) Setup: CommandHelper start
2018-04-17 12:17:07 [TRACE] (MvvmCross.Core.MvxSetup) Setup: PluginManagerFramework start
2018-04-17 12:17:07 [TRACE] (MvvmCross.Core.MvxSetup) Setup: Create App
2018-04-17 12:17:07 [TRACE] (MvvmCross.Core.MvxSetup) Setup: NavigationService
2018-04-17 12:17:07 [TRACE] (MvvmCross.Core.MvxSetup) Setup: Load navigation routes
2018-04-17 12:17:07 [TRACE] (MvvmCross.Core.MvxSetup) Setup: App start
2018-04-17 12:17:07 [TRACE] (MvvmCross.Core.MvxSetup) Setup: Application Initialize - On background thread
2018-04-17 12:17:08 [TRACE] (MvvmCross.Core.MvxSetup) Setup: ViewModelTypeFinder start
2018-04-17 12:17:08 [TRACE] (MvvmCross.Core.MvxSetup) Setup: ViewsContainer start
2018-04-17 12:17:08 [TRACE] (MvvmCross.Core.MvxSetup) Setup: Views start
2018-04-17 12:17:08 [TRACE] (MvvmCross.Core.MvxSetup) Setup: CommandCollectionBuilder start
2018-04-17 12:17:08 [TRACE] (MvvmCross.Core.MvxSetup) Setup: NavigationSerializer start
2018-04-17 12:17:08 [TRACE] (MvvmCross.Core.MvxSetup) Setup: InpcInterception start
2018-04-17 12:17:08 [TRACE] (MvvmCross.Core.MvxSetup) Setup: InpcInterception start
2018-04-17 12:17:08 [TRACE] (MvvmCross.Core.MvxSetup) Setup: LastChance start
2018-04-17 12:17:08 [TRACE] (MvvmCross.Core.MvxSetup) Setup: Secondary end
2018-04-17 12:17:08 [TRACE] (MvvmCross.Logging.MvxLog) AppStart: Application Startup - On UI thread

I traced it a little further. It is hanging up on the Initialize() in the view model. I created a test to demonstrate:

public class FirstViewModel : MvxViewModel
{

    public FirstViewModel()
    {
        Task.Run(async () =>
        {
            var l = await ListDataSource.GetLocations();
            var m = l;
        });
    }

    public async override Task Initialize()
    {
        await base.Initialize();
        var l = await ListDataSource.GetLocations();
        var m = l;
    }

}

If I set a break point on the two var m = l, It will get to the one in the constructor but will never get to the one in the Initialize. GetLocations is:

    public async static Task<LocationList> GetLocations()
    {
        ListServiceClient client = NewClient();

        LocationList ret = null;
        bool TryCache = false;

        try
        {
            //ret = await client.GetLocationListAsync();
            ret = await Task<LocationList>.Factory.FromAsync((asyncCallback, asyncState) => client.BeginGetLocationList(asyncCallback, asyncState),
               (asyncResult) => client.EndGetLocationList(asyncResult), null);

            client.Close();
            await BlobCache.LocalMachine.InsertObject("Locations", ret, DateTimeOffset.Now.AddDays(Settings.CacheDays));
        }
        catch (TimeoutException ex)
        {
            client.Abort();
            EventLog.Error(ex.ToString());
            TryCache = true;
        }
        catch (CommunicationException ex)
        {
            client.Abort();
            EventLog.Error(ex.ToString());
            TryCache = true;
        }
        catch (Exception ex)
        {
            EventLog.Error(ex.ToString());
            TryCache = true;
        }

        if (TryCache)
        {
            try
            {
                ret = await BlobCache.LocalMachine.GetObject<LocationList>("Locations");
            }
            catch (KeyNotFoundException)
            {
                ret = null;
            }
        }

        return ret;
    }

If you set a break point on Client.Close(), it will get there if called from the constructor but not if its called from Initialize.


Solution

  • My problem was twofold. First, when you upgrade to version 6.0 using Android, you now have to include a MainApplication.cs as follows:

    [Application]
    public class MainApplication : MvxAppCompatApplication<Setup, App>
    {
        public MainApplication(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
        {
        }
    }
    

    Without this, you will get stuck on the SplashScreen. Secondly, you need to know that the Initialize in the first ViewModel that you display has to be synchronous. As @Ale_lipa mentioned, an MvvmCross Author wrote a blog post explaining why this is and what to do about it.

    https://nicksnettravels.builttoroam.com/post/2018/04/19/MvvmCross-Initialize-method-on-the-first-view-model.aspx

    In a nutshell, if you are using a SplashScreen and you really need the Initialize for your first ViewModel to be Async, you can add a CustomAppStart as follows:

    public class CustomMvxAppStart<TViewModel> : MvxAppStart<TViewModel>
         where TViewModel : IMvxViewModel
    {
         public CustomMvxAppStart(IMvxApplication application, IMvxNavigationService navigationService) : base(application, navigationService)
         {
         }
    
        protected override void NavigateToFirstViewModel(object hint)
         {
             NavigationService.Navigate<TViewModel>();
         }
    }
    

    In you App.cs, replace your:

    RegisterAppStart<FirstViewModel>();
    

    with:

    RegisterCustomAppStart<CustomMvxAppStart<FirstViewModel>>();
    

    This will allow your first Initialize to be async. I am only sure it works for Android and only if you are using a SplashScreen.