View not updated on propertychanged after camera task

fsulserfsulser Member ✭✭
edited March 21 in Xamarin.Forms

My problem is that the value in my view (Binding FilePath) is not updated after the GaleryTapped(). But it does work with SendTapped() function. Therefore I think my basic binding is correct.

When I debug into the setter I see that it is called in both cases with a new string. Why is the view not updating in the second case?

public class BildViewModel : BaseViewModel
{
    private string _filePath;
    public string FilePath
    {
        get => _filePath;
        set
        {
            _filePath = value;
            OnPropertyChanged(nameof(FilePath));
        }
    }
    public ICommand SendTapCommand { get; set; }
    public ICommand GaleryTapCommand { get; set; }
    private BildViewModel()
    {
        FilePath = "AAA";
        SendTapCommand = new Command(SendTapped);
        GaleryTapCommand = new Command(GaleryTapped);
    }

    private void SendTapped()
    {
        FilePath = FilePath + "A";
    }
    private async void GaleryTapped()
    {
        await CrossMedia.Current.Initialize();

        if (!CrossMedia.Current.IsPickPhotoSupported)
        {
            await Application.Current.MainPage.DisplayAlert("Keine Galerie", "Keine Galerie verfügbar", "OK");
            return;
        }

        var file = await CrossMedia.Current.PickPhotoAsync(new Plugin.Media.Abstractions.PickMediaOptions
        {
            PhotoSize = Plugin.Media.Abstractions.PhotoSize.Full,
        });

        if (file == null)
            return;
        FilePath = file.Path;
    }
}

and the view is just basic:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="FCBubendorf.Views.TickerViews.BildPage">
    <ContentPage.Content>
        <StackLayout>
            <Button Text="Bild von Galerie" Command="{Binding GaleryTapCommand}" HorizontalOptions="StartAndExpand"/>
            <Button Text="Bild von Camera" Command="{Binding CameraTapCommand}" HorizontalOptions="EndAndExpand"/>
            <Label Text="{Binding FilePath}" />
            <Button Text="Erstellen" Command="{Binding SendTapCommand}" VerticalOptions="End"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Best Answer

  • fsulserfsulser ✭✭
    Accepted Answer

    Ok I now saw the problem:
    my BildPage.xaml.cs looks like:

        private long _spielId;
        public BildViewModel Bild { get; set; }
        public BildPage(long SpielId)
        {
            _spielId = SpielId;
            InitializeComponent();
        }
    
        protected override void OnAppearing()
        {
            base.OnAppearing();
            LoadDataAsync();
        }
    
        private void LoadDataAsync()
        {
            Bild = BildViewModel();
            BindingContext = Bild;
        }
    }
    

    and the LoadDataAsync is creating a viewmodel everytime when you go back from the camera. That's why it is not working. I will try to use a singleton. Or is there a better approach?
    Binding in the Constructor is not possible. Because I will later load data async on initializing and therefore I need a was to have it asynchronous

