DanilGholtsman
DanilGholtsman

Reputation: 2374

MvvmCross - View Model mvxInteraction is always null

I am new to MvvmCross and Xamarin, maybe I miss something here but when I trying to pass some data from ViewModel to View like it's described here (almost)

https://www.mvvmcross.com/documentation/fundamentals/mvxinteraction

But the thing is - my MvxInteraction variable in View is always null, even though I pass there string!

   public IMvxInteraction<MenuViewModel.YesNoQuestion> Interaction
        {
            get
            {
                return _interaction;
            }
            set
            {
                if (_interaction != null) // <-- Always NULL!
                    _interaction.Requested -= OnInteractionRequested;

                _interaction = value;
                _interaction.Requested += OnInteractionRequested;
            }
        }

ViewModel code:

  public class MenuViewModel : MvxViewModel
    {
        private IDataScanner _dataScanner { get; set; }

        //demo
        public class YesNoQuestion
        {
            public string Question { get; set; }
        }

        private MvxInteraction<YesNoQuestion> _interaction = new MvxInteraction<YesNoQuestion>();
        // need to expose it as a public property for binding (only IMvxInteraction is needed in the view)
        public IMvxInteraction<YesNoQuestion> Interaction
        {
            get
            {
                return _interaction;
            }
        }


        private void DoFinishProfileCommand()
        {
            // 1. do cool stuff with profile data
            // ...

            // 2. request interaction from view
            // 3. execution continues in callbacks
            var request = new YesNoQuestion
            {
                Question = "Do you want to save your profile?"
            };

            _interaction.Raise(request);
        }
        // demo

        public class DataEventArgs : EventArgs
        {
            public List<string> DataList;
        }

        public delegate void ScanDoneEvent(object sender, DataEventArgs args);
        public event ScanDoneEvent ScanDone;

        protected virtual void OnScanDone(List<string> dataList)
        {
            ScanDone?.Invoke(this, new DataEventArgs { DataList = dataList });
        }

        public MenuViewModel(IDataScanner dataScanner)
        {
            _dataScanner = dataScanner;

            RunScan();
        }

        private ObservableCollection<string> _filesCollection;
        public ObservableCollection<string> FilesCollection
        {
            get { return _filesCollection; }
            set
            {
                _filesCollection = value;
                RaisePropertyChanged(() => FilesCollection);
            }
        }

        private async void RunScan()
        {
            var files = await _dataScanner.GetDataListAsync().ConfigureAwait(false);
            FilesCollection = new ObservableCollection<string>(files);
            DoFinishProfileCommand();
        }

    }

View code:

  [Activity]
    public class MenuView : MvxActivity
    {
        public MenuView()
        {
            var set = this.CreateBindingSet<MenuView, MenuViewModel>();
            set.Bind(this).For(view => view.Interaction).To(viewModel => viewModel.Interaction).OneWay();
            set.Apply();
        }
        protected override void OnViewModelSet()
        {
            SetContentView(Resource.Layout.menu_view);
        }

        //demo
        private IMvxInteraction<MenuViewModel.YesNoQuestion> _interaction;
        public IMvxInteraction<MenuViewModel.YesNoQuestion> Interaction
        {
            get
            {
                return _interaction;
            }
            set
            {
                if (_interaction != null)// <-- Always NULL!
                    _interaction.Requested -= OnInteractionRequested;

                _interaction = value;
                _interaction.Requested += OnInteractionRequested;
            }
        }

        private async void OnInteractionRequested(object sender, MvxValueEventArgs<MenuViewModel.YesNoQuestion> eventArgs)
        {
            var yesNoQuestion = eventArgs.Value.Question;
            // show dialog
            Toast.MakeText(this, yesNoQuestion, ToastLength.Long).Show();
        }


    }

UPD 09.11.2017

Finally make it work in the way I wanted to.

Instead of MvxInteraction I just simply used service, as nmilcoff (thanks much for the answer btw) advised, and when I called toast from it wrap it in runOnUiThread.

So, final code is here.

ToastService:

public class ToastService : IToastService
    {
        private readonly IMvxAndroidCurrentTopActivity _topActivity;

        public ToastService(IMvxAndroidCurrentTopActivity topActivity)
        {
            _topActivity = topActivity;
        }

        public void ShowToast(string message)
        {
            _topActivity.Activity.RunOnUiThread(
                () => Toast.MakeText(_topActivity.Activity.ApplicationContext, message, ToastLength.Long).Show()
            );
        }
    }

MenuViewModel:

 public class MenuViewModel : MvxViewModel
    {
        private IDataScanner _dataScanner { get; set; }
        private IToastService _toastService { get; set; }

        public MenuViewModel(IDataScanner dataScanner, IToastService toastService)
        {
            _dataScanner = dataScanner;
            _toastService = toastService;
        }

        public override void ViewAppeared()
        {
            base.ViewAppeared();
            RunScan();
        }

        private ObservableCollection<string> _filesCollection;
        public ObservableCollection<string> FilesCollection
        {
            get { return _filesCollection; }
            set
            {
                _filesCollection = value;
               // RaisePropertyChanged(() => FilesCollection);

            }
        }

        private async void RunScan()
        {
            var files = await _dataScanner.GetDataListAsync().ConfigureAwait(false);
            FilesCollection = new ObservableCollection<string>(files);
            //  Someval = files[0];
           _toastService.ShowToast(files[0]);
        }

    }

Upvotes: 0

Views: 1294

Answers (1)

nmilcoff
nmilcoff

Reputation: 1084

You might find the StarWarsSample interesting, as it uses MvxInteraction.

As you can see in that app, you need to declare the bindings later in your View code. Can you move the Fluent Binding block to the Activity's OnCreate(Android.OS.Bundle bundle) method? Something like this:

protected override void OnCreate(Android.OS.Bundle bundle)
{
    base.OnCreate(bundle);

    var set = this.CreateBindingSet<MenuView, MenuViewModel>();
    set.Bind(this).For(view => view.Interaction).To(viewModel => viewModel.Interaction).OneWay();
    set.Apply();
}

But that probably won't work because you're requesting the MvxInteraction too soon in your code. Therefore I recommend you to use a Dependency Service instead:

1) Create an interface at Core level with a method that displays a toast:

public interface IToastService
{
    void ShowToast(string message);
}

2) Create an implementation of the service at platform level:

public class ToastService : IToastService
{
    private readonly IMvxAndroidCurrentTopActivity _topActivity;

    public ToastService(IMvxAndroidCurrentTopActivity topActivity)
    {
        _topActivity = topActivity;
    }

    public void ShowToast(string message)
    {
        Toast.MakeText(activity.ApplicationContext, message, ToastLength.Short).Show();
    }
}

3) Register your service with the IoC Container. Something like this (inside the Setup class of your platform project):

protected override void InitializeLastChance()
{
        base.InitializeLastChance();

        Mvx.RegisterType<IToastService, ToastService>();
}

That's it. You're ready to inject/resolve your IToastService wherever you want!

Upvotes: 2

Related Questions