-
Notifications
You must be signed in to change notification settings - Fork 104
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
Scope Concept #383
Comments
Currently there is a prototype implementation on the branch 383_scope_concept My first impression is, that the mechanism works quite good but there are some things to optimize:
Szenarios for point 2: Szenario 1: ViewModelHierarchie Szenario 2 ViewModelHierarchie |
I noticed that there is still no lifecycle for the ScopeStore itself - it's still a singleton. If you open two of these hierarchies side by side - won't they share the same scope instances between them? |
In this approach (and in the prototype) there is no ScopeStore anymore. Can you check it and tell me what you think? |
Sorry - I neglected to switch branches - I will definitely check it out and let you know :) |
I have refactored my code to use the new approach and it works well. I reviewed the code and your implementation is really well done :) In my use case I needed to do the following:
It would be nice to be able to:
BTW, Bottich is something that doesn't have English meaning (AFAIK). Other alternatives might be "scopeHolder" or "scopeMap". |
Re: @ScopeProvider Enhancing @InjectScope might be more intuitive:
|
Current WIP: @ScopeProvider does not work due to technical restrictions (I removed it) .providedScopes(…) in ViewLoader you can explicit add Scopes for the subview @InjectContext Resolves the Context of the parent (this is now a Markerinterface - you should not create instances by your own) .context(context) in ViewLoader provides mvvmFX the Context of the parent. this is nescessary to bypass already existing ApplicationFramework State (for example existing Scopes) from the parent to the child. |
Tested the updated implementation and it works well. I agree with not exposing Context creation - providedScopes(...) works well for my use case :) Re: @ScopeProvider - I think that the default behaviour should be that the framework creates a new instance when it doesn't already exist in the Context. I think this is intuitive default behaviour (i.e. the framework stays out of your way). I think, like originally proposed, @ScopeProvider should only be needed for cases where the default behaviour needs to be overridden. |
@lestard if we have the scenario that somebody creates the ViewModel by himself to provide the given instance to the ViewLoader, the necessary things like Scopes won't get injected and can't be accessed. Fixed it here: 39a766d Now we know what the line we've deleted was for :-) |
I've pushed some tests to reproduce still open problems. Problem 1Scope isolation between FXML branches doesn't work The example's fxml files have this structure:
There is one scope type that is injected in B and C.
b)
c)
d)
The reason for the wrong behaviour of c) is that FXML files are loaded from "left to right" (depth-first search) so that in this case B is loaded before C and because B is a ScopeProvider it is used for C too. While loading we have no clue that B is not above C in the hierarchy. The following fxml structure would look the same from the point of view of the FXMLLoader:
The only difference is the order of the So we have 2 specific problems here:
Problem 2When a ScopeProvider viewmodel isn't injected into it's view, it is ignored by the ViewLoader. Take the example from above:
When A is a ScopeProvider but the I think this problem isn't too hard to solve but we need to refactor the loading process. At the moment we are based on existing fields in the View class. If no such field for the ViewModel exists we doen't even create a ViewModel instance. To fix this we have to check if the View has a ViewModel field. If not, we need to check if the generic type of the View (the viewModel type) has a ScopeProvider annotation. Problem 3If the developer uses a fx:root approach, it's not possible to use Contexts. public class MyView extends VBox implements FxmlView<MyViewModel> {
@InjectContext
private Context context;
public MyView() {
FluentViewLoader.fxmlView(MyView.class)
.codeBehind(this)
.root(this)
.context(context) // not possible because context == null at this point in time
.load();
}
} |
Make ScopeProvider implicit ( first time it's asked for it gets created ). If you don't want to inherit a scope then @InjectScope( inheritFromParent="false") You don't have to worry about ScopeProvider exceptions, order of loading etc. |
@gtnarg About your "inheritFromParent=false" proposal: In a component you only want to say "I need an instance of Scope XY". This way you can reuse the component in different places by managing the scope that is used by the component from outside. In the above examples of "Problem A" there is a scenario possible: |
Solution for problem 1 would be: Remove / delete the scope instance in the context when the initialize method of the view with the ScopPprovider annotated ViewModel. This is possible due to the loading / initialize order of the view hierarchy. Therefore we would need a callback of the FXMLLoader when a initialize method of a controller/code behind was called. Possible solutions:
|
Everywhere @ScopeProvider is declared in the test cases, a matching @InjectScope exists - breaking the rule above. @InjectScope( inheritFromParent="false" ) == @ScopeProvider + @InjectScope One is needed occasionally - the other is needed every time (as I understand it) - convention over configuration should cover the common case (making reasoning easier). Perhaps the failing test cases (and possible workarounds) are the consequence of a design that is hard to reason about. Decent logging should cover "doesn't work as expected". |
@gtnarg D + E does have a shared @InjectScope MyScope scope; With @ScopeProvider(scopes={MyScope.class}) you can annotate B and C, with the inheritFromParent you would have to inject a Scope into B and C to create new Scope instances. |
@sialcasa - the design makes more sense now - thanks for clarifying. |
You're welcome. Any ideas for the last technical problem? :-) |
We will release the current scopes implementation with the next version 1.5.0 and mark it as Alpha state which means that the API will probably change in the future. I will let this issue open to keep the discussion in a single place |
Hi everyone,
What do you think? |
I am currently trying to use the new scope feature in our project and got a strange behaviour. I created a new scope and passed it to the FluentViewLoader via providedScopes(). I expected the newly created scope to be injected into my view models but got a new one instatiated by the di container. The reason for this behaviour was the definition of @ScopeProvider on the top level view model. I think it would be better to get the provided scope injected no matter if a @ScopeProvider has been set or not. What do you think? |
Hi denny, On the other hand there might be examples where this is confusing. See this example:
Both I tend to still think that the providedScope should have a higher priority (like what you requested) but I'd like to discuss such corner cases that might speak against this. |
Interesting point but the scenario sounds strange. When |
Another point: i am still refactoring our app to use the new scope mechanism and have some trouble with testing of view models. Due to the injection of the scope via I thought about another possibility. Would it be possible to allow the
|
When it comes to testing I would make the field of the Scope protected. This way you can set the field in your test case and then invoke the initialize method by hand. I don't see any problems with this approach. It's actually a common discussion when it comes to dependency injection in general on wether it's better to use constructor injection, field injection or method injection. |
|
Currently we rethink the behavior of scopes and we tend to adopt the DI-Mechanism from Angular2 for our Scopes.
The mechanism injects same instances of a type into the views that are in a hierarchy.
Example
Given the a Hierarchie of Views:
ViewHierarchie
ParentView
-ViewA
-ViewC
--ViewD
-ViewB
--ViewC
--ViewD
Szenario 1:
ViewModelHierarchie
ParentViewModel
-ViewAViewModel - @InjectScope MyScope scope; (ObjectID = 1)
--ViewCViewModel - @InjectScope MyScope scope; (ObjectID = 1)
--ViewDViewModel - @InjectScope MyScope scope; (ObjectID = 1)
-ViewBViewModel - @InjectScope MyScope scope; (ObjectID = 2)
--ViewCViewModel - @InjectScope MyScope scope; (ObjectID = 2)
--ViewDViewModel - @InjectScope MyScope scope; (ObjectID = 2)
Szenario 2:
ViewModelHierarchie
ParentViewModel - @InjectScope MyScope scope; (ObjectID = 1)
-ViewAViewModel - @InjectScope MyScope scope; (ObjectID = 1)
--ViewCViewModel - @InjectScope MyScope scope; (ObjectID = 1)
--ViewDViewModel - @InjectScope MyScope scope; (ObjectID = 1)
-ViewBViewModel - @InjectScope MyScope scope; (ObjectID = 1)
--ViewCViewModel - @InjectScope MyScope scope; (ObjectID = 1)
--ViewDViewModel - @InjectScope MyScope scope; (ObjectID = 1)
The principle is, that the highest Injection point decides for the subviews which scope instance is used.
The text was updated successfully, but these errors were encountered: