Dagger Hilt : Scope dependencies for parent/child fragment - dagger-2

Hilt does not provide a #ParentFragmentScope.
I want to share LoginInfo between Parent ViewModel and Child ViewModel.
I want to inject LoginInfo with the same scope into Parent ViewModel and Child ViewModel.
If you make it with #FragmentScope, Parent and Child ViewModel have different address values for LoginInfo instances.
And I don't want to share the view model as one.
What should I do?

Related

Autofac - Use the same DbContext across multiple repositories

I have a desktop app using Autofac. The framework I'm using doesn't provide hooks for dependency injection and thus the view models are instantiated using the service locator pattern.
One of my view models has two repositories that it uses. The repositories both take a single object, with is the applications DbContext.
Autofac instantiates two DbContext instances - one for each repository. The two repositories should use the same DbContext instance.
The service locator is implemented as:
ServiceLocator.Current = new ServiceLocator((type) =>
{
var resolved = _container.Resolve(type);
return resolved;
});
Where _container is an IContainer instance built thusly:
var builder = new ContainerBuilder();
/* snip */
_container = builder.Build();
How can I have Autofac use the same DbContext for all dependencies when creating the instance of the view model?
I definitely don't want a singleton context - subsequent instantiations of the view model, or other view models, should yield a different DbContext.
You could employ custom lifetime scopes for that (https://autofac.readthedocs.io/en/latest/lifetime/working-with-scopes.html).
The idea is to register DbContext with InstancePerLifetimeScope() and then create a lifetime scope for every instance of view model. Autofac would create a single instance of DbContext per lifetime scope (and, thus, per view model).
The issue would be that custom lifetime scopes require manual disposal so you'd need to take care of LifetimeScope.Dipose() call after your view model is no longer needed.

Global Model Not Accesible

I declared a model in Component.js of a UI5 application as below
init: function() {
sap.ui.core.UIComponent.prototype.init.apply(this);
var oModel1 = new sap.ui.model.json.JSONModel("model/mock.json");
sap.ui.getCore().setModel(oModel1, "oModelForSales");
},
but was not able to access the model in any of the onInit methods inside controllers unless the model is set on view instead as below:
var oModel1 = new sap.ui.model.json.JSONModel("model/routes.json");
this.getView().setModel(oModel1);
The log for sap.ui.getCore().getModel("oModelForSales") in controllers onInit shows the model as undefined but I was able to fetch it in onBeforeRendering handler.
Why are core models, set in Component.js, not accessible in onInit?
Avoid setting models on the Core directly if you're using Components. Components are meant to be independent and reusable parts and therefore will not inherit the Core models by default. Models should be set depending on your business case:
Models declared in the app descriptor (manifest.json) section /sap.ui5/models will be set on the Component. They are automatically propagated to its descendants. Given default model, the following returns true:
this.getOwnerComponent().getModel() === this.getView().getModel() // returns: true
Note: calling this.getView().getModel() in onInit will still return undefined since the view doesn't know its parent at that moment yet (this.getView().getParent() returns null). Therefore, in onInit, call the getModel explicitly from the parent that owns the model. E.g.:
{ // My.controller.js
onInit: function {
// The view is instantiated but no parent is assigned yet.
// Models from the parent aren't accessible here.
// Accessing the model explicitly from the Component works:
const myGlobalModel = this.getOwnerComponent().getModel(/*modelName*/);
},
}
Set models only on certain controls (e.g. View, Panel, etc.) if the data are not needed elsewhere.
Set models on the Core only if the app is not Component-based.
If Core models or any other model from upper hierarchy should still be propagated to the Component and its children, enable propagateModel when instantiating the ComponentContainer.
new ComponentContainer({ // required from "sap/ui/core/ComponentContainer"
//...,
propagateModel: true // Allow propagating parent binding and model information (e.g. from the Core) to the Component and it's children.
})
But again, this is not a good practice since Core models can be blindly overwritten by other apps on FLP as SAP recommends:
Do not use sap.ui.getCore() to register models.
About the Core model being undefined in onInit: This is not the case anymore as of version 1.34.0. The Core model can be accessed from anywhere in the controller. However, descendants of ComponentContainer are unaware of such models by default as explained above.
You should not set the Model to the Core.
Instead set it to the Component. That way the Controllers and Views belonging to that Component will be able to access it.
Adding more info on this:
During onInint of a controller, the view/controllers do not know their parent as where would they land, and hence they can not refer to the model.
However, this can be achieved from the following code:
this.getOwnerComponent().getModel()
As the component is already initialized and should return the model.
can you once try this code -
init:function(){
//sap.ui.core.UIComponent.prototype.init.apply(this);
var oModel1 = new sap.ui.model.json.JSONModel("model/mock.json");
sap.ui.getCore().setModel(oModel1,"oModelForSales");
console.log(sap.ui.getCore().getModel("oModelForSales"));
sap.ui.core.UIComponent.prototype.init.apply(this);
},
and then in you init method of any controller try -
console.log(sap.ui.getCore().getModel("oModelForSales"));
I think sap.ui.core.UIComponent.prototype.init.apply(this);->calls the create content methods and your view and controllers are initialised even before your model is defined, hence you get undefined as model. Using my approach, we create the model first and then call the super init method in Component.
Note #Admins-> I dont have enough points to comment, so adding an answer.

How to use the container to resolve a collection of viewmodels associated with a model?

Big Picture goal: I'd like to edit models in a Data window that is full of property pages that edit the given model. I'd like to mark the models with multiple interfaces that they satisfy. For each interface, an associated propertypage viewmodel and view exist.
What I'm struggling with is how can I resolve the Collection of property page viewmodels from a given model that satisfies 1-N interfaces.
I was wondering if I could put a property page view model factory within the container? I would try to resolve a collection of property page viewmodels from the container, and the container would use the factory to correctly generate the viewmodels needed. I could hand that collection of viewmodels to a data window, which would use the ViewModelToViewConverter to generate the views of the viewmodels.
Is it possible to register a factory with the container? Is this the best way to achieve this goal? I suppose I could have the data window's viewmodel handle converting the model to a collection of viewmodels, but that feels out of scope.
I think you can create a list (ObservableCollection) of models that you want to edit in the main view model. Then you create an ItemsControl with a custom view as data template:
<ItemsControl ItemsSource="{Binding MyModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<myViews:ModelEditorView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then you have this view model which is automatically created for your ModelEditorView:
public class ModelEditorViewModel : ViewModelBase
{
public ModelEditorViewModel(MyModel model /*, other dependency injections here*/)
{
Argument.IsNotNull(() => model);
Model = model;
}
public MyModel Model { get; private set; }
}
Then everything will be created for you automatically.

MVVM: Should a ViewModel have a member to another ViewModel?

The dependent ViewModel gets injected via the constructor (IoC container).
Example: ProductSelectionViewModel uses ShoppingBasketViewModel.
Is this a common practice or is this THE recommended way? I don´t think so...
How should it be done right?
Should the view use the 2 ViewModels?
Mediator pattern?
Event driven?
Personally I don´t like the last one.
There's nothing wrong with a view model having a direct reference to another view model, if it is a required dependency, then injecting it via the constructor is fine.
If you wish for a view model to be able to create new instances of another view model, then injecting a view model factory type would be the way to go.

how (multiple=true) presenter and view can be initialized with parameters using eventBus.addHandler?

With the Mvp4g architecture, (Only)one instance of the view (injected using #Presenter annotation)is associated with its presenter.
In my case, I have a EntityView with its Presenter EntityPresenter.
whenever user clicks on an Leaf node of a Navigator tree,
I add a new Tab into TabSet. And this new Tab will contain an EntityView.
So, I will have as many EntityView as many Tab in the TabSeT.
I have set multiple=true for EntityPresenter.
EntityView's constructor accepts one argument.
#Inject
public EntityView(final Record view) {
//some initialization
}
Question is, where I do (from another presenter):
EntityPresenter presenter = eventBus.addHandler(EntityPresenter.class);
I have one argument Record params which I want to pass to EntityView's constructor, how to do that?
and annotating constructor(accepting argument) with #Inject will inject EntityView to EntityPresenter ?
I suggest to use an EventHandler - that's a presenter without a view in mvp4g - which get an event showEntity(long key). In the onShowEntity(...) - method you can create the presenter with the statement:
EntityPresenter presenter = eventBus.addHandler(EntityPresenter.class);
With that reference of the instance, you can esaly set the key in the presenter.
But keep in mind, you have to manage your presenter instances by yourself, when using multiple=true.