Home > Software design >  Image Rotation while fetching data [Xamrin forms] EDITED WITH NEW ERROR MESSAGE Only the original th
Image Rotation while fetching data [Xamrin forms] EDITED WITH NEW ERROR MESSAGE Only the original th

Time:09-17

How can I make an image rotate while I fetch data from a web server ?

   private async void FetchDataAsync(object sender, EventArgs e)
    {
        ImageBox.IsVisible = true;
        rotation = new Animation(v => ImageBox.Rotation = v, 0, -360);
        rotation.Commit(this, "rotate", 5, 1000, Easing.Linear, (v, c) => ImageBox.Rotation = 0, () => true);using (
        
         var httpC = new HttpClient())
            {
              
                if (!string.IsNullOrEmpty(url))
                {
                    var result = httpC.GetAsync(finalUrl).GetAwaiter().GetResult();
                    var results = result.Content.ReadAsStringAsync();
                     .
                     .
                     .
                     .
                     .

                     etc
              }
         }
     }

If I remove the client part the Image spins perfectly so the rotation works, but why does it not work when I am fetching using http client ? how to make it run while the thread is busy ?

OK SO HERE IS THE EDITED PART, After some research I managed to get it spinning, and use async and await.. but now whenever I touch the phone screen while its doing the work in the background I get this error "Only the original thread that created a view hierarchy can touch its views.'"

        private async void BtnOpenServicesPageClicked(object sender, EventArgs e)
    {
      //  Device.BeginInvokeOnMainThread(() => { RunRotation(); });
        RunRotation();
        DisbaleUI();
        var userName = TxtUserName.Text;
        var password = TxtPassword.Text;

        if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
        {
          await  DisplayAlert("Missing Credentials", "User name and password are required fields", "Ok");
            return;
        }

      

        var url = "The Url To Be USed";
        var builder = new UriBuilder(url);
        builder.Port = 0000;
        var query = HttpUtility.ParseQueryString(builder.Query);
        query["username"] = "username";
        query["password"] = "password";
        builder.Query = query.ToString();

        string finalUrl = builder.ToString();
        var services=await Task.Run( () => RunHttpRequestAsync(finalUrl));
       
        var servicesPage= new ServicesMainPage(services);
        EnableUI();
        await Navigation.PushModalAsync(new NavigationPage(obcMapsPage));
    }

   private async Task<List<NNWServices>> RunHttpRequestAsync(string finalUrl)
    {
        var services = new List<NNWServices>();

        try
        {
        
            using (var httpC = new HttpClient())
            {

                if (!string.IsNullOrEmpty(finalUrl))
                {
                    var result = httpC.GetAsync(finalUrl).Result;
                    var results = result.Content.ReadAsStringAsync();


                    var responseDictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(results.Result);
                    var userInfo = responseDictionary["User"];
                    var allUSerInfo = JsonConvert.DeserializeObject<Dictionary<string, object>>(userInfo.ToString());
                    var userNAme = allUSerInfo["UN"].ToString();
                    var passWord = allUSerInfo["PW"].ToString();


                    var item = responseDictionary["SINFO"].ToString();

                    var allServerInfo = JsonConvert.DeserializeObject<Dictionary<string, object>>(item);


                    var servicesInside = allServerInfo["Services"].ToString();
                    var crntSrvces = JsonConvert.DeserializeObject<List<NNWServices>>(servicesInside);

                    foreach (var srv in crntSrvces)
                    {
                        srv.UserName = userNAme;
                        srv.Password = passWord;
                    }
                    services.AddRange(crntSrvces);
                }
            }

        }
        catch (Exception ex)
        {
            this.AbortAnimation("rotate");
            ImageBox.IsVisible = false;
            return services;
        }
        this.AbortAnimation("rotate");
        ImageBox.IsVisible = false;
        return services;
    }

Any help would be appreciated I am very new to Xamarin

CodePudding user response:

Async voids are considered a no-no generally speaking, as they are "fire and forget" rather than properly awaited. If you want to make sure your method is being properly awaited, you should make it a Task. You could make the event subscription asynchronous by change your subscription to something like this;

view.OnButtonClicked  = async (s, e) => await FetchDataAsync();

