How to do some async cleanup work on app closure in Android .NET MAUI?

445 views Asked by At

In my application I need to do some asynchronous cleanup whenever my application is closed. Ideally I would like a solution that would work cross platform but for now I would like to get this working on Android specifically. I know from the MAUI App lifecycle, the OnDestroy should be called when the application is closed so it seems like the right place to put this but it doesn't seem to be executing. The strange thing is that if I remove the asynchronous calls it the synchronous calls seem to work fine. Here is some code to illustrate:

In my App.xaml.cs

public partial class App: Application
{
    //... some other code here

    private JoinableTaskContext _joinableTaskContext = new JoinableTaskContext();

    protected override Window CreateWindow(IActivationState? activationState){
        Window window = base.CreateWindow(activationState);
        window.Destroying += Window_Destroying;
        
        return window;
    
    }
   
    private void Window_Destroying(object? sender, EventArgs e){
        /* Wrapping the async call in a joinableTaskFactory to avoid using async void in 
           the method signature*/
        _ = _joinableTaskContext.Factory.RunAsync(async () =>
        {
            await CleanupOnDestroyAsync();
        });
        // Some synchronous call to stop a ForegroundService that uses intents.
        
    }
    
    private async Task CleanupOnDestroyAsync(){
        await myService.Cleanup();
    }
}

I have also tried using this code in the MAUI-Android native OnDestroy but that doesn't work either.

Any thoughts on how I could approach this?

1

There are 1 answers

0
ToolmakerSteve On

This is one possible design for reliable server-app communication.
Its off the top of my head, to give "food for thought".
Treat it as Opinion or Brainstorming; not definitive.
My primary goal is to get you to question the assumptions implicit in your question.


The Server

  • In server-client communication, the server should NOT assume that the client (the app) has a chance to tell it "I'm done". Communication between server and client could be disrupted, making this impossible.

Solution: have some maximum timeout per client. If server doesn't hear from the client within that time, terminate the session, performing any cleanup.

How long the timeout should be depends on how "expensive" it is to hold on to each session, vs. the cost of processing extra packets from clients saying they are still alive. Could be anywhere from a number of minutes, to a day.

One simple implementation is to run a periodic job that checks all open sessions, closing any that have exceeded the timeout.


The App

  • It is not safe to rely on app closure code running. Especially on Android, where each vendor tweaks the OS.

  • Design your app so whenever it goes into background, it does whatever is necessary, even if the app never gets another chance to run code.

For example, Android: Save simple, lightweight UI state using onSaveInstanceState() contains this statement:

To save persistent data, such as user preferences or data for a database, take appropriate opportunities when your activity is in the foreground. If no such opportunity arises, save persistent data during the onStop() method.

  • App should talk to server periodically, so that timeout doesn't trigger. Keep track of when you last sent a message to server; use a timer to send a dummy message if necessary. Reset the timer whenever any message is sent. [The longer the timeout, the less critical this is. Such a dummy message could be omitted; simply be able to re-establish a session if its been longer than the timeout. As in next point.]

  • If communication is disrupted, the next time client talks to server, its possible that server already terminated the session. Both client and server need to cope with this situation; establish a new session without user having to enter login again. A "token" may help. [On login, app is sent a token that is valid only from this device, for a period of time such as 12 hours.]

In your specific case:

In addition to sending a message periodically (while app is in foreground), consider sending a message when the app goes into background, and when it comes back into foreground.

Server could use this to adjust timeout. For example, if user was in the middle of data entry, might extend the timeout, in hopes that user will get back to the app, to finish what they were doing. [Alternatively, the app might simply save the partial entered data locally, so can pick up where user left off, even if server drops the ball.]