CalciumFramework / Samples

Samples demonstrate how to use some of the features of Codon.
3 stars 2 forks source link

Xamarin.Forms sample #2

Closed abrasat closed 7 years ago

abrasat commented 7 years ago

Could you please extend the Sample001 with the implementation for a Xamarin.Forms based project ? Does the Codon framework also have a IoC container implementation (.NET Standard) that can be used for dependency injection for Xamarin.Forms projects ?

DVaughan commented 7 years ago

Yes, the FrameworkContainer is located in the .NET Standard "Codon" package. It can and is used for all supported platforms. To use it in your Forms project, access it via the Codon.Dependency class. It's initialized upon first use.

I was reluctant to create a Forms sample because of its rate of change and dependencies. I may put something together, but can't promise.

abrasat commented 7 years ago

How would the "Bootstrap" code for Xamarin.Forms (with .NET Standard) look like ? I am not sure where to put the dependency resolve code and the navigation route initialization code in this case.

DVaughan commented 7 years ago

Here's a sample solution with Xamarin Forms.

You'll notice it includes a FormsNavigationService, which was needed to utilize the Forms Navigation system. I'll merge that with the code-base at a later time. I also included a .NET Standard project for the app logic. While not necessary, I find that working with shared projects and pre-processor directives can make refactoring troublesome.

I've smoke tested the Android and UWP apps, but not the iOS.

FormsStarter.zip

abrasat commented 7 years ago

Thank you for the sample. The navigation works fine, but the show dialog and publish message do not work (I tested it with the VS android emulator). I tried to debug and the Page1ViewModel constructor is not called. Not sure what Page1ViewModel instance does the dependency resolver in MainPage return (default constructor ?). Do the DialogService and SettingsService also have to be registered in the bootstrapper code?

Regarding the bootstraper code, would be possible to have it also as .NET standard project, and inject somehow the platform specific dependencies over the App() constructor ? Or is better to leave it as shared code ?

DVaughan commented 7 years ago

I've attached an updated sample. You need to let the DialogService for Android know how to resolve the current Activity. You can do this in the MainActivity class, like so: Dependency.Register<Activity>(this);

Because we're using a custom service used by the Page1ViewModel, we need to ensure that the Bootstrapper has finished running. You can do that in the MainPage.xaml.cs, as shown:

protected override void OnAppearing()
        {
            base.OnAppearing();

            if (BindingContext == null)
            {
                BindingContext = Dependency.Resolve<Page1ViewModel>();
            }
        }

FormsStarter.zip

abrasat commented 7 years ago

Looks better now, thanks !

Are the Register() calls asynchronous ? What happens if Resolve() cannot get object instance ? I mean, what would happen if in OnAppearing() the dependency is still not resolvable ?

DVaughan commented 7 years ago

Register calls are synchronous. If Resolve is called using an interface, like so: Dependency.Resolve<IMyInterface>(); and no registration is found for IMyInterface, a TypeResolutionException is thrown. In the same situation, if Resolve is called using a concrete type argument, then a new instance of that type is returned.

The problem was that the bootstrapper didn't have a chance to register the FormsNavigationService before the MainPage attempted to resolve Page1ViewModel, which in turn caused INavigationService to be resolved via dependency injection. This resulted in the default platform specific navigation service being passed to the Page1ViewModel constructor, and not the FormsNavigationService.

abrasat commented 7 years ago

I have an application where I would like to inject more other dependencies in the ViewModel (kind of web services where for instance I pass the interface to the view model constructor; I can then either register the "real" implementation, or a mock implementation). These dependencies are using .Net standard libraries (for instance NewtonsoftJson). The problem is that the boostrapper is running in "shared" code, that means in Android environment, where NewtonsoftJson is not available, and thus the dependency cannot be registered. But in principle that would occur also in other cases, for instance if there is a ILog implementation for .Net standard, which does not exist in Android. How can I register it ? Is there any possibility to set Codon dependencies also in the .Net standard (or PCL for earlier Xamarin projects) environment ? That would probably imply that the pages are later created, as they need the view model as binding context.

DVaughan commented 7 years ago

Yes, you can do that. The only reason the bootstrapper is sitting in shared code is that the routes need to refer the the pages in the app. Create another class and place the routing configuration in it. Move the bootstrapper to your .NET Standard library. You app class in the shared library will now call your routing config class and your bootstrapper's Run method.

