0

My aim is to access bindable property across the the App. But My current framework ViewModel Instance create multiple time

My Requirement : I have the cart count in the bottomTray(CheckuoutViewModel) i want to increase the cart count any where in the app page, but in this cart count not update when back click, its only working on forward navigation, the reason behind CheckoutViewModel instance create each and every time. so that i'm try to instant creation at earlier.

Here I'm list out sample ViewModel and calling method

  1. Login ViewModel
  2. Checkuout ViewModel(This view model common for all page)
  3. BaseNavigationViewModel(Its BaseViewModel)

As of now i'm calling when BindinContext each and every time like,

  • new LoginViewMode(navigation)
  • new CheckoutViewModel(navigation) what will do to create all ViewModel instance when app start time like ViewModel Locator?

Im tried

public static ViewModelLocator Locator
    {
        get { return locator ?? (locator = new ViewModelLocator()); }
    }

And ViewModel Locator

public ViewModelLocator()
    {
        navigation = App.Current.MainPage.Navigation;
    }
internal CustomTabBarViewModel CustomTabBarVM
    {
        get
        {
            return customTabBarVM ?? (customTabBarVM = new CustomTabBarViewModel(navigation));
        }
    }

And CustomTabBar.xaml.cs

public CustomTabBar()
    {
        viewModel = App.Locator.CustomTabBarVM;
        InitializeComponent();
        BindingContext = viewModel;
    }

and Expectation

App.Locator.CustomTabBarVM.BadgeCartCount = OrderObject.Instance.ORDER_OBJECT.Items.Count;

