Unlock a world of possibilities! Login now and discover the exclusive benefits awaiting you.
Hello qlikers
I'm developing an app that uses the Qlik Sense Engine API (via the .NET SDK) to interact with a qlik sense app.
In the app, I need to make a series of selections, that I later have to revert. To do this, I thought I could use the StoreTempSelectionState and RestoreTempSelectionState methods, as I assumed by the name that they would allow me to save and then recall the state of the app's selections.
However, after testing it, it appears I'm either wrong or doing something incorrect, because the Restore method seems to do absolutely nothing. Here's the code I'm using:
var state = app.StoreTempSelectionState();
app.GetField("foo").Select("...");
app.GetField("bar").Select("...");
//etc.
app.RestoreTempSelectionState(state.Id);
Did I misunderstand what those methods do? Is there a proper way to do this?
Wow... You've found a bug that from what I can see is pretty much as old as the SDK itself! So you now have the honor of holding the title of the finder of the oldest SDK bug ever recorded 🙂 It goes back at least to v3.0 which was the oldest one I could test without too much effort, but I suspect this goes all the way back to 1.0.
The call to DestroyGenericBookmark will correctly clear the bookmark from the engine so you won't suffer any "ghost" bookmarks. It's when the response to DestroyGenericBookmark is interpreted that the error is thrown. A workaround (apart from a try/catch) is to use the CreateBookmark method from the client namespace instead of CreateGenericBookmark from the engine namespace.
The problem stems from how the result of CreateGenericBookmark is deserialized, and affects all creation methods that returns a result structure instead of a single return value. So other examples of endpoints affected by this are:
However, the method CreateGenericSessionObject is NOT affected by this problem as it returns a single ObjectInterface value instead of a struct. The reason why the CreateBookmark from the client namespace does not suffer from the problem is simply that it deserializes the response from the engine sligthly differently from the corresponding engine namespace method.
I don't think you have misunderstood those methods, but they only work with QCS. That functionality is not available in QSEfW. However, I see there is no mention of this in the documentation of the method which should be considered a documentation error I guess...
Thanks for the explanation... do you think there's an alternative way of doing it that works on both QSE and QCS?
At the moment as a workaround I am manually reading all the selections on all the fields and then manually restoring them, but it seems like a bit of a hack to me.
Think that are some options for you but I don't know which is the best:
- Try the https://qlik.dev/apis/json-rpc/qix/doc#%23%2Fentries%2FDoc%2Fentries%2FBack for going back in the "selection stack"
- Create a bookmark https://qlik.dev/apis/json-rpc/qix/doc#%23%2Fentries%2FDoc%2Fentries%2FCreateBookmarkEx that you apply when you want to reset the selections
- Do your selections in an alternate state https://qlik.dev/apis/json-rpc/qix/doc#%23%2Fentries%2FDoc%2Fentries%2FAddSessionAlternateState when you want to go back change back the state
Thanks for your suggestions.
I'm trying the bookmarks route, it seems to work to some extent, but I have a questions if you dont' mind.
I've written the following code:
var bookmark = app.CreateGenericBookmark(new GenericBookmarkProperties()
{
Info = new NxInfo() { Type = "bookmark", Id = Guid.NewGuid().ToString() }
});
app.GetField("foo").Select("...");
app.GetField("bar").Select("...");
//etc.
app.ApplyGenericBookmark(bookmark.Info.Id); //seems to work correctly, selections are restored
app.DestroyGenericBookmark(bookmark.Info.Id); //throws ArgumentNullException
It mostly works, until the last instruction (calling DestroyGenericBookmark) that throws a ArgumentNullException:
"Value cannot be null. Parameter name: key"
at Qlik.Engine.Communication.QlikConnection.AwaitResponseTask[T](T task, String methodName, CancellationToken cancellationToken)
at Qlik.Engine.Communication.QlikConnection.AwaitResponse[T](Task`1 task, String methodName, CancellationToken cancellationToken)
at Qlik.Engine.App.DestroyGenericBookmark(String id)
Can anyone tell me why this happens? My program needs to run every day on several qlik apps, I don't want to fill them with unused "ghost" bookmarks...
PS: yes, I've checked, I'm not passing a null ID to the method, I'm passing the correct ID, the problem is somewhere else...
Wow... You've found a bug that from what I can see is pretty much as old as the SDK itself! So you now have the honor of holding the title of the finder of the oldest SDK bug ever recorded 🙂 It goes back at least to v3.0 which was the oldest one I could test without too much effort, but I suspect this goes all the way back to 1.0.
The call to DestroyGenericBookmark will correctly clear the bookmark from the engine so you won't suffer any "ghost" bookmarks. It's when the response to DestroyGenericBookmark is interpreted that the error is thrown. A workaround (apart from a try/catch) is to use the CreateBookmark method from the client namespace instead of CreateGenericBookmark from the engine namespace.
The problem stems from how the result of CreateGenericBookmark is deserialized, and affects all creation methods that returns a result structure instead of a single return value. So other examples of endpoints affected by this are:
However, the method CreateGenericSessionObject is NOT affected by this problem as it returns a single ObjectInterface value instead of a struct. The reason why the CreateBookmark from the client namespace does not suffer from the problem is simply that it deserializes the response from the engine sligthly differently from the corresponding engine namespace method.
Happy to help 🤣
Thanks for the pointers, I'll just wrap the call in a try/catch until a bugfix is released
Some further info on this for anyone facing the same issue:
Wrapping the call in try/catch is not a viable solution, because when the ArgumentNullException is thrown the IApp object gets automatically disposed and is no longer in a usable state.
Using the deprecated CreateBookmark() method seems to work better.
Ouch! Well, v15.5.1 was just released which contains a fix for that null-value issue.