NullReferenceException when adding items to nonempty list
I'm trying to add an item to an InkList through C#. It already has values in it, and the item I'm adding is from the same origin as those values, so I believe this should be supported. I'm guessing this is because the items added to the list were added via loading saved data, and no items have been added since loading that data.
Minimal repro:
Ink:
LIST fruit = Apple, Orange
~ fruit += Apple
C# (Unity):
var story = new Ink.Runtime.Story(json);
story.ContinueMaximally();
var save = story.state.ToJson();
story = new Ink.Runtime.Story(json);
story.state.LoadJson(save);
var fruitList = story.variablesState["fruit"] as Ink.Runtime.InkList;
Debug.Log(fruitList.Count); // 1
fruitList.AddItem("Apple"); // NullReferenceException
Stack:
NullReferenceException: Object reference not set to an instance of an object
Ink.Runtime.InkList.AddItem (System.String itemName) (at Assets/Plugins/Ink/InkRuntime/InkList.cs:179)
Ok so I've been plagued by this exact problem, which was occurring because the InkList I was pulling from variableState had empty 'all' and 'inverse' lists! Which means if you use 'AddItem', you will get a null error because this item isn't known by the list--even if that's not true as defined in the Ink script. As far as I can tell you, anytime you modify an existing InkList in Unity it clears the all and inverse lists. While this is (probably) a bug, I've come up with a workaround.
So in the Writing With Ink documentation, Inkle states:
List entries can be added and removed, singly or collectively.
~ DoctorsInSurgery = DoctorsInSurgery + Adams ~ DoctorsInSurgery += Adams // this is the same as the above
And in the Running Your Ink documentation, Inkle states:
list.Union(otherList) // equivalent to (list + otherList) in ink
After much consternation you might realize that you could add a new item to a new InkList, since upon initialization, a new list is always populated with all and inverse, and then add that to the existing list using Union. There are a couple gotchyas, but my code:
if ( story.variablesState.Contains(listName) ) { var listToModify = story.variablesState[listName] as Ink.Runtime.InkList; var singleItemList = new Ink.Runtime.InkList(listName, story); //there's a simpler test, to just test the inverse list, but I wanted some detailed error reporting if (singleItemList.all.ContainsItemNamed(newItemName)) { if (!listToModify.ContainsItemNamed(newItemName)) { singleItemList.AddItem(newItemName); listToModify = listToModify.Union(singleItemList); story.variablesState[listName] = listToModify; } } else Debug.LogErrorFormat(this, "InkList {0} does not know of item{1}. All items in InkList: {2}", listName, newItemName, singleItemList.all.ToString()); }
One could ask why are we even trying to do this instead of calling an Ink function from Unity that would effectively do this but be way less obtuse, and my answer to that is I really don't know, but, whatever I solved it.