Skip to content

DryIoc#535

Merged
brianlagunas merged 30 commits intoPrismLibrary:masterfrom
joacar:pr-517
Jun 27, 2016
Merged

DryIoc#535
brianlagunas merged 30 commits intoPrismLibrary:masterfrom
joacar:pr-517

Conversation

@joacar
Copy link
Copy Markdown

@joacar joacar commented Apr 7, 2016

This PR contains a FormsApplication class that, when built in Test configuration, acts base class for PrismApplicationBase instead of Xamarin.Forms.Application to let us test properly.

As new configuration is introduced the appveyor config must be updated to encompas this.
#517

@dnfclas
Copy link
Copy Markdown

dnfclas commented Apr 7, 2016

Hi @joacar, I'm your friendly neighborhood .NET Foundation Pull Request Bot (You can call me DNFBOT). Thanks for your contribution!
You've already signed the contribution license agreement. Thanks!

The agreement was validated by .NET Foundation and real humans are currently evaluating your PR.

TTYL, DNFBOT;

@brianlagunas
Copy link
Copy Markdown
Member

brianlagunas commented May 19, 2016

I see this is where you added the Test configuration that PR #412 depends on. This actually looks really good. I don't think having the Test config is a big deal, and it adds a ton of test coverage. It seems my abstraction of the IApplicationProvider caused some conflicts. Do you want to fix them, or do you want me to just copy and paste what you have here into my solution?

@joacar
Copy link
Copy Markdown
Author

joacar commented May 20, 2016

Feel free to decide what is most convenient for you. I'm happy to do it tomorrow when I've time.

I'll create a new branch from master and implement the Test-configuration approach there and then submit a PR. Is that OK?

Is there an appveyor config to include the new configuration? Currently I think that the Test configuration is based on Debug, I'll change it to Release

@brianlagunas
Copy link
Copy Markdown
Member

Yes, please update and change the configuration to be based off of Release. I'll update AppVeyor to build off the Test configuration.

@brianlagunas
Copy link
Copy Markdown
Member

Can you get this one sync'd up with master?

@joacar
Copy link
Copy Markdown
Author

joacar commented May 25, 2016

Synced and merged.

var navigationService = Container.Resolve<INavigationService>();
((IPageAware)navigationService).Page = page;
}
return Container.Resolve(type);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The navigation service cannot be a singleton. We have to create a new instance each time and then override the parameters of the VM ctor to inject the correct instance. Can this be done in DryIoc?

@joacar
Copy link
Copy Markdown
Author

joacar commented May 26, 2016

Saw this as well yesterday and posted a question on stackoverflow since I couldn't find how to do it in the docs.

DryIoc supports resolving overrloads using func var @override = Container.Resolve<Func<INavigationService, TypeToResolve>>(); return @override(navigationService) however this fails since TypeToResolve is not known at compile time.

I'll dig into this!

…property injected when resolving view models
@joacar
Copy link
Copy Markdown
Author

joacar commented May 26, 2016

Fixed. Need to update some tests as well as a new test to check the added functionality. Hopefully I will have time during the day but right now my job calls for attention.

@brianlagunas
Copy link
Copy Markdown
Member

We have moved to using a named instance for extensibility reasons. Yu can see the Unity registration here: https://github.com/PrismLibrary/Prism/blob/master/Source/Xamarin/Prism.Unity.Forms/PrismApplication.cs#L82

And the usage here: https://github.com/PrismLibrary/Prism/blob/master/Source/Xamarin/Prism.Unity.Forms/PrismApplication.cs#L51

This allows the developer to provide their own implementation more easily by overriding the INavigationService registration wit their own implementation.

Can we do it like this?

@joacar
Copy link
Copy Markdown
Author

joacar commented May 26, 2016

One option is to use named registration Container.Register<INavigationService, DryIocNavigationService>(serviceKey: "dryIocNavigationService"); and resolve using Container.Resolve(serviceKey: "dryIocNavigationService");

Another option availble using DryIoc is Container.Register<INavigationService, DryIocNavigationService>(ifAlreadyRegistered: IfAlreadyRegistered.Replace); that simple replace the previos registration.

Not sure if one has any advantage over the other (except that the last clears all previos registrations). Will ask the creator of DryIoc which is most performant

@brianlagunas
Copy link
Copy Markdown
Member

Let's use the named instance and go with consistency with the other containers. This makes documentation a lot easier too. Whenever I get to that point :)

Joakim Carselind added 2 commits May 27, 2016 09:04
@dansiegel
Copy link
Copy Markdown
Member

@joacar if you need to make it internal you can add

[assembly: InternalsVisibleTo( "Prism.Forms.Tests" )]

@joacar
Copy link
Copy Markdown
Author

joacar commented May 30, 2016

@dansiegel Tried that first time but it didn't compile. But that was because I forget to add the internal modifier to the method.. Stupid stupid me.

Thanks

/// <param name="type"><see cref="Type" /> to resolve</param>
/// <param name="navigationService">Overriding instance of <see cref="INavigationService" /></param>
protected virtual void ResolveTypeForPage(Page view, Type type, INavigationService navigationService)
protected internal virtual void ResolveTypeForPage(Page view, Type type, INavigationService navigationService)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know... this feels a little dirty to me. What if we just created a method responsible for creating the navigation service for the page instead of doing that logic inside of the ConfigureViewModel method directly? Maybe something like

