Search code examples
c#xamarinmvvmpickercollectionview

How to send data from a picker IN a collectionview to an API endpoint using MVVM (Xamarin)


For my current project I am working on a cross-platform application. We are building this using Visual studio, cross platform-app. In this project we use questionaires which we set in an SQL Database.

Using MVVM I can load this dynamically into the mobile app. (We do this because there are many platforms where we show this, so we choose to set it in the back-end, the questionaire with the answers to each question).

But with saving there seems to be an issue, I can't seem to send data back to my back end -> I want to send either the value or the string showed from the picker back to the back end (preferably using MVVM). If I can get the data in the viewmodel (or code behind if MVVM would not be possible), i would be helped enough with my question.

I build my questionaire using a collectionview which contains a DataTemplate with a picker inside.

QuestionaireView.xaml:

    <ContentPage.BindingContext>
        <viewModels:Questionaire_viewmodel/>
    </ContentPage.BindingContext>
<CollectionView
                x:Name="CollectionQuestionsView"
                SelectionMode="None"
                Margin="0,0,0,0" RemainingItemsThreshold="5" 
                BackgroundColor="Transparent" 
                SelectedItems="{Binding selectedPickers}"
                ItemsSource="{Binding Questionaire.Vraag,Mode=TwoWay}">
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <StackLayout>
                            <Label Style="{StaticResource RegularLabelStyleSmallLogin}" Text="{Binding vraagstelling}" HorizontalTextAlignment="Start" VerticalTextAlignment="Start" LineBreakMode="WordWrap"/>
                            <Picker Style="{StaticResource PickRegularSmall}" SelectedItem="{Binding vraag_id}" x:Name="{Binding vraag_id}" ItemsSource="{Binding Antwoorden}" ItemDisplayBinding="{Binding antwoord_vraagstelling}"/>
                        </StackLayout>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
            <Frame Margin="0,0,0,20" Style="{StaticResource OrangeLoginFrame}">
                <Grid Margin="-15" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100*"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Label Style="{StaticResource SemiBoldLabelStyleMedium}" Grid.Row="0" Grid.Column="0" Text="Opslaan"/>
                    <Button Style="{StaticResource LoginButton}" Command="{Binding SaveVragenlijstCompetentiesCommand}" Grid.Row="0"  Grid.Column="0" />
                </Grid>
            </Frame>
            <Button x:Name="questionaireButton" Command="{Binding getQuestionaireLoopbaancompetenties}" IsVisible="False"/>

So to explain the bindings (since some names are not English):

  • itemsource of collectionview: The questionaire.vraag retrieves a collection of questions out of the questionaire object (see the model.cs down below)
  • label text binding: Displays the question
  • picker selectedItem binding: vraag id results in a string (retrieved from the backend) which here specifly is vraag_127, vraag_128 (the numbers are an id of the question)
  • itemsource picker: binding antwoorden, these are the answers to the questions
  • in the frame below the collectionview, I made a button to (at least i tried) to send the data to the ViewModel.cs
  • The (hidden)button below the frame loads the data (I execute it in the code behind, not sure if this is the best option)

QuestionaireViewModel.cs

        public string vraag_127 { get; set; }
        public string vraag_128 { get; set; }
        ... and so on
        public string vraag_161 { get; set; }

        public string categorie_vraag { get; set; }
        public string SelectedAnswer { get; set; }
        public string vraagstelling { get; set; }
        public string vraag_id { get; set; }
        public string score_antwoord { get; set; }
        public string antwoord_vraagstelling { get; set; }
        public string default_question { get; set; }
        public string description { get; set; }
