简体   繁体   中英

ViewModel data is being shared for each Page while Reusing single ViewModel for different Views

This is somehow similar to the repeated questions like Bind Two Different Views for same ViewModel But I am on the AppShell to show pages and having issue related to data:

<FlyoutItem Title="News &amp; Updates" Icon="icon_feed.png" >
    <Tab x:Name="UriUpdates" Title="News &amp; Updates" >
        <ShellContent Title="News" Route="NewsPage" ContentTemplate="{DataTemplate local:NewsPage}" />
        <ShellContent Title="Notifications" Route="NotificationsPage" ContentTemplate="{DataTemplate local:NotificationsPage}" />           
    </Tab>        
</FlyoutItem>

Both pages are using a custom control of type ContentView . In the code-behind of the pages I have provided the BindingContext

BindingContext = _viewModel = new UpdatesViewModel("news");
BindingContext = _viewModel = new UpdatesViewModel("notifications");

ViewModel has a property UpdateType which is updated in constructor and its value is used in the command (ExecuteLoadItemsCommand) to fill out the list (Items):

Items = new ObservableCollection<UpdatesModel>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());

The event ExecuteLoadItemsCommand() causes the VM to fill the Data from a DataStore

public IDataStore<UpdatesModel> DataStore => DependencyService.Get<IDataStore<UpdatesModel>>();

ExecuteLoadItemsCommand is called as expected and fills the respective items every time.

async Task ExecuteLoadItemsCommand()
{
    IsBusy = true;
    try
    {
        Items.Clear();
        var items = await DataStore.GetItemsAsync(UpdateType);
        if(items!=null)
        foreach (var item in items)
        {
            Items.Add(item);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
    finally
    {
        IsBusy = false;
    }
}

GetItemsAsync is in DataStore which returns the required items list:

public async Task<IEnumerable<UpdatesModel>> GetItemsAsync(string updateType, bool forceRefresh = false)
{
    if (forceRefresh || items == null || items.Count == 0)
    {
        Uri uri = new Uri(Constants.GetUpdatesUrl(updateType));
        try
        {
            HttpResponseMessage response = await client.GetAsync(uri);
            if (response.IsSuccessStatusCode)
            {
                string content = await response.Content.ReadAsStringAsync();
                items = (List<UpdatesModel>)JsonConvert.DeserializeObject<IEnumerable<UpdatesModel>>(content);
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(@"\tERROR {0}", ex.Message);
        }
    }   
    return await Task.FromResult(items);
}

What I think is whenever items are filled for a page News/Notifications it must not call the Rest-Service again and again for each tab-change click. It should update the items when required. So for this purpose I applied a condition in the method GetItemsAsync of DataStore (which is implementation of IDataStore ) as

if (forceRefresh || items == null || items.Count == 0)
{
    //Rest Service Call to fill items list
}

The problem is items (which is a property in VM) are always non-empty and filled with 1st page data (News items). After NewsPage is loaded, I don't know why the items are not empty and are filled with NewsPage data when NotificationsPage is being loaded.

Kindly help me out whether I am having a bad practice to share VM for different views or there is some issue which is causing the pages to have data which is filled in items by 1st page?

What was the mistake, I blamed the ViewModel for causing the Data to be shared on every page for Items List which is defined in VM:

public ObservableCollection<UpdatesModel> Items { get; }

But the data was being shared because of DependencyService:

public IDataStore<UpdatesModel> DataStore => DependencyService.Get<IDataStore<UpdatesModel>>();

so it has shared code which means it is a single instance and being shared to VM.

I resolved it by introducing a Dictionary in DataStore (implementation of IDataStore for DependencyService) as

private Dictionary<string, List<UpdatesModel>> itemsDict;

Instead of

private List<UpdatesModel> items;

and changed GetItemsAsync (which returns the data from Rest Service):

public async Task<IEnumerable<UpdatesModel>> GetItemsAsync(string updateType, bool forceRefresh = false)
{
    if (itemsDict == null || itemsDict.Count == 0) itemsDict = new Dictionary<string, List<UpdatesModel>>();

    if (forceRefresh || itemsDict == null || itemsDict.Count == 0 || !itemsDict.ContainsKey(updateType))
    {
        Uri uri = new Uri(Constants.GetUpdatesUrl(updateType, startDate));
        try
        {
            HttpResponseMessage response = await client.GetAsync(uri);
            if (response.IsSuccessStatusCode)
            {
                string content = await response.Content.ReadAsStringAsync();
                itemsDict[updateType] = (List<UpdatesModel>)JsonConvert.DeserializeObject<IEnumerable<UpdatesModel>>(content);
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(@"\tERROR {0}", ex.Message);
        }
    }
    return await Task.FromResult(itemsDict[updateType]);
}

so for all distinct updateTypes passed to GetItemsAsync from ViewModel, a dictionary value ( itemsDict[updateType] ) is being returned and I don't have to refill items again and again as Dictionary is being filled only once or when forced to refill for any updateType as Key.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM