mvvmlight bindings not updating after back navigation

Background: I've modified the android mvvmlight (v5.3.0) sample from the Visual Studio template to support fragments instead of activities. A single activity (content_shell) hosts pages as fragments that are selected from the menu in a navigation drawer. Pages are loaded by swapping out fragments in the activity:

            FragmentManager fm = ActivityBase.RootActivity.FragmentManager;
            Fragment f = fm.FindFragmentById(Resource.Layout.Second) ?? new SecondFragmentActivity();
            var ft = fm.BeginTransaction();
            ft.AddToBackStack(null);
            ft.Replace(Resource.Id.content_shell, f);
            ft.Commit();

Problem: All of the mvvmlight demo functionality works fine until I navigate back from SecondPage to MainPage, at which point all of the bindings between MainActivity and MainViewModel stop updating. Reloading MainPage from the navigation drawer gets the bindings working again, I think because the fragment is completely destroyed (OnDestroy) instead of paused/resumed. Switching between landscape and portrait screen orientation does the same thing. I've tried various incantations from the web to no avail, including detaching the bindings on pause, using different overloads for the binding methods, and unregistering/registering the bindings on resume. Any help appreciated. Some additional background follows.

Back navigation is done in the ActivityBase module, modified from the sample to inherit from Fragment instead of Activity, and to add a reference to the single activity (RootActivity).

using Android.App;
using Android.Support.V7.App;

namespace MVVMTest.Droid.Views
{
    public class ActivityBase :Fragment
    {
        public static ActivityBase CurrentActivity
        {
            get;
            private set;
        }

        public static AppCompatActivity RootActivity
        {
            get;
            set;
        }

        public static void GoBack()
        {
            if (RootActivity != null)
            {
                RootActivity.OnBackPressed();
            }
        }
    }
}

MainActivity.cs, slightly modified from the sample. Commented code is some of my experimentation.

using System.Collections.Generic;
using Android.App;
using Android.OS;
using Android.Views;
using GalaSoft.MvvmLight.Helpers;
using GalaSoft.MvvmLight.Messaging;
using MVVMTest.ViewModel;
using Messenger = GalaSoft.MvvmLight.Messaging.Messenger;

namespace MVVMTest.Droid.Views
{
 //   [Activity(Label = "MVVM LIGHT SAMPLE", MainLauncher = false, Icon = "@drawable/icon")]
    public partial class MainActivity : ActivityBase
    {
        private readonly List<Binding> _bindings = new List<Binding>();
        View view;

        private MainViewModel Vm
        {
            get
            {
                return App.Locator.Main;
            }
        }

        public override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            FragmentManager.AddOnBackStackChangedListener(this);
        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            // Use this to return your custom view for this Fragment
            view = inflater.Inflate(Resource.Layout.Main, container, false);
            return view;
        }

        public override void OnViewCreated(View view, Bundle savedInstanceState)
        {
            base.OnViewCreated(view, savedInstanceState);

//                   SimpleIoc.Default.Unregister<MainViewModel>();
//                   SimpleIoc.Default.Register<MainViewModel>();

            IncrementButton.Text = Resources.GetString(Resource.String.ButtonTest);

            Messenger.Default.Register<NotificationMessageAction<string>>(
                this,
                HandleNotificationMessage);

            // Binding and commanding

            _bindings.Add(
                this.SetBinding(
                    () => Vm.WelcomeTitle,
                    () => WelcomeText.Text));

            IncrementButton.SetCommand(
                "Click",
                Vm.IncrementCommand);

            var dialogNavBinding = this.SetBinding(
                    () => DialogNavText.Text);

            _bindings.Add(dialogNavBinding);

            TapText.SetCommand(
                "Click",
                Vm.NavigateCommand,
                dialogNavBinding);

            ShowDialogButton.SetCommand(
                "Click",
                Vm.ShowDialogCommand,
                dialogNavBinding);

            _bindings.Add(this.SetBinding(
                () => Vm.Clock,
                () => ClockText.Text));

            SendMessageButton.SetCommand(
                "Click",
                Vm.SendMessageCommand);
        }

        private void HandleNotificationMessage(NotificationMessageAction<string> message)
        {
            message.Execute("Success! (from MainActivity.cs)");
        }

        public override void OnResume()
        {
//            SimpleIoc.Default.Unregister<MainViewModel>();
//            SimpleIoc.Default.Register<MainViewModel>();
//            foreach (var binding in _bindings)
//            {
//                binding.ForceUpdateValueFromSourceToTarget();
//                binding.ForceUpdateValueFromTargetToSource();
//            }
//            Vm.RaisePropertyChanged();
            Vm.StartClock();
            base.OnResume();
        }

        public override void OnPause()
        {
            // Stop the clock background thread on the MainViewModel.
            Vm.StopClock();
            base.OnPause();
        }

        public override void OnDestroy()
        {
            // Stop the clock background thread on the MainViewModel.
            Vm.StopClock();
            foreach (var binding in _bindings)
                binding.Detach();
            _bindings.Clear();
            base.OnDestroy();
        }
    }
}

MainViewModel.cs, identical to the sample. Main.axml is also identical to the sample (not shown).

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Threading;
using GalaSoft.MvvmLight.Views;
using Microsoft.Practices.ServiceLocation;
using MVVMTest.Model;

namespace MVVMTest.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        public const string ClockPropertyName = "Clock";
        public const string WelcomeTitlePropertyName = "WelcomeTitle";

        private readonly IDataService _dataService;
        private readonly INavigationService _navigationService;
        private string _clock = "Starting...";
        private RelayCommand _incrementCommand;
        private int _index;
        private RelayCommand<string> _navigateCommand;
        private bool _runClock;
        private RelayCommand<string> _showDialogCommand;
        private string _welcomeTitle = "Hello MVVM";

        public string Clock
        {
            get
            {
                return _clock;
            }
            set
            {
                Set(ref _clock, value);
            }
        }

        public RelayCommand IncrementCommand
        {
            get
            {
                return _incrementCommand
                       ?? (_incrementCommand = new RelayCommand(
                           () =>
                           {
                               WelcomeTitle = string.Format("Clicked {0} time(s)", ++_index);
                           }));
            }
        }

        public RelayCommand<string> NavigateCommand
        {
            get
            {
                return _navigateCommand
                       ?? (_navigateCommand = new RelayCommand<string>(
                           parameter => _navigationService.NavigateTo(
                               ViewModelLocator.SecondPageKey,
                               parameter)));
            }
        }

        public RelayCommand<string> ShowDialogCommand
        {
            get
            {
                return _showDialogCommand
                       ?? (_showDialogCommand = new RelayCommand<string>(
                           async text =>
                           {
                               var dialogService = ServiceLocator.Current.GetInstance<IDialogService>();
                               await dialogService.ShowMessage(
                                   "This is a message displayed by the dialog service | " + text,
                                   "Dialog sample",
                                   "OK",
                                   () =>
                                   {
                                       Debug.WriteLine("This code is only executed after the dialog closes (1)");
                                   });

                               Debug.WriteLine("This code is only executed after the dialog closes (2)");
                           },
                           text => !string.IsNullOrEmpty(text))); // This line disables the button if the text is null or empty
            }
        }

        public string WelcomeTitle
        {
            get
            {
                return _welcomeTitle;
            }
            set
            {
                Set(ref _welcomeTitle, value);
            }
        }

        public MainViewModel(
            IDataService dataService,
            INavigationService navigationService)
        {
            _dataService = dataService;
            _navigationService = navigationService;

            _dataService.GetData(
                (item, error) =>
                {
                    if (error != null)
                    {
                        // Report error here
                        return;
                    }

                    WelcomeTitle = item.Title;
                });
        }

        public void StartClock()
        {
            _runClock = true;

            Task.Run(
                async () =>
                {
                    while (_runClock)
                    {
                        DispatcherHelper.CheckBeginInvokeOnUI(
                            () =>
                            {
                                Clock = DateTime.Now.ToString("HH:mm:ss");
                            });

                        await Task.Delay(1000);
                    }
                });
        }

        public void StopClock()
        {
            _runClock = false;
        }

        private RelayCommand _sendMessageCommand;

        public RelayCommand SendMessageCommand
        {
            get
            {
                return _sendMessageCommand
                    ?? (_sendMessageCommand = new RelayCommand(
                    () =>
                    {
                        Messenger.Default.Send(
                            new NotificationMessageAction<string>(
                                "AnyNotification",
                                reply =>
                                {
                                    WelcomeTitle = reply;
                                }));
                    }));
            }
        }
    }
}
Sign In or Register to comment.