SAP / openui5

OpenUI5 lets you build enterprise-ready web applications, responsive to all devices, running on almost any browser of your choice.
http://openui5.org
Apache License 2.0
2.96k stars 1.24k forks source link

Can not call callFunction in Controller.onExit or Component.exit #3603

Closed s-v-o closed 1 year ago

s-v-o commented 2 years ago

OpenUI5 version:

Browser/version (+device/version): 1.71.49

Steps to reproduce the problem:

  1. sap.ui.model.odata.v2.ODataModel.callFunction in Controller.onExit or in Component.exit
  2. produces error Message in JavaScript Console and OData FunctionImport is not called

What is the expected result? OData Function Import should be called

What happens instead? Error message in JavaScript console

Any other information? (attach screenshot if possible)

I want to call an oData FunctionImport, when the user closes the app i.e. by navigate away through the launchpad shell buttons. But when I do so, I get an error because the oMetadata are not available anymore.

Why is this hook called after destroying half of the ODataModel?

It seems like a bug.

The only solution to get around this is to use some custom request (maybe XMLHttpRequest) and make the call without the ODataModel, but I don't think that this is desired behavior.

URL (minimal example if possible): https://plnkr.co/edit/LZ2WBEso3Nh5tTBb

HerzogIgzorn commented 2 years ago

Hello s-v-o,

am I getting this right that you are complaining that the onExit hook gets executed AFTER the objects have been destroyed? Is there a way that you can provide some code samples to get a better understanding what you are going to do and how those objects (i.E. the ODataModel) are being initialized?

I informed the responsible development team which will provide further information. In case of questions you can refer to the internal ID 2270138172.

Regards Sebastian (SAPUI5 Development Support)

s-v-o commented 2 years ago

Thank you for your reply.

For an executable example I need some time.

But essentially it is something like this:


sap.ui.define([
    "sap/ui/core/mvc/Controller",
    "sap/m/ObjectListItem",
    "sap/m/ObjectAttribute"
], function(Controller, ObjectListItem, ObjectAttribute) {
    "use strict";

    return Controller.extend("sap.ui.demo.MockServer.controller.App", {
           onExit: function(){
             this.getView().getModel().callFunction("/MyFunctionImport", { method: "POST" })
           }
    });
});
Thodd commented 2 years ago

Hi @s-v-o,

it's hard to say what actually happens here wrt. the ODataModel, but I'll outline the general the destruction lifecycle of a Component below:

Component destruction lifecycle

  1. destroy() of the application Component instance
  2. super destroy() of the sap.ui.core.UIComponent base class
  3. Destroy Router (incl. Views) - This is where your Controller#onExit Hook is called during View destruction
  4. super destroy() of the sap.ui.core.Component base class
  5. Destruction of Services, Models, EventBus, managed Fragments
  6. super destroy() of the sap.ui.base.ManagedObject base class
  7. exit() hook on application instance is called
  8. deregister of application instance from MessageManager singleton
  9. Unloading of includes via legacy ComponentMetadata

In general the exit() hook of a Component is intended for clean-up only and not for additional workload. The intention behind this is partly because we can't catch any asynchronous behavior anymore after the Component is destroyed (at least not currently).

So in general the exit() hook should only take care of destroying controls/fragments/... that are not managed by the framework. All the stuff you can see in the above list is framework managed, e.g. Models created via the Component, Views created via Routing and so on.

Now to the Controller's onExit hook:

A controller's onExit hook is executed at the same time as the beforeExit event of the View. I think the reason why this doesn't work for you either is the following:

While the onExit hook of the Controller still has access to the Model instance, the function import is asynchronous, but the whole destruction process is synchronous (and has to stay this way for compatibility reasons). This means that while you might be able to trigger the function import asynchronously, the Model will be destroyed regardless when the Component Models are cleaned up (Number 5).

I presume this leads to the case that the Model is already destroyed when some internal ODataModel promise resolves and thus runs into the missing metadata issue.

So I think the issue is that you are using a framework managed Component Model created through manifest definition. A standalone model you create yourself will not be destroyed automatically, so you could use this in the exit hooks. However you must make sure to destroy it manually after your function import is resolved.

Best regards,

Thorsten

s-v-o commented 2 years ago

Thank you very much for your detailed explanation. It seems it is exactly what happens and your explanation help me to understand. I will try your suggested workaround and come back with an executable example.

On the other site it is a quite common task to talk back to the backend when leaving the app so I hope to see a better support therefore in the future.

s-v-o commented 2 years ago

I have added an example which illustrates the error further. But I think it confirms your analysis.

s-v-o commented 2 years ago

@Thodd Your suggested workaround is working and I can achieve what I want.

Thank you very much.

Here is an executable example: https://plnkr.co/edit/CLHQSabzTFroKTi6?preview

But it is not optimal, because I have to decide not to use the Component managed model or instantiate a new model (including a new request to metadata) in the onExit hook.

Also I could imagine, that a hook before the application Component instance is destroyed would be general useful.

But for me the workaround solves my problem at hand.

s-v-o commented 2 years ago

@Thodd With the given workaround I could solve my problem.

But in my opinion it is a bug, that I'm unable to use the component model in onExit.

If you disagree, you could close this issue.

Thodd commented 2 years ago

Hi @s-v-o,

so I discussed this with a colleagues just now again and the bottom line is that the whole destroy lifecycle is just conceptually not equipped to deal with this. We consider this to more a conceptual weakness than a bug, since we currently don't have options to catch any asynchronous behavior. So from framework/component perspective there currently is no way to solve this compatibly.

However, I will forward this issue to the maintainers of the V2 ODataModel. The model can theoretically be used from within the Controller#onExit, but the (internal) callbacks themselves crash because of the already cleaned up metadata.

Maybe this can be made more robust, or maybe there could be a way to tell the model, that this function import is a "beacon-like" request...

BR, Thorsten

Thodd commented 1 year ago

Hi @s-v-o,

so this is a rather old topic, but it recently came back up to my attention.

The stance on this is, that the exit() hooks are regarded as client-side only cleanup methods. They should only be used to tear down internal client-side only resources. The "exit" hook is not designed to cover use cases where any client-server communication can be reliably established. Since the Models itself are managed client-side resource (by the component) and have already been destroyed during the exit() handler, we do not regard this as a bug anymore. Any requesting function of the ODataModel cannot reliably used during exit().

The ODataModel v2 will also not provide another API to terminate serverside states (e.g. via a Beacon).

BR, Thorsten

lhendrik commented 7 months ago

Hi,

i have the same requirement as @s-v-o , "releasing a lock in the backend when the user leaves the app". I understand the flow in exit() of the component and that the model can not be used any more. But i would request a hook where OData calls are still possible as s-v-o already suggested. Or can you explain how it is handled in other UI5 applications, e.g. SAP UI5 application?

Best regards, lhendrik

uhlmannm commented 7 months ago

Hi @lhendrik,

we are discussing internally to provide a possibility to send a Beacon request with the {{v4.ODataModel}}. As Beacon requests are POST requests this will mean to call an OData action in the OData service. As already written by @Thodd, there are no plans to provide a similar possibility with the {{v2.ODataModel}}. It is then required to use the Beacon API directly in the application.

Best regards Mathias.