This approach is working fine but it's create some navigation issues

  • I don't see a problem with the current approach why exactly do you wanna make this change? Are you sure making a common instance won't create issues? – FreakyAli Aug 02 '21 at 10:15
  • Yes, But i need common viewmodel so that i will use BaseViewModel property across the app. but current approach BaseViewModel always re-create instance. – SheikMydeenMuthu Aug 02 '21 at 10:20
  • 1
    Can you say more about what you will do with this common BaseViewModel property? It *might* be that it is not the ViewModel itself that you need in common, but rather than all the ViewModels should contain a property that refers to that common information. Regardless, show the method in which you cann `new LoginViewModel` - then someone can tell you how to change it. – ToolmakerSteve Aug 02 '21 at 10:25
  • Hi @ToolmakerSteve , I want show count in bottomTray, count Bindable property available in CheckoutViewModel(BaseViewModel) Count increase page i just click it not work, forward navigation is working fine. reason behind instant create each every time. – SheikMydeenMuthu Aug 02 '21 at 10:36
  • Ok. Show the complete method in which you call `new CheckoutViewModel(navigation). Specifically, I need to see how a new page uses this. Is it in the constructor, in the page's code behind? Is LoginViewModel used on the same page, or is that on a different page? – ToolmakerSteve Aug 02 '21 at 10:53
  • Bellow code is BaseNavigationMode `public BaseNavigationViewModel(INavigation navigation) { InitializeCommands(); this.navigation = navigation; }` CheckOutViewModel like `class CheckoutTabBarViewModel : BaseNavigationViewModel { public CheckoutTabBarViewModel(INavigation navigation) : base(navigation) { SetTabSelected(); } }` And instance create time `new CheckOutViewModel(naviagtion)` here (navigation) is my biggest problem – SheikMydeenMuthu Aug 02 '21 at 11:09
  • @ToolmakerSteve how to create instant with navigation on initial app load time – SheikMydeenMuthu Aug 02 '21 at 11:11
  • It doesn't have to be during initial app load time. **Set it the first place that you use `navigation`.** If that is code that gets called multiple times, then do what I showed in a comment on Axemasta's answer: `if (App.CheckoutViewModelInstance == null) App.CheckoutViewModelInstance = new CheckoutViewModel(navigation);` – ToolmakerSteve Aug 02 '21 at 11:20
  • Add to the question a description of when navigation does work, and when it does not work. Exact steps, what you expect to happen, what happens instead. Make sure you show all code involved in those steps. – ToolmakerSteve Aug 02 '21 at 11:27
  • @ToolmakerSteve Kindly check my updated Question – SheikMydeenMuthu Aug 02 '21 at 11:36
  • "some navigation issues"? – ToolmakerSteve Aug 02 '21 at 12:03
  • @ToolmakerSteve When login time its land homePage and automatically Popasync() to LoginPage – SheikMydeenMuthu Aug 02 '21 at 12:06
  • 1
    Hi @SheikMydeenMuthu did the answers below solve your problem ? If it does could you select one and mark it as answer ,it's helpful to other members. – ColeX Aug 05 '21 at 05:18

2 Answers2

2

A singleton instance is a common feature of virtually all MVVM frameworks (Prism, FreshMVVM etc). If you aren't using a framework (if you aren't, I would STRONGLY advise you consider using one), below is a solution.

To obtain a single instance of a ViewModel you can use the App class to host the object and access it whenever you need.

  • Create a public static property of your ViewModel:
public static MyViewModel MyViewModelInstance { get; }
  • Create an instance in the constructor of the app
public App()
{
    InitializeComponent();

    MyViewModelInstance = new MyViewModel();

    var myPage = new MyPage()
    {
        BindingContext = MyViewModelInstance
    };

    var navPage = new NavigationPage(myPage);

    MainPage = navPage;
}
  • Whenever you create a new page, access the shared instance
// This method is just an example of how you might create a new page and wire up the view model
async void GoNextClicked(System.Object sender, System.EventArgs e)
{
    var myPage = new MyPage()
    {
        BindingContext = App.MyViewModelInstance
    };

    await this.Navigation.PushAsync(myPage);
}

This approach comes with a few caveats, you are creating instances when the app loads not when they are needed (Eagerly loading). So a performance optimisation would be to use Lazy<T> to handle the creation of these objects. However this is logic that has already been written for you in MVVM frameworks, they are there to help you and you should be using them.

Lazy Load

You can save memory and performance at startup by lazy loading the viewmodel, here is this example rewritten to support this pattern:

public static MyViewModel MyViewModelInstance
{
    get => _myViewModelInstanceFactory.Value;
}

private static Lazy<MyViewModel> _myViewModelInstanceFactory = new Lazy<MyViewModel>(() => new MyViewModel(), true);

public App()
{
    InitializeComponent();

    var myPage = new MyPage()
    {
        BindingContext = MyViewModelInstance
    };

    var navPage = new NavigationPage(myPage);

    MainPage = navPage;
}

Now this object won't be created until it is accessed by your code, and once it has been accessed once it has already been created and will go on to live in memory for the rest of your apps lifecycle.

Axemasta
  • 763
  • 1
  • 9
  • 24
  • 1
    Nice explanation. FWIW, given that VM's are generally only used on UI thread, so no multi-thread concerns, I would do the "old-school" hand-written "lazy" initialization (because it doesn't require set-up elsewhere): `.. MyViewModelInstance { get { if (_myVM == null) ( _myVM = new MainViewModel(); } return _myVM; }` where `private static MyViewModelInstance _myVM;`. – ToolmakerSteve Aug 02 '21 at 10:38
  • 1
    I'm Bit confusing bellow line of code `async void GoNextClicked(System.Object sender, System.EventArgs e) { var myPage = new MyPage() { BindingContext = App.MyViewModelInstance }; await this.Navigation.PushAsync(myPage); }` kindle explain more detail about this code? – SheikMydeenMuthu Aug 02 '21 at 10:44
  • @SheikMydeenMuthu - the important point of this answer is `public static LoginViewModel LoginViewModelInstance{ get; }`. The rest of the answer is showing ideas about how you set that and use it. Bottom line is: set it once, somewhere. Then do `BindingContext = App.LoginViewModelInstance` instead of `BindingContext = new LoginViewModel()`. – ToolmakerSteve Aug 02 '21 at 10:45
  • 1
    @SheikMydeenMuthu I've added a little clarification to the code in the answer, its just pseudo code to show you an example of how you should be creating a new page instance using the shared view model. – Axemasta Aug 02 '21 at 10:48
  • 1
    Thanks for adding the example of Lazy using a static Factory. That's much cleaner than the example in the docs, because it can be in the code next to the property. After seeing that, I will consider using the Lazy class in the future. – ToolmakerSteve Aug 02 '21 at 10:58
  • 1
    @ToolmakerSteve I usually provide a method instead of using a lambda, but the more the use it the more you couldn't imagine life without it. I've refactored a-lot of code to use lazy loading and it has improved startup times and memory allocation tenfold. – Axemasta Aug 02 '21 at 11:21
0

Axemasta has good answer about re-use of a shared view model instance.

I'll give an alternative approach to the underlying need given in one comment: how to have a static property (so the value is common), and Bind to it when Binding to an instance.

Use this approach if you do want a different CheckoutViewModel for each new page. For example, if there are other properties that should be set up differently, depending on the page.

public class CheckoutViewModel :  : INotifyPropertyChanged  // or your MVVM library's base class for ViewModels.
{
    public static int SharedCount { get; set; }

    public void IncrementCount()
    {
        Count = Count + 1;
    }

    public int Count {
        get => SharedCount;
        set {
                // Exact code might be simpler if using an MVVM library.
                if (SharedCount != value)
                {
                    SharedCount = value;
                    OnPropertyChanged("Count");
                }
            }
        }
    }
}

LIMITATION: This assumes that only the current instance of CheckoutViewModel is visible; if you need to "notify" OTHER Views (update other CheckoutViewModel instances), then you'll need a "publish/subscribe" solution. In Xamarin Forms, one such solution is MessagingCenter.

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196