Forum Xamarin.Forms
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

Current listview switch state changing when the listview gets refresh?

SreeeeSreeee INMember ✭✭✭✭✭

I have a listview, which shows 20 users initially. Whenever the listview bottom reaches a new REST API call will start and it shows more users(20,40,60 etc). When loading more users the list gets refreshed.

Listview items have a switch option and if I press the switch option the userid of that user is added to a list. If I again press the same switch that userid is removed from the list.

My problem is the selected user's switches are going to off state when loading more users. But the userids saved in the list have no problem, the list contains the selected user's id. So how can I on the switch of already selected users after loading more users?

Thanks in advance :)

Tagged:

Best Answers

  • LandLuLandLu Member, Xamarin Team Xamurai
    Accepted Answer

    Yes this is correct to bind your switch's IsToggled to point out which user has been selected.
    Also you can define an extra property in your model. Then you can change this property directly in your OnToggledEvent instead of using a converter.

  • SreeeeSreeee INMember ✭✭✭✭✭
    Accepted Answer

    Hi @Gigex42 and @LandLu

    I created a new property like below and assign that property directly to OnToggledEvent.

    public bool isToggledUser
            {
                get
                {
                    bool toggle = false;
                    string selectedIds = Application.Current.Properties["GroupUserIds"].ToString();
                    if (!string.IsNullOrWhiteSpace(selectedIds))
                    {
                        List<int> TagIds = selectedIds.Split(',').Select(int.Parse).ToList();
                        if (TagIds.Contains(Int32.Parse(userId)))
                        {
                            toggle = true;
                        }
                        else
                        {
                            toggle = false;
                        }
                    }
                    else
                    {
                        toggle = false;
                    }
                    return toggle;
                }
            }
    

    But the problem is the switch already have toggled event Toggled="OnToggledEvent". When loading more items also it fires.

    Now I need to stop the OnToggledEvent function codes when loading more items. Is that possible?

Answers

  • LandLuLandLu Member, Xamarin Team Xamurai
    edited October 2018

    Please show your code to help us figure out what's your issue.

  • SreeeeSreeee INMember ✭✭✭✭✭
    edited October 2018

    @LandLu I am working on a feature like WhatsApp group. I can add members to the groups. When I am trying to add members to a group, a list of users UI is shown with a switch option on the right-hand side of each user. Screenshot adding below.

    When clicking on a switch I will save the selected user's id to a list using the Toggled property.
    Xaml code:

    <Switch
              Toggled="OnToggledEvent"
              HorizontalOptions="EndAndExpand"
              VerticalOptions="CenterAndExpand"/>
    

    On the OnToggledEvent() I will save the user id to a list.
    The listview shows 20 users initially. Whenever the listview bottom reaches a new REST API call will start and it shows more users(20,40,60 etc). When loading more users the list gets refreshed and the already selected switches get disabled. But the already selected user's ids are saved in a list.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @LandLu How I tried to solve this problem

    1. When loading more items I will convert the list to a comma separated string and save that comma separated string to local DB like below. Where userIds is the list.

      string selectedIds = String.Join(",", userIds);
          Application.Current.Properties["GroupUserIds"] = selectedIds;
      
    2. Using IsToggled property I bind the current userid and create a converter to check that userid is already selected or not.

      <Switch
                 Toggled="OnToggledEvent"
                 IsToggled="{Binding userProfileTO.userId, Converter={StaticResource isToggledConverter}}"
                 HorizontalOptions="EndAndExpand"
                 VerticalOptions="CenterAndExpand"/>
      
    3. In the converter, change the comma-separated string again to a list and check the current userid is exist or not in that list. If exist return true value and if not exist return false value.

         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
              {
                  bool toggle = false;
                  string selectedIds = Application.Current.Properties["GroupUserIds"].ToString();
                  if (!string.IsNullOrWhiteSpace(selectedIds))
                  {
                      List<int> TagIds = selectedIds.Split(',').Select(int.Parse).ToList();
                      if (TagIds.Contains(Int32.Parse(value.ToString())))
                      {
                          toggle = true;
                      }
                      else
                      {
                          toggle = false;
                      }
                  }
                  else
                  {
                      toggle = false;
                  }
                  return toggle;
              }
      
      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
      {
          return false;
      }
      

    After doing the above codes also no changes in the current issue. Is this approach correct?
    Thanks in advance :)

  • LandLuLandLu Member, Xamarin Team Xamurai
    Accepted Answer

    Yes this is correct to bind your switch's IsToggled to point out which user has been selected.
    Also you can define an extra property in your model. Then you can change this property directly in your OnToggledEvent instead of using a converter.

  • Gigex42Gigex42 USMember ✭✭✭✭

    Just a quess but does your ConvertBack Method get called when adding the next 20 items?

    Some items dont have oneway binding as the default. Maybe the switch is one of those.
    Maybe it has two way binding which throws then the convertback method.
    Try setting it to oneway.

  • SreeeeSreeee INMember ✭✭✭✭✭
    Accepted Answer

    Hi @Gigex42 and @LandLu

    I created a new property like below and assign that property directly to OnToggledEvent.

    public bool isToggledUser
            {
                get
                {
                    bool toggle = false;
                    string selectedIds = Application.Current.Properties["GroupUserIds"].ToString();
                    if (!string.IsNullOrWhiteSpace(selectedIds))
                    {
                        List<int> TagIds = selectedIds.Split(',').Select(int.Parse).ToList();
                        if (TagIds.Contains(Int32.Parse(userId)))
                        {
                            toggle = true;
                        }
                        else
                        {
                            toggle = false;
                        }
                    }
                    else
                    {
                        toggle = false;
                    }
                    return toggle;
                }
            }
    

    But the problem is the switch already have toggled event Toggled="OnToggledEvent". When loading more items also it fires.

    Now I need to stop the OnToggledEvent function codes when loading more items. Is that possible?

  • SreeeeSreeee INMember ✭✭✭✭✭

    @Gigex42 Convert back method giving NotImplementedException. If I remove the throw new NotImplementedException(); in my ConvertBack method, everything will work fine. Or explicitly set the binding mode to be One-way.

  • SreeeeSreeee INMember ✭✭✭✭✭

    Hi @LandLu,@Gigex42 and @Charwaka Still I have an issues with this. Can you guys help me out?
    I need to stop the OnToggledEvent function codes when loading more items. Is there any way to stop it? I will show my codes.

    Switch code:

    <Switch
              IsToggled="{Binding userProfileTO.isToggledUser}"
              Toggled="OnToggledEvent"
              HorizontalOptions="EndAndExpand"
              VerticalOptions="CenterAndExpand"/>
    

    OnToggledEvent(): When I click a switch this fuction will fire and I will take the userid of the item and save it into a list. If I again press the same item that userid will be removed from the list. Evreytime when button trigger(on or off) this function will execute. But When loading more items I need to stop the execution of this code. Because when loading more items there is a chance of toggling of switch if we already select switch, as a result that userid will be removed from the list. I need to stop that.

    void OnToggledEvent(object sender, EventArgs args)
            {
                ViewCell cell = (sender as Switch).Parent.Parent as ViewCell;
                UserProfileHBList model = cell.BindingContext as UserProfileHBList;
    
                if (model != null)
                {
                    bool adduserid = true;
                    if (userIds.Count != 0)
                    {
                        for (int i = 0; i < userIds.Count; i++)
                        {
                            if (adduserid)
                            {
                                if (model.userProfileTO.userId == userIds[i])
                                {
                                    userIds.RemoveAt(i);
                                    adduserid = false;
                                    break;
                                }
                            }
                        }
                    }
                    if (adduserid)
                    {
                        userIds.Add(model.userProfileTO.userId);
                    }
                }
            }
    

    Loading more items code:

    AddMemberList.ItemAppearing += (sender, e) =>
                { 
                    string selectedIds = String.Join(",", userIds);
                    Application.Current.Properties["GroupUserIds"] = selectedIds;
                    LoadMore(e);
                };
    

    The LoadMore(e) will call the REST API for loading more items. At this time I need to stop the OnToggledEvent() codes. But don't know how to stop it. Please help me, thanks in advance :)

  • SreeeeSreeee INMember ✭✭✭✭✭

    Hi @LandLu,@Gigex42 and @Charwaka I tried the following logic:

    I declared a bool variable and set false value initially for that.

    bool loadingItems = false;
    

    When loading more items I changed the bool variable value as true. After loading completed I changed the value to false.

     AddMemberList.ItemAppearing += (sender, e) =>
                {
                    loadingItems = true;
                    string selectedIds = String.Join(",", userIds);
                    Application.Current.Properties["GroupUserIds"] = selectedIds;
                    LoadMore(e);
                    loadingItems = false;
                };
    

    Also inside of OnToggledEvent I am checking the value of this bool variable.

    void OnToggledEvent(object sender, EventArgs args)
    {
        if (!loadingItems)
                {
            //OnToggledEvent codes, same code as the above comment
        }
    }
    

    But the problem is, the bool value returns false value when loading more items and entering inside of the OnToggledEvent() codes. As a result already saved userids from the list are removing. Is this approach correct or any other way to solve this?

  • LandLuLandLu Member, Xamarin Team Xamurai

    Did LoadMore(e); method consume the loading code? It seems this method isn't a task one, and the code after it will not wait until your request has been completed.
    Try to modify it like:

    async Task LoadMore(...)
    {
        await ...
    }
    

    And use it like:

    await LoadMore(e);
    loadingItems = false;
    
  • SreeeeSreeee INMember ✭✭✭✭✭
    edited October 2018

    Hi @LandLu : I am adding my LoadMore(e) codes and its methods.

    My LoadMore(): Where DVM is the viewmodel object.

    private void LoadMore(ItemVisibilityEventArgs e)
            {
                if (DVM.isLoading || DVM.AllItems != null && DVM.AllItems.Count == 0)
                    return;
    
                //hit bottom
                if (DVM.AllItems != null && DVM.AllItems.Count > 0)
                {
                    try
                    {
                        UserProfileHBList item = DVM.AllItems[DVM.AllItems.Count - 1];
                        if ((e.Item as UserProfileHBList).userProfileTO.userId.ToString() == item.userProfileTO.userId.ToString())
                        {
                            DVM.LoadMoreCommand.Execute(null);
                        }
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine("Exception:>" + ex);
                    }
                }
            }
    

    Viewmodel Codes:

    public ICommand LoadMoreCommand
            {
                get
                {
                    return new Command(() =>
                    {
                        AddressbookList();
                    });
                }
            }
    
        public async void AddressbookList()
        {
            try
            {
                   UserDialogs.Instance.ShowLoading("Fetching Data...");
                    //Setting values
                    HttpClient client = new HttpClient();
                    var directoryUrlResponse =await client.GetAsync("My REST API CALL"");
                    if (directoryUrlResponse.IsSuccessStatusCode)
                    {
                        string response = await directoryUrlResponse.Content.ReadAsStringAsync();
                        DirectoryResponse directoryResponse = new DirectoryResponse();
                        if (response != "")
                        {
                            directoryResponse = JsonConvert.DeserializeObject<DirectoryResponse>(response.ToString());
                            foreach (var tweet in directoryResponse.userProfileHBList)
                            {
                                if (!Items.Contains(tweet))
                                {
                                    Items.Add(tweet);
                                }
                            }
                        }
                        AllItems = new ObservableCollection<UserProfileHBList>(directoryResponse.userProfileHBList);
                        UserDialogs.Instance.HideLoading();
                    }
                    else
                    {
                        UserDialogs.Instance.HideLoading();
                        await Application.Current.MainPage.DisplayAlert("Alert", "Something went wrong at server, please try again later", "Ok");
                    }
                }
           }
            catch (Exception ex)
            {
                UserDialogs.Instance.HideLoading();
                System.Diagnostics.Debug.WriteLine("Exception:>" + ex);
            }
        }
    

    Binding AllItems to listview

     ItemsSource="{Binding AllItems,Mode=TwoWay}"
    
  • SreeeeSreeee INMember ✭✭✭✭✭

    @LandLu Can you suggest where I can place the await property. I tried like below:

    Added the loadmore value changing code after the acr userdialog code. But this also gives the wrong result. After loading more items the bool value returns true value, not changing to false.

    await Task.Delay(TimeSpan.FromSeconds(1));
    UserDialogs.Instance.HideLoading();
    Utility.loadMore = false;
    
  • LandLuLandLu Member, Xamarin Team Xamurai

    I notice you use load more command to load the data from server. Try to separate this method. I think this will help:

    private async Task LoadMore(ItemVisibilityEventArgs e)
    {
        if (DVM.isLoading || DVM.AllItems != null && DVM.AllItems.Count == 0)
            return;
    
        //hit bottom
        if (DVM.AllItems != null && DVM.AllItems.Count > 0)
        {
            try
            {
                UserProfileHBList item = DVM.AllItems[DVM.AllItems.Count - 1];
                if ((e.Item as UserProfileHBList).userProfileTO.userId.ToString() == item.userProfileTO.userId.ToString())
                {
                    HttpClient client = new HttpClient();
                    var directoryUrlResponse =await client.GetAsync("My REST API CALL"");
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception:>" + ex);
            }
        }
    }
    

    Then LoadMore() is a task method. You can use the code we talked above:

    await LoadMore(e);
    loadingItems = false;
    
  • SreeeeSreeee INMember ✭✭✭✭✭

    @LandLu said:
    I notice you use load more command to load the data from server. Try to separate this method. I think this will help:

    private async Task LoadMore(ItemVisibilityEventArgs e)
    {
        if (DVM.isLoading || DVM.AllItems != null && DVM.AllItems.Count == 0)
            return;
    
        //hit bottom
        if (DVM.AllItems != null && DVM.AllItems.Count > 0)
        {
            try
            {
                UserProfileHBList item = DVM.AllItems[DVM.AllItems.Count - 1];
                if ((e.Item as UserProfileHBList).userProfileTO.userId.ToString() == item.userProfileTO.userId.ToString())
                {
                    HttpClient client = new HttpClient();
                    var directoryUrlResponse =await client.GetAsync("My REST API CALL"");
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception:>" + ex);
            }
        }
    }
    

    Then LoadMore() is a task method. You can use the code we talked above:

    await LoadMore(e);
    loadingItems = false;
    

    After doing this also same result. When loading more items the value of bool is false.

  • LandLuLandLu Member, Xamarin Team Xamurai
    edited October 2018

    @Sreeee Need your sample to help me understand your issues.

  • CharwakaCharwaka INMember ✭✭✭✭✭
    edited October 2018

    @Sreeee Why dont you create a bool property in model and bind value ? sorry if i understood your question wrong
    Can you send a sample ZIP Code.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @Charwaka and @LandLu

    I will provide the sample zip code ASAP.

  • SreeeeSreeee INMember ✭✭✭✭✭
    edited October 2018

    @Charwaka I am using the bool value to know the loading more items scenario, at that time the code execution not go inside of the OnToggledEvent(). But how I can manage it with a bool model property?

  • CharwakaCharwaka INMember ✭✭✭✭✭

    @Sreeee said:
    @Charwaka I am using the bool value to know the loading more items scenario, at that time the code execution not go inside of the OnToggledEvent(). But how I can manage it with a bool model property?

    yes put a bool value in ObservableList Collection

    public class student
    {
    public string Name;
    Public bool isValidStudetn // use this to bind Toggle
    }

    ObservableCollection collection = new ObservableCollection();

    //Add Some Data

    Listview.ItemSource=collection; Or Bind through Front End (Mode=TwoWay)

  • SreeeeSreeee INMember ✭✭✭✭✭

    @Charwaka and @LandLu

    I created a sample project, but having some issues with that. The UI is blank, binding of REST data to listview is not working. I will fix it and provide the sample project ASAP.

  • LandLuLandLu Member, Xamarin Team Xamurai

    @Sreeee Change your loading data logic to

    AddMemberList.ItemAppearing += (sender, e) =>
    {
        if (e.Item == DVM.AllItems.LastOrDefault())
        {
            LoadMoreItems(e);
        }              
    };
    

    Fire the LoadMoreItems method when the listView has reached the last item instead of place your logic code to this method. Since ItemAppearing will always be fired if the listView has been scrolled causing an item's disappearing. Then your Utility.loadMore = false; will be run again and again so it seems this property is always false.
    And my code above will make sure LoadMoreItems(e); only fires when the list view has been scrolled to the last item. The loadMore will be true if you make a break point to check this.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @LandLu Still getting false value for loadmore when loading more items. True value is not assigning into it when loading more items.

    Following is my latest code, can you please check.

    AddMemberList.ItemAppearing += (sender, e) =>
      {
         if (e.Item == DVM.AllItems.LastOrDefault())
            {
                  LoadMoreItems(e);
             }
       }
    
      public async void LoadMoreItems(ItemVisibilityEventArgs e)
        {
            Utility.loadMore = true;
            string selectedIds = String.Join(",", userIds);
            Application.Current.Properties["GroupUserIds"] = selectedIds;
            await LoadMore(e);
            Utility.loadMore = false;
        }
    
  • LandLuLandLu Member, Xamarin Team Xamurai

    @Sreeee This will make it work if you add a break point in OnToggledEvent. But I'm confused about what you want. I've seen you have added an alert window when loading data, this will intercept user clicking the switch. And You have refreshed your allItems list every time getting data form your server. What is your userIds used for?
    I think your code logic is too complicated. When you change the switch's state, post this action to your server. Change your original data on the server to achieve what you want.

Sign In or Register to comment.