Prism icon indicating copy to clipboard operation
Prism copied to clipboard

[Bug] RegisterTypes can be called multiple times within the lifespan of an Android app

Open BurkusCat opened this issue 4 years ago • 11 comments

Description

On Android, it is possible for RegisterTypes in App.xaml.cs to be called multiple times within the "lifespan" of the app. This can lead to unexpected behaviours. In the production version of our app, it caused some dependencies to be registered multiple times and we were finding users after several hours of opening the app or after days encountering a crash. This crash occurred after certain services were registered in the container multiple times and multiple registrations continued to exist.

Each time RegisterTypes is called, the previously registered dependencies are still there. For many services, this is fine. Attempting to re-register them either does nothing or replaces the existing registration. For some of our dependencies (see BuildServiceProvider line in the repro) new registrations would be made and the container would grow.

Steps to Reproduce

  1. Place breakpoint at end of RegisterTypes in App.xaml.cs on Android. Observe total number of services registered (should be 41 in repro project) at end of method.
  2. Wait for the device to load the root page of the app. Press the Android back button. You should be on Android launcher home screen.
  3. Press the Android recents button and select the app
  4. Breakpoint in RegisterTypes should be hit again. This time 59 services will be registered.
  5. Repeat these steps and the container will continue to grow

Expected Behavior

RegisterTypes should only be called once OR containerRegistry should be a blank/fresh/new container. I found it unexpected that this could be called multiple times and keep previously registered services in the container.

Actual Behavior

RegisterTypes is called multiple times. A significant portion of our users encountered a crash because of this issue (requires the repro steps above 10~ times for our particular app) so I would guess there other ways RegisterTypes can be triggered other than the back button method. We have worked around the issue by checking if one of our types IsRegistered, if it is we don't continue registering anything else.

See also this recent StackOverflow link: https://stackoverflow.com/questions/68617006/prism-forms-di-how-to-avoid-multiple-singleton-instances

Basic Information

  • Version with issue: 8.1.97
  • Last known good version: N/A
  • Xamarin.Forms version: 5.0.0.2083
  • IDE: Visual Studio 2019 16.10.3

Screenshots

After app launch image

Second time image

Five times image

Reproduction Link

PrismRegisterTypes.zip

BurkusCat avatar Aug 12 '21 15:08 BurkusCat

That's weird. The only time RegisterTypes gets called in if the ctor of the App gets called. If the ctor of the app gets called then a new Container should be created each time. Thanks for reporting this. We'll look into it.

brianlagunas avatar Aug 12 '21 15:08 brianlagunas

@BurkusCat can you update your MainActivity as follows and let me know if it resolves your issue...

LoadApplication(App.Current ?? new App());

dansiegel avatar Aug 12 '21 20:08 dansiegel

@dansiegel @brianlagunas ~~Confirmed this resolves the issue we were seeing. Thanks for getting back so quick. Do you think its a Unity Issue?~~

Rechecked this morning and still seeing it in the PrismRegisterTypes.zip from this repo

ncunning avatar Aug 12 '21 21:08 ncunning

@dansiegel I updated the code in the production app + repro project but it doesn't resolve the issue:

LoadApplication(App.Current ?? new App(new AndroidInitializer()));

App.Current is always null so it always uses new App(new AndroidInitializer().

BurkusCat avatar Aug 13 '21 08:08 BurkusCat

For now, you'll need to call ContainerLocator.ResetContainer() just before you load the application.

ContainerLocator.ResetContainer();
LoadApplication(new App(new AndroidInitializer()));

brianlagunas avatar Sep 21 '21 18:09 brianlagunas

for reference on this issue... the "solution" proposed by @brianlagunas will work for anyone who is using Prism and has no backgrounding concerns whether with your own framework or with something like Shiny. To work around the issue a better solution that will solve the issue all around is to do what Prism will be doing internally moving forward.

public class App : PrismApp
{
    private static bool FirstRun = true;

    protected override void OnInitialized()
    {
        FirstRun = false;
    }

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        if(!FirstRun) return;

        // register your services
    }

    protected override void RegisterRequiredTypes(IContainerRegistry containerRegistry)
    {
        if(!FirstRun) return;
        base.RegistrRequiredTypes(containerRegistry);
    }

    protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
    {
        if(!FirstRun) return;
        // Register any modules
    }
}

dansiegel avatar Sep 27 '21 18:09 dansiegel

Thanks @dansiegel

Noticed this issue when toggling dark mode in the Android settings. The suggested solution worked a treat.

robfrancis avatar Dec 05 '21 22:12 robfrancis

@brianlagunas ,@dansiegel isn't the root cause of this related to 12050. This defect has been with the forms team for quite a while without any update.

pasteusernamehere avatar Apr 07 '22 12:04 pasteusernamehere

Looks like it. The PR is almost ready to merge, Dan just has a few tests failing and needs to figure out why, but he's on vacation.

brianlagunas avatar Apr 07 '22 13:04 brianlagunas

Is it possible to create App instance only once and use it all the time?

if(FirstRun)
  _app = new App(new AndroidInitializer());
LoadApplication(_app);

Or may it lead to unexpected behavior?

raV720 avatar May 18 '22 07:05 raV720

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Sep 21 '22 03:09 stale[bot]

Wow, I was struggling with this issue for quite a while. In our situation, we had a MainActivity with LaunchMode=SingleTop (like many other apps; nothing unusual). When we switched the system language, the current MainActivity was destroyed (OnDestroy) and a new MainActivity is created (OnCreate).

In our case, the workaround was to reset the DI container when OnDestroy was called.

protected override void OnDestroy()
{
    App.Current.Destroy();
    base.OnDestroy();
}

In our App.xaml.cs we added a Destroy method which does following: With GetContainer() we get the DryIoC container and on this, we run Dispose(). Disposing the DI container runs Dispose on all IDisposable services (events unsubscribed, resources released, etc). ContainerLocator.ResetContainer() is the workaround which forces to create a new DI container whenever a new Xamarin Forms App is created (this is very important, because we cannot reuse a disposed DI container!)

public void Destroy()
{
    var container = Container.GetContainer();
    container.Dispose();

    ContainerLocator.ResetContainer();
}

This looks like a widespread problem but many apps (at least those I've seen so far) don't seem to care about it.

My question to you guys:

  • Will there be a fix in Prism for Xamarin.Forms available any time soon? I would kindly appreciate it.
  • What happens with the changes in PR #2567 ? Will they ever be merged to master and released in a NuGet package?

thomasgalliker avatar Nov 16 '22 08:11 thomasgalliker

I have not had time to work on this and the changes in the PR resulted in breaking a number of tests. A fix will be merged for the next release but I cannot give you an exact time. Frankly I had hoped to get one out a few months ago. If it's more urgent to you and you have the bandwidth we do take PRs

dansiegel avatar Nov 16 '22 13:11 dansiegel

For those still using Xamarin.Forms who are hitting this issue please be sure to use Shiny.Framework which has the fix for this.

dansiegel avatar Jun 12 '23 14:06 dansiegel