Answers

  • robobororoboboro Member ✭✭
    edited March 21

    @fsulser Because of the async it will happen on a different thread then the UI so the UI will not be notified.
    this wil help you https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

  • fsulserfsulser Member ✭✭
    edited March 21

    Thanks for the message.

    So I changed to:

        public NotifyTaskCompletion<string> FilePath
        {
            get; set;
        }
    
        private void GaleryTapped()
        {
            FilePath = new NotifyTaskCompletion<string>(GetFileFromLibrary());
    
        }
    
    ...
    
        private async Task<string> GetFileFromLibrary()
        {
            await CrossMedia.Current.Initialize();
    
            if (!CrossMedia.Current.IsPickPhotoSupported)
            {
                await Application.Current.MainPage.DisplayAlert("Keine Galerie", "Keine Galerie verfügbar", "OK");
                return "";
            }
    
            var file = await CrossMedia.Current.PickPhotoAsync(new Plugin.Media.Abstractions.PickMediaOptions
            {
                PhotoSize = Plugin.Media.Abstractions.PhotoSize.Full,
            });
    
            if (file == null)
                return "";
    
            return file.Path;
        }
    

    and xaml:

            <Label Text="{Binding FilePath.Result}" />
    

    but the value is still not updated in my view

  • robobororoboboro Member ✭✭

    @fsulser and you are using the INotifyPropertyChanged in the NotifyTaskCompletion class?

  • fsulserfsulser Member ✭✭
    edited March 21

    @roboboro yes I basically copied the code for NotifyTaskCompletion

    public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged
    {
        public NotifyTaskCompletion(Task<TResult> task)
        {
            Task = task;
            if (!task.IsCompleted)
            {
                var _ = WatchTaskAsync(task);
            }
        }
    
        private async Task WatchTaskAsync(Task task)
        {
            try
            {
                await task;
            }
            catch
            {
            }
            var propertyChanged = PropertyChanged;
            if (propertyChanged == null)
                return;
            propertyChanged(this, new PropertyChangedEventArgs("Status"));
            propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
            propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted"));
            if (task.IsCanceled)
            {
                propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
            }
            else if (task.IsFaulted)
            {
                propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
                propertyChanged(this, new PropertyChangedEventArgs("Exception"));
                propertyChanged(this,
                  new PropertyChangedEventArgs("InnerException"));
                propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
            }
            else
            {
                propertyChanged(this,
                  new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
                propertyChanged(this, new PropertyChangedEventArgs("Result"));
            }
        }
        public Task<TResult> Task { get; private set; }
        public TResult Result
        {
            get
            {
                return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult);
            }
        }
        public TaskStatus Status { get { return Task.Status; } }
        public bool IsCompleted { get { return Task.IsCompleted; } }
        public bool IsNotCompleted { get { return !Task.IsCompleted; } }
        public bool IsSuccessfullyCompleted
        {
            get
            {
                return Task.Status == TaskStatus.RanToCompletion;
            }
        }
        public bool IsCanceled { get { return Task.IsCanceled; } }
        public bool IsFaulted { get { return Task.IsFaulted; } }
        public AggregateException Exception { get { return Task.Exception; } }
        public Exception InnerException
        {
            get
            {
                return (Exception == null) ? null : Exception.InnerException;
            }
        }
        public string ErrorMessage
        {
            get
            {
                return (InnerException == null) ? null : InnerException.Message;
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
    
  • robobororoboboro Member ✭✭

    @fsulser i have set up a test project and it works when the page is loaded. but not when i push the button.

    https://github.com/theonlyroboboro/AsyncTestProject
    i hope someone can help us because i'm looking for the same thing

  • fsulserfsulser Member ✭✭

    Ok nice, thank you. But it works when the page is setup also without the NotifyTaskCompletion because it is leaded and then the element is bound to the model afterwards, so the change is not really neaded there

  • LandLuLandLu Member, Xamarin Team Xamurai

    @fsulser I tested your code on my side, the image's path could be successfully displayed on the label.

    Android has the same effect.
    Have you done other configurations on your project? Please share a sample to help us reproduce your issue.

  • robobororoboboro Member ✭✭
    edited March 22

    @fsulser

    I have it working now and updated the repo. this was the solution:

    public NotifyTaskCompletion<string> FilePath { get => filePath; private set { filePath = value; this.OnPropertyChanged(); } }

  • fsulserfsulser Member ✭✭
    Accepted Answer

    Ok I now saw the problem:
    my BildPage.xaml.cs looks like:

        private long _spielId;
        public BildViewModel Bild { get; set; }
        public BildPage(long SpielId)
        {
            _spielId = SpielId;
            InitializeComponent();
        }
    
        protected override void OnAppearing()
        {
            base.OnAppearing();
            LoadDataAsync();
        }
    
        private void LoadDataAsync()
        {
            Bild = BildViewModel();
            BindingContext = Bild;
        }
    }
    

    and the LoadDataAsync is creating a viewmodel everytime when you go back from the camera. That's why it is not working. I will try to use a singleton. Or is there a better approach?
    Binding in the Constructor is not possible. Because I will later load data async on initializing and therefore I need a was to have it asynchronous

Sign In or Register to comment.