0

When the view appears on the screen there is a short delay setting the values to each control. Is it possible to set the values before the user sees the view?

public UserSettingsView()
{
    InitializeComponent();
    LoadAsync();        
}

private async void LoadAsync()
{
    try
    {
        // Loading data from API
        Languages = await _languageService.GetAsync(AccessToken);
        USStates = await _uSStateService.GetAsync(AccessToken);
        
        // Assigning the list to the ItemSource of each Picker.
        ddlLanguages.ItemsSource = Languages;
        ddlUSStates.ItemsSource = USStates;

        // Getting the user's preferred settings
        var userSettings = await _accountService.GetSettingsAsync(UserID, AccessToken);

        if (userSettings != null)
        {
            // Setting user values to each Picker control. 
            // This is where the delay happens.
            ddlLanguages.SelectedIndex = Languages.FindIndex(x => x.ID == userSettings .LanguageID);
            ddlUSStates.SelectedIndex = USStates.FindIndex(x => x.ID == userSettings .USStateID);
            cbAge.IsChecked = currentSettings.AgeQualified;
        }
    }
    catch
    {
        await DisplayAlert("Oh no!", "Error loading the page", "OK");
    }
}  
Victor_Tlepshev
  • 478
  • 6
  • 19
  • You've called an `async` method without doing `await LoadAsync()`. DO NOT IGNORE the warning this gives you. Can result in deadlock, depending on what you do in the method. Don't get in that habit. But here it causes a different problem, which the warning tells you: returns to constructor before the method runs. So constructor finishes, and page loads. THEN `LoadAsync` runs. I'll find a previous discussion that tells what to do instead. – ToolmakerSteve Jan 28 '23 at 20:36
  • Does this answer your question? [Can constructors be async?](https://stackoverflow.com/questions/8145479/can-constructors-be-async): *"I'm trying to populate some data in a constructor..."* See [this answer](https://stackoverflow.com/a/12520574/199364): *Use a static async method that returns a class instance created by a private constructor..."* – ToolmakerSteve Jan 28 '23 at 20:37
  • Hmm. Just realized that is only possible if you start the page by explicitly creating it somewhere with `new UserSettingsView();` - then you can replace that with `UserSettingsView.CreateAsync();` or similar method name. In Maui, that might not be how your page gets created. Try `protected override async void OnAppearing() { await LoadAsync(); }` – ToolmakerSteve Jan 28 '23 at 20:43
  • In regards to your first comment, I do not have any warnings. I am not sure why but the constructor does not require you to await asynchronous calls. Second comment, no it does not answer my question and on your third comment I have tried it on the OnAppearing() and still same behavior. Also, I am not using MVVM. I am simply writing all my code into the code behind class. – Victor_Tlepshev Jan 28 '23 at 22:30
  • *" I am not sure why but the constructor does not require you to await asynchronous calls"* Very strange. Should at least be an intellisense warning when you hover over it. Anyway, regardless of why it lets you, if you do that, you will get the symptom you see. The solution shown in the other answer does not require MVVM; its showing fundamental c# features. Ignore the fact that they were discussing a ViewModel class. The important point (of that answer) is to do all the slow `async` work BEFORE creating the view. Its a shame `OnAppearing` did not help... Before Shell existed, it would have... – ToolmakerSteve Jan 28 '23 at 22:50
  • ... In your case, how/where does UsetSettingsView get called to display? Do the `await`s there, and store results in a simple class, that has a few properties to hold the data. Then pass that class instance as a parameter (if using Shell Navigation, use a "query parameter") to the new view. – ToolmakerSteve Jan 28 '23 at 22:54
  • I am not passing any parameters. I use a TapGestureRecognizer to invoking the await Navigation.PushModalAsync(); – Victor_Tlepshev Jan 28 '23 at 22:56
  • 1
    You gave me an idea. I am passing the two lists from the previous page onto the UserSettingView and it looks much better almost instance no delays just a quick blink. I can live with it. Thank you! – Victor_Tlepshev Jan 28 '23 at 23:12

2 Answers2

1

To resolve the delay, I am passing the two lists for the languages and the States from the previous page.

public UserSettingsView(List<Language> _languages, List<USState> _usStates)
{                      
    InitializeComponent();

    Languages = _languages;
    USStates = _usStates;

    LoadAsync();
}    

private async void LoadAsync()
{
    try
    {            
        ddlLanguages.ItemsSource = Languages;
        ddlUSStates.ItemsSource = USStates;

        var currentSettings = await _accountService.GetSettingsAsync(UserID, AccessToken);

        if (currentSettings != null)
        {
            ddlLanguages.SelectedIndex = Languages.FindIndex(x => x.ID == currentSettings.LanguageID);
            ddlUSStates.SelectedIndex = USStates.FindIndex(x => x.ID == currentSettings.USStateID);
            switchAgeQualification.IsToggled = currentSettings.AgeQualified;
        }
    }
    catch
    {
        await DisplayAlert("Error", "Could not load page data", "OK");
    }
}
Victor_Tlepshev
  • 478
  • 6
  • 19
0

If I understand correctly, you currently have a line like this:

await Navigation.PushModalAsync(new UserSettingsView());

I don't see the types of the properties involved, but the basic idea is to do all the slow awaits BEFORE doing new UserSettingsView....

Something like this:

public class UserSettingsData
{
  SomeType1 Languages;
  SomeType2 USStates;
  SomeType3 UserSettings;
}

...
// Slow await calls.
var data = await UserSettingsView.PrepAsync(UserId, AccessToken);
// Now create and display the view.
await Navigation.PushModalAsync(new UserSettingsView(data));

...
  public static async UserSettingsData PrepAsync(SomeType4 UserId, SomeType5 AccessToken)
  {
    var data = new UserSettingsData();
    data.Languages = await _accountService.GetSettingsAsync(...);
    data.USStates = await ...;
    data.UserSettings = await ...;
  }
  public UserSettingsView(UserSettingsData data)
  {
    ...
    // NOT AN ASYNC METHOD, so happens immediately, before page is shown.
    Load(data);
  }

  // NOT AN ASYNC METHOD, so happens immediately.
  private void Load(UserSettingsData data)
  {
    Languages = data.Languages;
    USStates = data.USStates;
    var userSettings = data.UserSettings;
    ...

    // if still need DisplayAlert
    Dispatcher.InvokeOnMainThread(async () =>
      await DisplayAlert...
    );
  }

Replace "SomeType" etc with your actual types.

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196