private async Task FetchDataAsync(object sender, EventArgs e) 
{ 
    ... 
}

Generally speaking, async events are frowned upon, this is where MVVM Command Bindings are preferred as they are better suited for calling async code.

Additionally this;

httpC.GetAsync(finalUrl).GetAwaiter().GetResult();

Isn't great. But awaiting this method would remove the need for this code anyway

More information about why you shouldn't use Async Events are listed here - https://codereview.stackexchange.com/questions/133464/use-of-async-await-for-eventhandlers.

EDIT

In regards to your newly updated title, you are getting this issue because you are attempting to perform UI updates from a thread other than the Main thread. If your UI was beign updated in an awaited Task, you would not have this issue.

I do not wish to encourage "hacky" code, as your answer to your previous issue is not very thread efficient, but if you wish to quickly make changes on the main UI thread, you can call the following method from the XamarinEssentials library

MainThread.BeginInvokeOnMainThread(() =>
{
    // Code to run on the main thread
});

https://docs.microsoft.com/en-us/xamarin/essentials/main-thread

CodePudding user response:

You should be using an activity indicator to do that. Activity indicator can be bound to the status of a bound image source

<ActivityIndicator Grid.Row="0" Grid.Column="0" IsVisible="True" BindingContext="{x:Reference Name=image}" IsRunning="{Binding Path=IsLoading}" Color="Blue" />

<Image x:Name="image" Margin="0" HorizontalOptions="Center" VerticalOptions="Center" Aspect="AspectFill" HeightRequest="40" WidthRequest="40" Source="{Binding Source}"></Image>

CodePudding user response:

I managed to get it running as I wanted without any issues by running the HTTP Requests Async correctly in a separate method, solution below.

  private async void BtnOpenServicesPageClicked(object sender, EventArgs e)
        {
          //  Device.BeginInvokeOnMainThread(() => { RunRotation(); });
              RunRotation();
              DisbaleUI();
              var userName = TxtUserName.Text;
              var password = TxtPassword.Text;

              if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
                  {
                    await  DisplayAlert("Missing Credentials", "User name and password are required fields", "Ok");
                    return;
                  }

  

             var url = "The Url To Be USed";
             var builder = new UriBuilder(url);
             builder.Port = 0000;
             var query = HttpUtility.ParseQueryString(builder.Query);
             query["username"] = "UserP123";
             query["password"] = "P@$$WORD";
             builder.Query = query.ToString();

             string finalUrl = builder.ToString();
             var services=await Task.Run( () => RunHttpRequestAsync(finalUrl));
   
             var servicesPage= new ServicesMainPage(services);
             EnableUI();
             await Navigation.PushModalAsync(new NavigationPage(obcMapsPage));
       }

   private async Task<List<NNWServices>> RunHttpRequestAsync(string finalUrl)
       {
            var services = new List<NNWServices>();

            try
               {
    
                using (var httpC = new HttpClient())
                  {

                    if (!string.IsNullOrEmpty(finalUrl))
                      {
                       var result = httpC.GetAsync(finalUrl).Result;
                       var results = result.Content.ReadAsStringAsync();


                      var responseDictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(results.Result);
                      var userInfo = responseDictionary["User"];
                      var allUSerInfo = JsonConvert.DeserializeObject<Dictionary<string, object>>(userInfo.ToString());
                      var userNAme = allUSerInfo["UN"].ToString();
                      var passWord = allUSerInfo["PW"].ToString();


                      var item = responseDictionary["SINFO"].ToString();

                      var allServerInfo = JsonConvert.DeserializeObject<Dictionary<string, object>>(item);


                      var servicesInside = allServerInfo["Services"].ToString();
                      var crntSrvces = JsonConvert.DeserializeObject<List<NNWServices>>(servicesInside);

                      foreach (var srv in crntSrvces)
                         {// Set Each Srvc USerName and Password to be the same
                           srv.UserName = userNAme;
                           srv.Password = passWord;
                         }
                      services.AddRange(crntSrvces);
                  }
             }

    }
    catch (Exception ex)
    {
        Logger.LogException ("Exception Occurred",ex);
        return services;
    }

    return services;
}
  • Related