Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: [Android] NavigateToFirstViewModel() not called on Android when SplashScreenActivity is derived from MvxActivity #4854

Closed
preetanshumishra opened this issue May 15, 2024 · 20 comments
Labels
t/bug Bug type

Comments

@preetanshumishra
Copy link

preetanshumishra commented May 15, 2024

Description

I am migrating Xamarin.Native to .NET8 and also migrating from MvvmCross 8.0.2 to MvvmCross 9.1.1 with it.
I was able to make the .net8-ios project work. But the .net-android project starts but gets stuck at SplashScreenActivity.

Steps to Reproduce

  1. Regular MvvmCross Setup with a Custom AppStart.
  2. Defined SplashScreen as public class SplashScreenActivity : MvxActivity
  3. NavigateToFirstViewModel() method in AppStart is never called. The same works fine on iOS.

Link to public reproduction project repository

No response

Version with bug

Unknown/Other

Is this a regression from previous behavior?

Yes, this used to work in a previous version

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

No response

Did you find any workaround?

No response

@preetanshumishra preetanshumishra added the t/bug Bug type label May 15, 2024
@preetanshumishra preetanshumishra changed the title [Bug]: NavigateToFirstViewModel not called on Android [Bug]: NavigateToFirstViewModel not called on Android May 15, 2024
@preetanshumishra preetanshumishra changed the title [Bug]: NavigateToFirstViewModel not called on Android [Bug]: NavigateToFirstViewModel() not called on Android May 15, 2024
@preetanshumishra preetanshumishra changed the title [Bug]: NavigateToFirstViewModel() not called on Android [Bug]: NavigateToFirstViewModel() not called on Android when SplashScreenActivity is derived from MvxActivity May 15, 2024
@preetanshumishra preetanshumishra changed the title [Bug]: NavigateToFirstViewModel() not called on Android when SplashScreenActivity is derived from MvxActivity [Bug]: [Android] NavigateToFirstViewModel() not called on Android when SplashScreenActivity is derived from MvxActivity May 15, 2024
@Cheesebaron
Copy link
Member

This doesn't seem like a bug, but rather a misunderstanding of how MvvmCross works.

Making an Activity and calling it something with splash screen doesn't magically do anything.

You need to have a MvxAndroidApplication which does the initialization of Setup and navigates to the VM registered in your App class.

Refer to the playground app or some of the samples in the samples repo. TipCalc should be fairly up to date.

@preetanshumishra
Copy link
Author

preetanshumishra commented May 15, 2024

This doesn't seem like a bug, but rather a misunderstanding of how MvvmCross works.

Making an Activity and calling it something with splash screen doesn't magically do anything.

You need to have a MvxAndroidApplication which does the initialization of Setup and navigates to the VM registered in your App class.

Refer to the playground app or some of the samples in the samples repo. TipCalc should be fairly up to date.

@Cheesebaron Yes, everything is already setup as per the documentation.
It was working when on Xamarin and MvvmCross 8.0.2.
I am trying to upgrade it to MvvmCross 9.1.1 with .NET8.
It seems like since I upgraded, the method is not called at all. It works perfectly on iOS though.
I did refer to the playgroud app to see if anything is different, but everything looks fine except that the SplashScreenActivity seems to be derived from MvxStartActivity which was previously MvxActivity.

@preetanshumishra
Copy link
Author

@Cheesebaron I also checked the TipCalc app, that hasn't been updated as the SplashScreenActivity in TipCalc.Droid is derived from MvxSplashScreenActivity which has been removed from MvvmCross.

@Cheesebaron
Copy link
Member

The splash screen is not the important part, but rather the MainApplication. MvvmCross doesn't rely on the splash anymore for setup.

You need to provide a bit more info so I can help you.

Code snippets would be a good start

@preetanshumishra
Copy link
Author