private Questionaire_model _questionaire;
        public Questionaire_model Questionaire
        {
            get { return _questionaire; }
            set
            {
                _questionaire = value;
                OnPropertyChanged();
            }

        }

        private List<Questionaire_model> _vraag;
        public List<Questionaire_model> Vraag
        {
            get { return _vraag; }
            set
            {
                _vraag = value;
                OnPropertyChanged();
            }

        }



        private List<Questionaire_model> _antwoorden;
        public List<Questionaire_model> Antwoorden
        {
            get { return _antwoorden; }
            set
            {
                _antwoorden = value;
                OnPropertyChanged();
            }

        }

        public ICommand getQuestionaireLoopbaancompetenties
        {
            get
            {
                return new Command(async (boolean) =>
                {
                    Questionaire = await _apiServices.GetQuestionaireQuestions("Competenties");
                });
            }
        }
        private ObservableCollection<vragenModel> _selectedPicker;

        public ObservableCollection<vragenModel> SelectedPicker
        {
            get
            {
                return _selectedPicker;
            }
            set
            {
                _selectedPicker = value;
                OnPropertyChanged();
            }
        }
        public ICommand SaveVragenlijstCompetentiesCommand
        {
            get
            {
                return new Command(async () =>
                {

                    var responseBool = await _apiServices.SaveCultuur(vraag_127,vraag_128, ... vraag_161)});
            }
        }
public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyname = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
        }
     

And my QuestionaireModel.cs:

public class Questionaire_model
    {

        public int id { get; set; }
        public string default_question { get; set; }
        public string description { get; set; }
        public IList<Vragen> Vraag{ get; set; }

        public class Vragen
        {
            public string categorie_vraag { get; set; }
            public string SelectedAnswer { get; set; }
            public string vraagstelling { get; set; }
            public string vraag_id { get; set; }
            public IList<antwoorden> Antwoorden { get; set; }
        }
        public class antwoorden
        {
            public string score_antwoord { get; set; }
            public string antwoord_vraagstelling { get; set; }
        }
    }

The reason we use an collectionView, is because we use the template for various questionaires with various amount of questions and answer possibilities.

So at this moment, when I execute my save action, it gives "null" in my ViewModel - action I have defined public class Questionaire_viewmodel : INotifyPropertyChanged above my viewmodel

So I hope someone can either help me with my code, that I missed something here Or if I have to change something because it is simply impossible well then I would like to have an direction where to go to.


Solution

  • As I understand, you want your Picker.SelectedItem actually bind to QuestionaireViewModel.vrag_xxx properties. That won't work like this - what now happens is that your Picker.SelectedItem is bound to Vragen.vraag_id field and is not able to use it, as long as it expects antwoorden type there.

    However, I see that you have SelectedAnswer property in Vragen, you can change it to be of antwoorden type and then fetch user choice from that field. So, if your Vragen class is:

    public class Vragen
    {
        public string categorie_vraag { get; set; }
        public antwoorden SelectedAnswer { get; set; }
        public string vraagstelling { get; set; }
        public string vraag_id { get; set; }
        public IList<antwoorden> Antwoorden { get; set; }
    }
    

    Then, you can change your binding for Picker to:

    <Picker Style="{StaticResource PickRegularSmall}" SelectedItem="{Binding SelectedAnswer}" x:Name="{Binding vraag_id}" ItemsSource="{Binding Antwoorden}" ItemDisplayBinding="{Binding antwoord_vraagstelling}"/>
    

    And, at last in SaveVragenlijstCompetentiesCommand you'll be able to get array of answers (sorted by question id):

    Questionaire.Vraag.OrderBy(x => x.vraag_id).Select(x => x.SelectedAnswer.antwoord_vraagstelling)
    

    That will give you array of answer texts. Still it doesn't conform to your apiServices.SaveCultuur call. I would strongly recommend to change that method - method that contains a lot of parameters is certainly a problem.
    If you can change it to accept string[], then you will be able to pass Questionaire.Vraag.OrderBy(x => x.vraag_id).Select(x => x.SelectedAnswer.antwoord_vraagstelling) to it.
    If you can't change apiServices, there's still possibility to call the method through reflection using array, but it's a bit tricky.