INavigationService CreateNavigationServiceForPage(Page page)
{
    var navigationService = CreateNavigationService(); 
    ((IPageAware)navigationService).Page = page; 
    return navigationService;
}

Thoughts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that - it makes it clear that the navigation service utilize the page instance. Also I was thinking refactoring ConfigureViewModel like this

protected override void ConfigureViewModelLocator()
        {
            ViewModelLocationProvider.SetDefaultViewModelFactory((view, type) =>
            {
                var page = view as Page;
                if (page != null)
                {
                    return PageViewModelFactory(page, type);
                }
                return Container.Resolve(type);
            });
        }

        /// <summary>
        /// Resolve the view model of <paramref name="type"/> associated with <paramref name="page"/>
        /// </summary>
        /// <remarks>
        /// The method will set the <see cref="IPageAware.Page" /> property on the <see cref="INavigationService"/>  
        /// instance that will be injected into the view model.
        /// </remarks>
        /// <param name="page">The <see cref="Page"/> associated with the view model</param>
        /// <param name="type">View model type to resolve</param>
        /// <returns>View model instance of type <paramref name="type"/></returns>
        protected virtual object PageViewModelFactory(Page page, Type type)
        {
            // Use CreateNavigationServiceForPage here instead
            var navigationService = CreateNavigationService();
            ((IPageAware)navigationService).Page = page;
            ResolveTypeForPage(page, type, navigationService);
            // Resolve type using the instance navigationService
            var resolver = Container.Resolve<Func<INavigationService, object>>(type);
            return resolver(navigationService);
        }

Moving back to the original question, yes, it feels dirty. That method exists soley for the purpose of grabbing the instance injected into the view model so that it can be tested that the Page property of IPageAware is correct.

Another option would be to add extension methods (for test only) to INavigationService that returns the view model navigated to. E.g. Task<TViewModel> NavigateTo<TViewModel>(). Then we can check that the navigation service in the mock view model is correctly initialized.

If you have a better way please share

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the test didn't involve testing the method directly, but rather we just test the result: creating a Page, then get the VM from the binding context (which has a public navigation service property, grab the nav service, and cast it as IPageAware, and then make the check. Instead of trying to so tricks with methods and assembly attributes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect. Feel so stupid I didn't think of that before. Will implement the changes and adjust the tests.

Thanks

/// <param name="page">The <see cref="Page"/> associated with the view model</param>
/// <param name="type">View model type to resolve</param>
/// <returns>View model instance of type <paramref name="type"/></returns>
protected virtual object PageViewModelFactory(Page page, Type type)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where the IPageAware is being set

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is set in CreateNavigationService(Page)

@brianlagunas
Copy link
Copy Markdown
Member

I made some changes to the PrismApplicationBase which will make it easier to create container projects in the future and make them easier to maintain. Could you rebase from master and update the PrismApplication? We're almost there :)

@joacar
Copy link
Copy Markdown
Author

joacar commented Jun 10, 2016

Yes I'm aware of the changes, just havn't hade time to fix them :) Will implement the changes later this afternoon or tomorrow!

/// <typeparam name="TPage">The Type of Page to register</typeparam>
public static void RegisterTypeForNavigation<TPage>(this IContainer container) where TPage : Page
{
container.RegisterTypeForNavigation<TPage>(typeof(TPage).FullName);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be Name, not FullName

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 83 in PageNavigationService use typeof(ViewModel).FullName. Shouldn't this be using Name as well, otherwise it won't work

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joacar you can look at the updated Unity version which was updated for more clarity. It's not about Pages and classes. Since Prism is about MVVM it's really about Views and ViewModels which with Xamarin Forms are fulfilled with Pages and BindableBase types. You navigate to View's not ViewModel's which is why for TView we just get the type Name, and to discourage the bad practice of navigating to a ViewModel we're taking the FullName of the TViewModel.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing me in the right direction.

@brianlagunas
Copy link
Copy Markdown
Member

It's missing the new IPlatformInitializer implementation. See the Unity project for how to implement.

var app = new PrismApplicationMock();
var navigationService = ResolveAndSetRootPage(app);
await navigationService.NavigateAsync<ViewModelAMock>();
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to assert anything in this test?

@ederbond
Copy link
Copy Markdown
Contributor

Congratulations @joacar
I've never used DryIoC but after a few research I realized this is one of the fastest IoC Frameworks nowadays. Definately will use this with prism after your PR will be aceepted.

@brianlagunas Is there a timeframe for the release of Prism.Forms 6.2.0 + Prism.DryIoC.Forms aut of pre ?

@brianlagunas
Copy link
Copy Markdown
Member

@ederbond waiting on Xamarin to release the stable version of XF

@brianlagunas brianlagunas merged commit 162594d into PrismLibrary:master Jun 27, 2016
@brianlagunas
Copy link
Copy Markdown
Member

Great PR! Thanks for the hard work!

@joacar joacar deleted the pr-517 branch June 28, 2016 11:11
@lock
Copy link
Copy Markdown

lock bot commented Jan 29, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Jan 29, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants