OndrejKunc / SkiaScene

Simple API to transform SkiaSharp objects using functions like zoom and rotate or using gestures like pan and pinch.
MIT License
67 stars 18 forks source link

TouchTracking. throw System.ArgumentException: An item with the same key has already been added. Key: 0 #25

Open EugenyTsoy opened 3 years ago

EugenyTsoy commented 3 years ago

I am using TouchTracking.Forms with Prism.Forms INavigationService for app on Andriod platform.

OnTouchAction is used for navigation between view by INavigationService. For example in touchReleasedactionHandler call: navigationService.NavigateAsync($"/{nameof(someView)}");

If I frequantly switching between pages by touching corresponded buttons with attached OnTouchAction then generated exception:

{System.ArgumentException: An item with the same key has already been added. Key: 0
  at System.Collections.Generic.Dictionary`2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) [0x000dd] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs:531 
  at System.Collections.Generic.Dictionary`2[TKey,TValue].Add (TKey key, TValue value) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs:240 
  at **TouchTracking.Droid.TouchHandler.OnTouch** (System.Object sender, Android.Views.View+TouchEventArgs args) [0x0008f] in <16086595d2354bd2a5fae8bf7e121c37>:0 
  at Android.Views.View+IOnTouchListenerImplementor.OnTouch (Android.Views.View v, Android.Views.MotionEvent e) [0x00014] in /Users/builder/azdo/_work/30/s/xamarin-android/src/Mono.Android/obj/Release/monoandroid10/android-29/mcw/Android.Views.View.cs:4028 
  at Android.Views.View+IOnTouchListenerInvoker.n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_v, System.IntPtr native_e) [0x00018] in /Users/builder/azdo/_work/30/s/xamarin-android/src/Mono.Android/obj/Release/monoandroid10/android-29/mcw/Android.Views.View.cs:3967 
  at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.53(intptr,intptr,intptr,intptr)}

The exception is thrown when trying to add a duplicate key 1 or 0 to dictionary the TouchHandler._idToTouchHandlerDictionary.

I found that there might be a case where an element receives a touch action (adding touch to _idToTouchHandlerDictionary) and does not receive a touch release action (remove touch from _idToTouchHandlerDictionary) because it was previously disposed.

During disposing, the visual element is detahed from the "TouchEffect" effect. "TouchEffect" calls "_touchHandler.UnregisterEvents (_view)" where happens unscribing from "view.Touch" event:

view.Touch -= OnTouch;

Is it need to add some code that will be removed elements associated with disposed or detached view? It solves problem in my case.

 var idToTouchHandlerDictKeys = _idToTouchHandlerDictionary
                    .Where(x => x.Value.Handler._view == view)
                    .Select(x => x.Key)
                    .Distinct();

                foreach (var key in idToTouchHandlerDictKeys)
                {
                    _idToTouchHandlerDictionary.Remove(key);
                }

in TouchHandler.UnregisterEvents

public override void UnregisterEvents(View view)
        {
            try
            {
                view.GetHashCode();
            }
            catch (ObjectDisposedException) //view can be already disposed and we have no other way to remove it from dictionary
            {

                Console.WriteLine($"___disposed view:{Type.GetTypeHandle(view).Value}");
                var newDictionary = new Dictionary<View, TouchHandler>();
                foreach (KeyValuePair<View, TouchHandler> item in _viewDictionary)
                {
                    try
                    {
                        newDictionary[item.Key] = item.Value;
                    }
                    catch (ObjectDisposedException)
                    {
                        continue;
                    }
                }
                _viewDictionary = newDictionary;
                return;
            }
            if (_viewDictionary.ContainsKey(view))
            {
                _viewDictionary.Remove(view);

                // Is it need to add this processing?
                var idToTouchHandlerDictKeys = _idToTouchHandlerDictionary
                    .Where(x => x.Value._view == view)
                    .Select(x => x.Key)
                    .Distinct();

                foreach (var key in idToTouchHandlerDictKeys)
                {
                    _idToTouchHandlerDictionary.Remove(key);
                }
                //---

                view.Touch -= OnTouch;
                Console.WriteLine($"___OnTouch removed view:{Type.GetTypeHandle(view).Value}");
            }
        }