preetanshumishra commented May 15, 2024

    [Application]
    public class Application : MvxAndroidApplication, IActivityLifecycleCallbacks
    {
        private IDisposable _languageObserver;
        private IDisposable _accountObserver;
        private SessionManager _sessionManager;

        internal static ActivityMediator Mediator { get; } = new ActivityMediator();

        public Application() : base()
        {
        }

        public Application(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
        {
        }

        public override void OnCreate()
        {
             base.OnCreate();

            RegisterActivityLifecycleCallbacks(this);
        }

        protected override void RegisterSetup()
        {
            this.RegisterSetupType<Setup>();
        }

        public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
        {
            SetOrientationForFormFactor(activity);

            if (_languageObserver == null
                && Mvx.IoCProvider.TryResolve<ResourceSyncService>(out var resourceSyncService)
                && Mvx.IoCProvider.TryResolve<AccountService>(out var accountService))
            {
                // When the logged in user changes his language, update the channel to that language
                _accountObserver = accountService.GetAndObserveAccountUpdated()
                                                 .Subscribe(account => CreateNotificationChannel(this));
                _languageObserver = resourceSyncService.ObserveLanguage()
                                                       .Subscribe(newLanguage => CreateNotificationChannel(this));
            }
        }

        public static void CreateNotificationChannel(Context context)
        {
            if (Build.VERSION.SdkInt < BuildVersionCodes.O)
            {
                return;
            }

            var languageBinder = Mvx.IoCProvider.Resolve<IMvxLanguageBinder>();
            var channelName = languageBinder.GetText(nameof(Strings.push_notification_channel_name));
            var channelId = context.Resources.GetString(Resource.String.default_notification_channel_id);
            var channel = new NotificationChannel(channelId, channelName, NotificationImportance.High)
            {
                Description = languageBinder.GetText((nameof(Strings.push_notification_channel_description)))
            };

            var notificationManager = (NotificationManager)context.GetSystemService(NotificationService);
            notificationManager.CreateNotificationChannel(channel);
        }

        private void SetOrientationForFormFactor(Activity activity)
        {
            if (activity.GetType() != typeof(SomeActivity))
            {
                if (DeviceInfo.IsOnPhone)
                {
                    if (activity.GetType() != typeof(DocumentViewerActivity))
                    {
                        activity.RequestedOrientation = ScreenOrientation.Portrait;
                    }
                }
                else
                {
                    activity.RequestedOrientation = ScreenOrientation.SensorLandscape;
                }
            }
        }

        void IActivityLifecycleCallbacks.OnActivityDestroyed(Activity activity)
        {
        }

        void IActivityLifecycleCallbacks.OnActivityPaused(Activity activity)
        {
            TrySetSessionManagerFocus(false);
        }

        void IActivityLifecycleCallbacks.OnActivityResumed(Activity activity)
        {
            TrySetSessionManagerFocus(true);
        }

        void IActivityLifecycleCallbacks.OnActivitySaveInstanceState(Activity activity, Bundle outState)
        {
        }

        void IActivityLifecycleCallbacks.OnActivityStarted(Activity activity)
        {
        }

        void IActivityLifecycleCallbacks.OnActivityStopped(Activity activity)
        {
        }

        private void TrySetSessionManagerFocus(bool focus)
        {
            _sessionManager ??= Mvx.IoCProvider.Resolve<ISessionManager>() as SessionManager;
            _sessionManager?.SetFocus(focus);
        }
    }

@preetanshumishra
Copy link
Author

@Cheesebaron Above is my Application class for Android. I have a Custom AppStart and Setup as well.

@preetanshumishra
Copy link
Author

preetanshumishra commented May 15, 2024

    public class Setup : MvxAndroidSetup<App>
    {
        public Setup()
        {
        }

        public override IEnumerable<Assembly> GetViewModelAssemblies()
        {
            var result = new List<Assembly>(base.GetViewModelAssemblies());

            result.Add(typeof(Setup).Assembly);

            return result;
        }

        protected override IMvxApplication CreateApp(IMvxIoCProvider iocProvider)
        {
            return new App();
        }

        protected override void InitializeFirstChance(IMvxIoCProvider iocProvider)
        {
            base.InitializeFirstChance(iocProvider);

            var storageLocation = new StorageLocationService();
            iocProvider.RegisterSingleton<IStorageLocationService>(storageLocation);
        }

        protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
        {
            base.FillTargetFactories(registry);

            registry.RegisterPropertyInfoBindingFactory(typeof(MvxNumberPickerValueTargetBinding),
                                                        typeof(NumberPicker),
                                                        nameof(NumberPicker.Value));
            registry.RegisterPropertyInfoBindingFactory(typeof(MvxDaterPickerValueTargetBinding),
                                                        typeof(DatePicker),
                                                        nameof(DatePicker.DateTime));
        }

        protected override IMvxAndroidViewPresenter CreateViewPresenter()
        {
            return new ViewPresenter(AndroidViewAssemblies);
        }

        public override void LoadPlugins(IMvxPluginManager pluginManager)
        {
            base.LoadPlugins(pluginManager);

            pluginManager.EnsurePluginLoaded<MvvmCross.Plugin.Json.Plugin>();
            pluginManager.EnsurePluginLoaded<MvvmCross.Plugin.Messenger.Plugin>();
        }

        protected override void RegisterDefaultSetupDependencies(IMvxIoCProvider iocProvider)
        {
            //https://github.com/MvvmCross/MvvmCross/issues/3572
            var nameMappingStrategy = CreateViewToViewModelNaming();
            Mvx.IoCProvider.RegisterSingleton(nameMappingStrategy);

            base.RegisterDefaultSetupDependencies(iocProvider);
        }

        protected override Microsoft.Extensions.Logging.ILoggerProvider CreateLogProvider()
        {
            LoggerConfig.OverrideBaseDirectoryForLogs(Application.Context.ExternalCacheDir?.AbsolutePath);

            return new MvxLoggerProvider();
        }

        protected override Microsoft.Extensions.Logging.ILoggerFactory CreateLogFactory()
        {
            return new MvxLoggerFactory();
        }
    }

@preetanshumishra
Copy link
Author

preetanshumishra commented May 15, 2024

    public class AppStart : MvxAppStart
    {
        private readonly IAnalyticsService _analyticsService;

        private IConnectivityService _connectivityService;

        public AppStart(
            IMvxApplication application,
            IMvxNavigationService navigationService)
            : base(application, navigationService)
        {

        }

        protected override Task NavigateToFirstViewModel(object hint = null)
        {
            var tcs = new TaskCompletionSource<bool>();
            Task.Run(async () => tcs.SetResult(await SignInSilent()));
            var isAuthenticated = tcs.Task.Result;

            if (isAuthenticated)
            {
                NavigationService.Navigate<HomeViewModel, bool>(true).GetAwaiter().GetResult();
            }
            else
            {
                NavigationService.Navigate<LoginViewModel>().GetAwaiter().GetResult();
            }

            return Task.CompletedTask;
        }

        private Task<bool> SignInSilent()
        {
            var authorizationService = Mvx.IoCProvider.Resolve<AuthorizationService>();
            return authorizationServerService.SignInSilent();
        }

        protected override async Task<object> ApplicationStartup(object hint = null)
        {
            // Some Code here.
            return await base.ApplicationStartup(hint);
        }
    }

@preetanshumishra
Copy link
Author

@Cheesebaron I have added more info. Please have a look.

@Cheesebaron
Copy link
Member

Cheesebaron commented May 16, 2024

You don't call RegisterAppStart in your MvxAppStart class.

You main application doesn't specify your MvxAppStart. Should look like:

public class MainApplication : MvxAndroidApplication<Setup<AppStart>, AppStart>

@preetanshumishra
Copy link
Author

preetanshumishra commented May 16, 2024

@Cheesebaron Is there a difference between how to specify the AppStart in Android and iOS?
Also, I haven't called RegisterAppStart in iOS anywhere but it's working fine. How is that?

@entdark
Copy link
Contributor

entdark commented May 16, 2024

@preetanshumishra

MvvmCross does the intial navigation on Android very weird with a proxy MvxStartActivity - which is kinda wrong on Android and also creates undefined behavior with restoration. I proposed to change that, so we have only 1 host Activity (in case of Fragments navigation only) that is started and remains one and only active: #4846

Personally I solved the issue but implementing my own startup navigation:
https://github.com/entdark/JKChat/blob/master/JKChat.Android/Application.cs#L50-L118
to avoid using the proxy Activity and navigate to the first VM which is a Fragment.

@Cheesebaron
Copy link
Member

@entdark you don't have to have the MvxStartActivity it is there for legacy purposes for when you want to show your own splash animation. All init happens now in MvxAndroidApplication which navigates to the ViewModel you registered in MvxAppStart.

As for having a single navigation host to do Fragment navigation, you can do regardless of a MvxStartActivity being present or not.

@preetanshumishra
Copy link
Author

preetanshumishra commented May 17, 2024

You don't call RegisterAppStart in your MvxAppStart class.

You main application doesn't specify your MvxAppStart. Should look like:

public class MainApplication : MvxAndroidApplication<Setup<AppStart>, AppStart>

@Cheesebaron Thank you! One more question,
Where should I be calling RegisterAppStart() in the MvxAppStart class?
Please provide a reference to a sample project if you can, will be really helpful.

@preetanshumishra
Copy link
Author

preetanshumishra commented May 17, 2024

Refer to:

@Cheesebaron Thank You!
Yes, I do call the RegisterAppStart in my App class excatly like the Playground.Core.App class.
RegisterCustomAppStart<AppStart>(); at the end of the Initialize() method.
Everything works perfectly in iOS. The problem is only with Android.
Is there something I am missing in my MvxAppStart class?

@entdark
Copy link
Contributor

entdark commented May 20, 2024

@entdark you don't have to have the MvxStartActivity it is there for legacy purposes for when you want to show your own splash animation. All init happens now in MvxAndroidApplication which navigates to the ViewModel you registered in MvxAppStart.

As for having a single navigation host to do Fragment navigation, you can do regardless of a MvxStartActivity being present or not.

Unfortunantely currently MvvmCross never calls IMvxAppStart.Start or IMvxAppStart.StartAsync anywhere but in MvxStartActivty. There is RunAppStart method in MvxAndroidApplication but it's never called.

@preetanshumishra
Copy link
Author

@entdark you don't have to have the MvxStartActivity it is there for legacy purposes for when you want to show your own splash animation. All init happens now in MvxAndroidApplication which navigates to the ViewModel you registered in MvxAppStart.
As for having a single navigation host to do Fragment navigation, you can do regardless of a MvxStartActivity being present or not.

Unfortunantely currently MvvmCross never calls IMvxAppStart.Start or IMvxAppStart.StartAsync anywhere but in MvxStartActivty. There is RunAppStart method in MvxAndroidApplication but it's never called.

@entdark That's exactily what I found when I was checking out the MvxAndroidApplication class. I tried to call the RunAppStart method at different places but it didn't work, not sure exactly from where it should be called.

@entdark
Copy link
Contributor

entdark commented May 20, 2024

And proper startup navigation should be after Main Launcher Activity got created/resumed, not just in Application. Since Application can be started by a Service, BootReceiver or something. So there should be an Activity that gets launched first then does startup navigation to the first VM.
And that's why my "hack" in the link above is the only right way since I monitor for the host activity to be ready then perform the proper navigation to the first VM (the actual right way to do that inside the host Activity as proposed in the PR above).

@preetanshumishra
Copy link
Author

@Cheesebaron @entdark My issue was completely different from what I initially thought.
I followed the Playground app and created a new project with MvxActivity and MvxStartActivity.
Both worked completely fine. Forced me to think the issue was somewhere else.
Turns out, with the upgrade, the Android project needed an extra SQLite dependency to be added.
Closing the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
t/bug Bug type
Development

No branches or pull requests

3 participants