Search code examples
androidxamarinmvvmcrossffimageloading

Xamarin Android visibility binding issue using MvvmCross and FFImageLoading


To create an animated loader UI control in my app (Xamarin AndroidX using Mvx and FFimageLoading) I created a custom control that inherits from MvxCachedImageView. Then I apply my animation as follow:

public class LoaderView : MvxCachedImageView
    {
        protected LoaderView(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
            Create();
        }

        public LoaderView(Context context)
            : base(context)
        {
            Create();
        }

        public LoaderView(Context context, IAttributeSet attrs)
            : base(context, attrs)
        {
            Create();
        }

        private void Create()
        {
            if (Context == null)
                return;

            ImageService.Instance.LoadCompiledResource("loader_indicator")
                .Into(this);

            ApplyRotation();

            StartAnimation(Animation);
        }
        
        private void ApplyRotation()
        {
            var rotation = new RotateAnimation(
                0,
                360,
                Dimension.RelativeToSelf,
                0.5f,
                Dimension.RelativeToSelf,
                0.5f)
            {
                Duration = 1200,
                Interpolator = new DecelerateInterpolator(1.25f),
                RepeatCount = Animation.Infinite
            };

            Animation = rotation;
        }
    }

Note: loader_indicator is a gif file

This control has been working properly until I tried to bind its visibility property to the 'classic' IsLoading BaseViewModel's property as follow:

var set = CreateBindingSet();
set.Bind(loader).For(v => v.Visibility).To(vm => vm.IsLoading)
                .WithConversion<MvxVisibilityValueConverter>();
set.Apply();

Executing the app as above, the Visibility binding and the gif animation work randomly: sometimes they do, sometimes they don't; sometimes instead the RotateAnimation is working but against the parent element and not itself - with the bad scenario where you have the icon going around into the page..

Doing different tests I've understood that if I remove the ApplyRotation and StartAnimation the control is displayed and binded correctly.

This seems to be a threading issue somewhere. Does anyone ever seen and (possibly) fixed this issue?

EDIT: the Cheesebaron anwser gave me a right hint, so I changed the LoaderView implementation as follow and it actually fixed the issue:

public class LoaderView : MvxCachedImageView
{
    private Animation _animation;

    protected LoaderView(IntPtr javaReference, JniHandleOwnership transfer)
        : base(javaReference, transfer)
    { }

    public LoaderView(Context context)
        : base(context)
    { }

    public LoaderView(Context context, IAttributeSet attrs)
        : base(context, attrs)
    { }

    protected override void OnVisibilityChanged(View changedView, ViewStates visibility)
    {
        base.OnVisibilityChanged(changedView, visibility);

        if (visibility == ViewStates.Visible)
            StartAnimation(_animation);
        else
            ClearAnimation();
    }

    protected override void OnAttachedToWindow()
    {
        base.OnAttachedToWindow();
        Create();
    }

    private void Create()
    {
        if (Context == null)
            return;

        ImageService.Instance.LoadCompiledResource("loader_indicator")
            .Into(this);

        _animation = ApplyRotation();
    }

    private static RotateAnimation ApplyRotation() =>
        new RotateAnimation(
            0,
            360,
            Dimension.RelativeToSelf,
            0.5f,
            Dimension.RelativeToSelf,
            0.5f)
        {
            Duration = 1200,
            Interpolator = new DecelerateInterpolator(1.25f),
            RepeatCount = Animation.Infinite
        };

}

Solution

  • You can try change your binding expression to:

    set.Bind(loader).For(v => v.BindVisible()).To(vm => vm.IsLoading);
    

    However, you are applying the animation when the element is instantiated, not when visibility is set. So the animation might be running, while the element is not visible or vise-versa. You might want to start the animation at a more appropriate point of time.