I should also mention that Codon's FrameworkContainer supports default type and type name attributes, which allows it to resolve the implementation if no mapping registration is made. You can decorate your interfaces with a [Codon.InversionOfControl.DefaultType(typeof(MyConcreteTypeThatImplementsThisInterface), Singleton=true)] or a [Codon.InversionOfControl.DefaultTypeName("MyNamespace.FooClass, MyAssembly", Singleton = true)] or even a [System.ComponentModel.DefaultValue(typeof(MyConcreteTypeThatImplementsThisInterface))] which doesn't require a Codon reference. When using the built-in DefaultValue attribute, Codon assumes you want a singleton.

These attributes are the reason that Codon doesn't require any initialization from your app.

abrasat commented 7 years ago

Thank you for the information. The attributes are a nice option although I personally prefer the "bootstrap" style of setting the dependencies at one place in code.

What happens if both kinds of dependency are necessary ? I mean some for the different platform-dependent implementations (for Android, UWP, iOS), and also some for different .NET standard implementations. Do you eventually have any samples about how to organize the bootstrap for such a scenario ?

DVaughan commented 7 years ago

Attributes provide a fall-back mechanism. If there is no mapping registration, the FrameworkContainer looks for the attributes. The Xamarin Forms sample demonstrates how to register a type. I'm not sure what you're asking.

abrasat commented 7 years ago

I understand how to register the types. What I cannot figure out is how to change the code in view to able to pass an additional interface to the view model constructor, which is registered in the .NET standard code, something like this:

public Page1ViewModel( IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService, IMyWebService myWebService)

Where must be placed this .NET standard code to register the additional type, as this must be executed before constructing the view model ?

Dependency.Register<IMyWebService>(() => new MyWebService(), true);

The Page1ViewModel is actually already constructed and resolved in the shared project, in MainPage OnAppearing:

BindingContext = Dependency.Resolve<Page1ViewModel>();

At this point, MyWebService is not registered yet.

DVaughan commented 7 years ago

Place the dependency registration in your bootstrapper. The FrameworkContainer selects the constructor of your view-model that has the most number of parameters (or is decorated with a InjectDependencies attribute). Your IMyWebService will be resolved automatically and passed to your view-model's constructor.

abrasat commented 7 years ago

Is there any possibility to set breakpoints and debug the code from the Page1ViewModel or Page2ViewModel from the last sample above ? The application is working correctly, but the breakponts are hit for instance in the Bootstrapper code, but not in the ViewModels (I am using the VisualStudio Android Emulator).

DVaughan commented 7 years ago

I just verified that I am able to debug the Page1ViewModel. I set a breakpoint in the constructor and ShowMessage method, and the debugger did break. (VS Android Emulator) I have, however, seen this issue before with Xamarin. I recall there was an issue resolved some time ago. I suggest you ensure that VS 2017 and Xamarin are up to date via the Tools/Extension and Updates option in VS.

abrasat commented 7 years ago

Thank you for the reply, Found meanwhile the workaround here Are you using the Android emulator integrated in VisualStudio (under Tools), or the stand-alone VisualStudio Emulator for Android ?

abrasat commented 7 years ago

I need to use the Environment.GetFolderPath functionality in my Xamarin.Forms project. Do I need to implement a DependencyService like described here , or can I solve it with the Codon FrameworkContainer registration/resolve mechanisms (my bootstrapper code is actually in a .NetStandard library). If possible with Codon, could you please put a code snippet about how to do it ?

abrasat commented 6 years ago

What happens if constructing and initializing a service (lets say a connection to some cloud, database or whatever) might take some time. If such a service would be registered in the bootstrapper would block the startup of the application. Wouldn't be better to initialize and register such services asynchronously in the application OnStart() method ? In this case the problem is that the view models are already created and thus the Resolve() for such a service cannot be called in the view model constructor, as not yet available to that point. Is there any possibility in Codon to publish an event when some object (service in our case) has been registered ? Or is there a better way to implement such a scenario ?

DVaughan commented 6 years ago

Codon's FrameworkContainer does not have an event for registrations.

There are several ways you can do this. The way I usually do this is that I create an EnsureInitializedAsync method in the service that must be awaited before using the service. That method can be called when the app is starting or when the service is required; depending on the UI thread requirements. Alternatively, you could publish a message via the IMessenger, announcing that the service is available when it is initialized.

abrasat commented 6 years ago

Thanks for the reply. If I understand correctly, the best way to handle the creation and initialization of services requiring asynchronous access is directly in the application (by using some asynchronous factories), without using the Codon framework Register/Resolve methods.

DVaughan commented 6 years ago

Yes, since the IoC container works synchronously.