locked
Current listview switch state changing when the listview gets refresh? RRS feed

  • Question

  • User351573 posted

    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 :)

    Tuesday, October 2, 2018 7:44 AM

Answers

  • User369979 posted

    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.

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Thursday, October 4, 2018 5:57 AM
  • User351573 posted

    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?

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Friday, October 5, 2018 5:43 AM

All replies

  • User369979 posted

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

    Wednesday, October 3, 2018 8:56 AM
  • User351573 posted

    @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.

    Thursday, October 4, 2018 5:35 AM
  • User351573 posted

    @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 :)

    Thursday, October 4, 2018 5:52 AM
  • User369979 posted

    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.

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Thursday, October 4, 2018 5:57 AM
  • User191123 posted

    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.

    Thursday, October 4, 2018 6:26 AM
  • User351573 posted

    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?

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Friday, October 5, 2018 5:43 AM
  • User351573 posted

    @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.

    Friday, October 5, 2018 5:46 AM
  • User351573 posted

    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 :)

    Tuesday, October 9, 2018 5:54 AM
  • User351573 posted

    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?

    Tuesday, October 9, 2018 6:04 AM
  • User369979 posted

    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;
    
    Tuesday, October 9, 2018 9:57 AM
  • User351573 posted

    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}"
    
    Tuesday, October 9, 2018 10:36 AM
  • User351573 posted

    @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;
    
    Thursday, October 11, 2018 9:33 AM
  • User369979 posted

    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;
    
    Thursday, October 11, 2018 9:52 AM
  • User351573 posted

    @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.

    Friday, October 12, 2018 7:54 AM
  • User369979 posted

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

    Friday, October 12, 2018 8:51 AM
  • User155041 posted

    @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.

    Friday, October 12, 2018 10:31 AM
  • User351573 posted

    @Charwaka and @LandLu

    I will provide the sample zip code ASAP.

    Friday, October 12, 2018 11:08 AM
  • User351573 posted

    @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?

    Friday, October 12, 2018 12:51 PM
  • User155041 posted

    @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)

    Friday, October 12, 2018 1:00 PM
  • User351573 posted

    @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.

    Saturday, October 13, 2018 11:21 AM
  • User369979 posted

    @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.

    Monday, October 15, 2018 9:35 AM
  • User351573 posted

    @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;
        }
    
    Monday, October 15, 2018 10:15 AM
  • User369979 posted

    @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.

    Tuesday, October 16, 2018 1:30 AM