0
\$\begingroup\$

In Unity, after importing UnityIAP Version: 2.3.0, I have a button to which I attached an IAP Button component like in the image below:

enter image description here

My SkinsPanel class looks like this:

using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Purchasing;

public class SkinsPanel : MonoBehaviour
{
    [SerializeField] private RectTransform SorcererPurchaseButton; //reference to the button that is being pressed
    private string sorcererID = "com.company.test.sorcererID";

    public void OnPurchaseComplete(Product product)
    {
        if (product.definition.id == sorcererID)
        {
            SorcererPurchaseButton.gameObject.SetActive(false);
            //SorcererPurchaseButton.DOAnchorPos(new Vector2(1000, 1000), 0f);
        }
    }

    public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    {
        Debug.Log("Failed");
    }
}

Nothing incredible complicated, a reference to the button and two functions: OnPurchaseComplete and OnPurchaseFailed.

Now, if the user completes the purchase succesfully:

enter image description here

I'm getting a Collection was modified; enumeration operation may not execute. error:

enter image description here

Full stack trace:

InvalidOperationException: Collection was modified; enumeration operation may not execute.
System.ThrowHelper.ThrowInvalidOperationException (System.ExceptionResource resource) (at <9577ac7a62ef43179789031239ba8798>:0)
System.Collections.Generic.List`1+Enumerator[T].MoveNextRare () (at <9577ac7a62ef43179789031239ba8798>:0)
System.Collections.Generic.List`1+Enumerator[T].MoveNext () (at <9577ac7a62ef43179789031239ba8798>:0)
UnityEngine.Purchasing.CodelessIAPStoreListener.ProcessPurchase (UnityEngine.Purchasing.PurchaseEventArgs e) (at Assets/Plugins/UnityPurchasing/script/CodelessIAPStoreListener.cs:175)
UnityEngine.Purchasing.StoreListenerProxy.ProcessPurchase (UnityEngine.Purchasing.PurchaseEventArgs e) (at Library/PackageCache/[email protected]/Runtime/managed/Purchasing/StoreListenerProxy.cs:34)
UnityEngine.Purchasing.PurchasingManager.ProcessPurchaseIfNew (UnityEngine.Purchasing.Product product) (at Library/PackageCache/[email protected]/Runtime/managed/Purchasing/PurchasingManager.cs:250)
UnityEngine.Purchasing.PurchasingManager.OnPurchaseSucceeded (System.String id, System.String receipt, System.String transactionId) (at Library/PackageCache/[email protected]/Runtime/managed/Purchasing/PurchasingManager.cs:112)
UnityEngine.Purchasing.JSONStore.OnPurchaseSucceeded (System.String id, System.String receipt, System.String transactionID) (at <828972e73f634ff790a93860533a30ad>:0)
UnityEngine.Purchasing.FakeStore.<>n__0 (System.String id, System.String receipt, System.String transactionID) (at <828972e73f634ff790a93860533a30ad>:0)
UnityEngine.Purchasing.FakeStore+<>c__DisplayClass15_0.<FakePurchase>b__0 (System.Boolean allow, UnityEngine.Purchasing.PurchaseFailureReason failureReason) (at <828972e73f634ff790a93860533a30ad>:0)
UnityEngine.Purchasing.UIFakeStore+<>c__DisplayClass14_0`1[T].<StartUI>b__0 (System.Boolean result, System.Int32 codeValue) (at <828972e73f634ff790a93860533a30ad>:0)
UnityEngine.Purchasing.UIFakeStore.OkayButtonClicked () (at <828972e73f634ff790a93860533a30ad>:0)
UnityEngine.Purchasing.UIFakeStore.<InstantiateDialog>b__16_1 () (at <828972e73f634ff790a93860533a30ad>:0)
UnityEngine.Events.InvokableCall.Invoke () (at /Users/bokken/buildslave/unity/build/Runtime/Export/UnityEvent/UnityEvent.cs:166)
UnityEngine.Events.UnityEvent.Invoke () (at /Users/bokken/buildslave/unity/build/Runtime/Export/UnityEvent/UnityEvent/UnityEvent_0.cs:58)
UnityEngine.UI.Button.Press () (at /Applications/Unity/Hub/Editor/2019.4.17f1/Unity.app/Contents/Resources/PackageManager/BuiltInPackages/com.unity.ugui/Runtime/UI/Core/Button.cs:68)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at /Applications/Unity/Hub/Editor/2019.4.17f1/Unity.app/Contents/Resources/PackageManager/BuiltInPackages/com.unity.ugui/Runtime/UI/Core/Button.cs:110)
UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at /Applications/Unity/Hub/Editor/2019.4.17f1/Unity.app/Contents/Resources/PackageManager/BuiltInPackages/com.unity.ugui/Runtime/EventSystem/ExecuteEvents.cs:50)
UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at /Applications/Unity/Hub/Editor/2019.4.17f1/Unity.app/Contents/Resources/PackageManager/BuiltInPackages/com.unity.ugui/Runtime/EventSystem/ExecuteEvents.cs:261)
UnityEngine.EventSystems.EventSystem:Update() (at /Applications/Unity/Hub/Editor/2019.4.17f1/Unity.app/Contents/Resources/PackageManager/BuiltInPackages/com.unity.ugui/Runtime/EventSystem/EventSystem.cs:377)

I understand this issue would be caused if I were to iterate over a collection with something like a foreach and modifying/removing the elements over the iteration.

If I were to to simply change the position of the button like:

//SorcererPurchaseButton.gameObject.SetActive(false);
SorcererPurchaseButton.DOAnchorPos(new Vector2(1000, 1000), 0f);

this would work without any issues.

What is the issue here? Is it some Unity IAP bug?

\$\endgroup\$
2
  • \$\begingroup\$ Please post your full error message and stack trace as text. The image doesn't show the interesting parts that would help us track down what code is triggering this error. \$\endgroup\$
    – DMGregory
    Commented Oct 11, 2021 at 14:48
  • \$\begingroup\$ added the full error @DMGregory \$\endgroup\$
    – Stefan
    Commented Oct 11, 2021 at 14:56

2 Answers 2

1
\$\begingroup\$

It looks like this error is occurring inside CodelessIAPStoreListener.ProcessPurchase() - you can look at the source code here.

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
    PurchaseProcessingResult result;

    // if any receiver consumed this purchase we return the status
    bool consumePurchase = false;
    bool resultProcessed = false;

    foreach (IAPButton button in activeButtons)
    {
        if (button.productId == e.purchasedProduct.definition.id)
        {
            result = button.ProcessPurchase(e);

            if (result == PurchaseProcessingResult.Complete) {

                consumePurchase = true;
            }

            resultProcessed = true;
        }
    }

    // ...

IAPButton.ProcessPurchase() is called inside a forEach loop, and it in turn invokes the onPurchaseComplete event:

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
    onPurchaseComplete.Invoke(e.purchasedProduct);

    return (consumePurchase) ? PurchaseProcessingResult.Complete : PurchaseProcessingResult.Pending;
}

So when your OnPurchaseComplete() listener is called, you are inside a foreach loop that's iterating over IAPButton objects.

When you set the button game object to inactive, that calls OnDisable() on all its components, including the IAPButton, which says:

void OnDisable()
{
    if (buttonType == ButtonType.Purchase)
    {
        CodelessIAPStoreListener.Instance.RemoveButton(this);
    }
}

And removing the button means:

public void RemoveButton(IAPButton button)
{
    activeButtons.Remove(button);
}

So this modifies the activeButtons collection that CodelessIAPStoreListener is in the middle of iterating. It's not legal to modify an enumerable collection while you're iterating it, so that's the reason for the error you're seeing.

What you can do instead is defer the hiding of this button until after this iteration has completed - say by setting a "to hide" flag or adding it to a list of buttons to hide that another function will process later in the frame (maybe in LateUpdate(), just before the next frame is rendered), or by using a coroutine to hide it at the end of the frame. Or you could disable just the visible components of the button, without triggering the IAPButton's OnDisable() by deactivating the whole game object.

\$\endgroup\$
1
  • \$\begingroup\$ I'm having the same issue. However, I can't modify CodelessIAPStoreListener, since it's Unity code. So how do I know when its done and I can deactivate the button? \$\endgroup\$ Commented Dec 15, 2023 at 18:11
1
\$\begingroup\$

I solved this by adding an "IAP Listener" object into the scene. You can add a script to it and call a function when purchase is complete.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .