Forum Xamarin.Forms

Announcement:

The Xamarin Forums have officially moved to the new Microsoft Q&A experience. Microsoft Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

To create new threads and ask questions head over to Microsoft Q&A for .NET and get involved today.

How to create a new table of items

mndevelopmentsmndevelopments Member ✭✭
edited November 2020 in Xamarin.Forms

Hello

This is most likely a really newbie question, but I don't really understand how to load/create a new table besides the standard one that comes with the AppShell template.

I've tried recreating the existing one from the template, but I'm missing something, so I'm hoping someone can give me some pointers.

My XAML for the collection view that I want to load the items into, looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:LommeKonstruktoren.ViewModels"
    x:Class="LommeKonstruktoren.Views.ItemsUValuePage"
    xmlns:model="clr-namespace:LommeKonstruktoren.Models"
    Title="{Binding Title}">


    <RefreshView x:DataType="local:ItemsUValueViewModel" Command="{Binding LoadItemsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
        <ListView ItemsSource="{Binding UValueItems}" SelectionMode="None">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackLayout x:DataType="model:UValueItems">
                            <StackLayout>
                                <Label Text="{Binding Name}" LineBreakMode="NoWrap" Style="{DynamicResource ListItemTextStyle}" FontSize="16" />
                                <Label Text="{Binding UValue, StringFormat='λ = {0}'}" LineBreakMode="NoWrap" VerticalOptions="CenterAndExpand" HorizontalOptions="EndAndExpand" Style="{DynamicResource ListItemDetailTextStyle}" FontSize="13" />
                            </StackLayout>
                    </StackLayout>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </RefreshView>
</ContentPage>

And the ViewModel looks like this:

namespace LommeKonstruktoren.ViewModels
{
    public class ItemsUValueViewModel : BaseViewModel
    {
        private UValueItems _selectedItem;

        public ObservableCollection<UValueItems> UValueItems { get; }
        public Command LoadItemsCommand { get; }
        public Command AddItemCommand { get; }



        public ItemsUValueViewModel()
        {
            Title = "U-værdier";
            UValueItems = new ObservableCollection<UValueItems>();
            LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());



        }

        async Task ExecuteLoadItemsCommand()
        {

            try
            {

                UValueItems.Clear();
                var uvalues = await DataStoreU.GetUValueItemAsync(true);

                foreach (var uvalue in uvalues)
                {
                    UValueItems.Add(uvalue);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                IsBusy = false;
            }


        }

        public void OnAppearing()
        {
            IsBusy = true;
            SelectedItem = null;
        }

I then added a new interface to the IDataStore like this:

namespace LommeKonstruktoren.Services
{
    public interface IDataStore<T>
    {
        Task<bool> AddItemAsync(T item);
        Task<bool> UpdateItemAsync(T item);
        Task<bool> DeleteItemAsync(string id);
        Task<T> GetItemAsync(string id);
        Task<IEnumerable<T>> GetItemsAsync(bool forceRefresh = false);


    }

    public interface IDataStoreU<U>
    {
        Task<bool> AddUValueItemAsync(U uvalue);
        Task<bool> UpdateItemAsync(U uvalue);
        Task<U> GetUValueItemAsync(string id);
        Task<IEnumerable<U>> GetUValueItemAsync(bool forceRefresh = false);

    }
}

I also added a new MockDataStore where I created some random content.

namespace LommeKonstruktoren.Services
{
    public class MockDataStoreUValues : IDataStoreU<UValueItems>
    {

        readonly List<UValueItems> uvalues;

        public MockDataStoreUValues()
        {
            uvalues = new List<UValueItems>()
            {
                new UValueItems {Id = Guid.NewGuid().ToString(), Name = "Konstruktion 1", UValue = 1.1 },
                new UValueItems {Id = Guid.NewGuid().ToString(), Name = "Konstruktion 2", UValue = 1.2 },
                new UValueItems {Id = Guid.NewGuid().ToString(), Name = "Konstruktion 3", UValue = 1.3 },
                new UValueItems {Id = Guid.NewGuid().ToString(), Name = "Konstruktion 4", UValue = 1.4 }
            };
        }

        // ADD ITEMS



        public async Task<bool> AddUValueItemAsync(UValueItems uvalueitem)
        {
            uvalues.Add(uvalueitem);

            return await Task.FromResult(true);
        }

        //UPDATE ITEMS


        public async Task<bool> UpdateItemAsync(UValueItems uvalue)
        {
            var oldItem = uvalues.Where((UValueItems arg) => arg.Id == uvalue.Id).FirstOrDefault();
            uvalues.Remove(oldItem);
            uvalues.Add(uvalue);

            return await Task.FromResult(true);
        }


        //GET ITEMS


        public async Task<UValueItems> GetUValueItemAsync(string id)
        {
            return await Task.FromResult(uvalues.FirstOrDefault(s => s.Id == id));
        }

        public async Task<IEnumerable<UValueItems>> GetUValueItemAsync(bool forceRefresh = false)
        {
            return await Task.FromResult(uvalues);
        }

Can anyone see/tell me what I might be missing? - I'm not getting any errors, but the collection view shows up empty.

Best Answer

  • mndevelopmentsmndevelopments Member ✭✭
    Accepted Answer

    @jezh Okay, so I figured out a solution.

    I made a seperate IDataStoreU.cs, and then I added the new dependency in my app.xaml.cs like this:

     public App()
            {
                InitializeComponent();
    
                if (UseMockDataStore)
                {
                    DependencyService.Register<MockDataStore>();
                    DependencyService.Register<MockDataStoreUValues>();
                }
                else
                {
                    DependencyService.Register<AzureDataStore>();
                }
                MainPage = new AppShell();
            }
    

    This seemed to work, but I needed to do way more extra coding from what I expected, so I'm suspecting that there is a better way of doing what I'm doing.

    Marking the question as solved for now.

Answers

  • jezhjezh Member, Xamarin Team Xamurai
    edited November 2020

    Have you set the BindingContext for you page?

    For example , we should add the following code to the xaml file of your page :

    <ContentPage.BindingContext>
        <local:ItemsUValueViewModel ></local:ItemsUValueViewModel>
    </ContentPage.BindingContext>
    

    Of course, you can also set the BindingContext in file YourPage.xaml.cs

    In addition, you should recheck to see if you get the 'UValueItems' correctly.

  • mndevelopmentsmndevelopments Member ✭✭
    edited November 2020

    @jezh Oh sorry, I didn't include my codebehind. Yes, I'm setting the binding context in that like this:

    ItemsUValueViewModel _viewModel;
    
            public ItemsUValuePage()
            {
                InitializeComponent();
                BindingContext = _viewModel = new ItemsUValueViewModel();
            }
    
            protected override void OnAppearing()
            {
                base.OnAppearing();
                _viewModel.OnAppearing();
            }
    

    I've tried checking if I'm getting the UValueItems correctly, but from what I can tell, I'm doing the exact same thing.

    Can you elaborate on where and what I should check for?

    Should I insert my 'MockDataUvalues' into the normal MockDataStore? When I did that at first, I got an error because the app tried to load my 'UValueItems' model into an 'Item' model list, but I'm not sure if the correct thing is to create a new MockDataStore.

    And I also wasn't sure if I could create an 'IDataStoreU', but without it I got the error that I was trying to get a list of the Item model, and not the UValuesItems.

    Is there any code I can upload for you to help me out a bit more?

  • jezhjezh Member, Xamarin Team Xamurai
    edited November 2020

    According to the code you posted, I couldn't reproduce this problem. What's the DataStoreU? And how did you use the MockDataUvalues?

    If it is convenient for you, could you please post more code snippets or a basic demo so that we can try to test on our side?

  • mndevelopmentsmndevelopments Member ✭✭
    edited November 2020

    @jezh Yes I'll try posting some more snippets, if that doesn't help I'll try posting a demo.

    The IDataStoreU is just another interface in my IDataStore Service, because the current one only accepts list from the Item model. (Maybe I should create an entirely new service instead?

    The IDataStore.cs looks like this:

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using LommeKonstruktoren.Models;
    
    namespace LommeKonstruktoren.Services
    {
        public interface IDataStore<T>
        {
            Task<bool> AddItemAsync(T item);
            Task<bool> UpdateItemAsync(T item);
            Task<bool> DeleteItemAsync(string id);
            Task<T> GetItemAsync(string id);
            Task<IEnumerable<T>> GetItemsAsync(bool forceRefresh = false);
    
    
        }
    
        public interface IDataStoreU<U>
        {
            Task<bool> AddUValueItemAsync(U uvalue);
            Task<bool> UpdateUValueItemAsync(U uvalue);
            Task<U> GetUValueItemAsync(string id);
            Task<IEnumerable<U>> GetUValueItemsAsync(bool forceRefresh = false);
    
        }
    }
    

    The MockDataUValues is basically just a copy of the 'MockDataStore.cs' service that's in the AppShell template.

    My version looks like this:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using LommeKonstruktoren.Models;
    
    namespace LommeKonstruktoren.Services
    {
        public class MockDataStoreUValues : IDataStoreU<UValueItems>
        {
    
            readonly List<UValueItems> uvalues;
    
            public MockDataStoreUValues()
            {
                uvalues = new List<UValueItems>()
                {
                    new UValueItems {Id = Guid.NewGuid().ToString(), Name = "Konstruktion 1", UValue = 1.1 },
                    new UValueItems {Id = Guid.NewGuid().ToString(), Name = "Konstruktion 2", UValue = 1.2 },
                    new UValueItems {Id = Guid.NewGuid().ToString(), Name = "Konstruktion 3", UValue = 1.3 },
                    new UValueItems {Id = Guid.NewGuid().ToString(), Name = "Konstruktion 4", UValue = 1.4 }
                };
            }
    
            // ADD ITEMS
    
    
    
            public async Task<bool> AddUValueItemAsync(UValueItems uvalueitem)
            {
                uvalues.Add(uvalueitem);
    
                return await Task.FromResult(true);
            }
    
            //UPDATE ITEMS
    
    
            public async Task<bool> UpdateUValueItemAsync(UValueItems uvalue)
            {
                var oldItem = uvalues.Where((UValueItems arg) => arg.Id == uvalue.Id).FirstOrDefault();
                uvalues.Remove(oldItem);
                uvalues.Add(uvalue);
    
                return await Task.FromResult(true);
            }
    
    
            //GET ITEMS
    
    
            public async Task<UValueItems> GetUValueItemAsync(string id)
            {
                return await Task.FromResult(uvalues.FirstOrDefault(s => s.Id == id));
            }
    
            public async Task<IEnumerable<UValueItems>> GetUValueItemsAsync(bool forceRefresh = false)
            {
                return await Task.FromResult(uvalues);
            }
        }
    }
    

    I then use the GetUValueItemsAsync command to load the the table in my ViewModel (The ItemSelected does not refer to the correct page yet):

    using System;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using LommeKonstruktoren.Models;
    using LommeKonstruktoren.Views;
    using Xamarin.Forms;
    
    namespace LommeKonstruktoren.ViewModels
    {
        public class ItemsUValueViewModel : BaseViewModel
        {
            private UValueItems _selectedItem;
    
            public ObservableCollection<UValueItems> UValueItems { get; }
            public Command LoadItemsCommand { get; }
            public Command AddItemCommand { get; }
            public Command<UValueItems> ItemTapped { get; }
    
    
            public ItemsUValueViewModel()
            {
                Title = "U-værdier";
                UValueItems = new ObservableCollection<UValueItems>();
                LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
    
                ItemTapped = new Command<UValueItems>(OnItemSelected);
    
            }
    
            async Task ExecuteLoadItemsCommand()
            {
    
                try
                {
    
                    UValueItems.Clear();
                    var uvalues = await DataStoreU.GetUValueItemsAsync(true);
    
                    foreach (var uvalue in uvalues)
                    {
                        UValueItems.Add(uvalue);
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                }
                finally
                {
                    IsBusy = false;
                }
    
    
            }
    
            public void OnAppearing()
            {
                IsBusy = true;
                SelectedItem = null;
            }
    
            public UValueItems SelectedItem
            {
                get => _selectedItem;
                set
                {
                    SetProperty(ref _selectedItem, value);
                    OnItemSelected(value);
                }
            }
    
            async void OnItemSelected(UValueItems uvalue)
            {
                if (uvalue == null)
                    return;
    
                // This will push the ItemDetailPage onto the navigation stack
                await Shell.Current.GoToAsync($"{nameof(ItemDetailPage)}?{nameof(ItemDetailViewModel.ItemId)}={uvalue.Id}");
            }
    
        }
    
    
    } 
    

    The codes I use above, is "copies" of the items that's loaded in the template where MockDataStore.cs is my MockDataStureUValues.cs. Then in my IDataStore.cs I created af new interface called IDataStoreU, in order to call the new model 'UValuesItems', instead of the existing one called 'Item'. - I'm unsure if this is wrong.

    In my BaseViewModel I create the dependencies (I'm unsure if I should implement the new table differently:

    public IDataStore<Item> DataStore => DependencyService.Get<IDataStore<Item>>();
            public IDataStoreU<UValueItems> DataStoreU => DependencyService.Get<IDataStoreU<UValueItems>>();
    
            bool isBusy = false;
            public bool IsBusy
            {
                get { return isBusy; }
                set { SetProperty(ref isBusy, value); }
            }
    
            string title = string.Empty;
            public string Title
            {
                get { return title; }
                set { SetProperty(ref title, value); }
            }
    
            public bool IsBusyU
            {
                get { return isBusy; }
                set { SetPropertyU(ref isBusy, value); }
            }
    
            string titleU = string.Empty;
            public string TitleU
            {
                get { return titleU; }
                set { SetPropertyU(ref titleU, value); }
            }
    
            protected bool SetPropertyU<U>(ref U backingStore, U value,
                [CallerMemberName] string propertyName = "",
                Action onChanged = null)
            {
                if (EqualityComparer<U>.Default.Equals(backingStore, value))
                    return false;
    
                backingStore = value;
                onChanged?.Invoke();
                OnPropertyChanged(propertyName);
                return true;
            }
    
            protected bool SetProperty<T>(ref T backingStore, T value,
                [CallerMemberName] string propertyName = "",
                Action onChanged = null)
            {
                if (EqualityComparer<T>.Default.Equals(backingStore, value))
                    return false;
    
                backingStore = value;
                onChanged?.Invoke();
                OnPropertyChanged(propertyName);
                return true;
            }
    
  • jezhjezh Member, Xamarin Team Xamurai
    public IDataStore<Item> DataStore => DependencyService.Get<IDataStore<Item>>();
    public IDataStoreU<UValueItems> DataStoreU => DependencyService.Get<IDataStoreU<UValueItems>>();
    

    I don't understand why you are using DependencyService here?

    As we all know, the DependencyService class is a service locator that enables Xamarin.Forms applications to invoke native platform functionality from shared code.

    But here I couldn't find that you invoked native platform functionality from shared code.

    Besides, when you called the following code, code var uvalues = await DataStoreU.GetUValueItemsAsync(true); should have no data , you can debug step to step to recheck this.

          LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
    
            async Task ExecuteLoadItemsCommand()
            {
    
                try
                {
    
                    UValueItems.Clear();
                    var uvalues = await DataStoreU.GetUValueItemsAsync(true);
    
                    foreach (var uvalue in uvalues)
                    {
                        UValueItems.Add(uvalue);
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                }
                finally
                {
                    IsBusy = false;
                }
            }
    
  • mndevelopmentsmndevelopments Member ✭✭
    edited November 2020

    @jezh The Dependyservice is used in the AppShell template, so I've just tried to duplicate the current code.

    The exact same code is calling a table of items that are In the MockDataStore.cs file, which is what I don't understand.

    If you open up the AppShell template with an ASP .net core Web API project, I get an app with a list of items from the MockDataStore.cs file, which then also saves to the phone and Azure. This all works fine, I just want to be able to create multiple tables in this template.

    Could you perhaps give me an example of how this is implemented?

    **Basically what I want is: **

    Two listviews with separate data, using the AppShell template - If I could figure out how to do that, I would be able to figure out the rest my self and implement it in my app.

    I tried playing around with an empty AppShell template, but no luck so far.

  • mndevelopmentsmndevelopments Member ✭✭
    Accepted Answer

    @jezh Okay, so I figured out a solution.

    I made a seperate IDataStoreU.cs, and then I added the new dependency in my app.xaml.cs like this:

     public App()
            {
                InitializeComponent();
    
                if (UseMockDataStore)
                {
                    DependencyService.Register<MockDataStore>();
                    DependencyService.Register<MockDataStoreUValues>();
                }
                else
                {
                    DependencyService.Register<AzureDataStore>();
                }
                MainPage = new AppShell();
            }
    

    This seemed to work, but I needed to do way more extra coding from what I expected, so I'm suspecting that there is a better way of doing what I'm doing.

    Marking the question as solved for now.

Sign In or Register to comment.