Hello! Last time
we figured out the implementation of Subcomponent and the cases of using it on the example of a single screen . There will be several references to that article, so it is better to first read it.
Today we will discuss the creation of a
real authorized application zone and work with social networks. Of course, not without the help of Dagger!
Interesting? Welcome under the cut!
Abstracting
')
The previous article was based on the abstraction of a doctor and an assistant, but in order to describe the cases stated in the introduction, I decided to choose a more suitable one.
Imagine a large railway station with all its forks, semaphores and long freight trains. On the platform of the 3rd way, a crowd of people is waiting for its electric train in the drizzling rain. In the distance, a ringing whistle is already heard, people get up from their bags, when suddenly ... The long-awaited train arrives on the 7th route! It seems that the driver did not translate the arrow (and in our example it is the driver who translates the arrows).
And, in general, nothing terrible - to run to the desired platform through the overpass. It is terrible that on the 7th way from the other side a MOVIE pulls up!
STOP
Let's see how this could happen? There can be several reasons:
- The first is, of course, inattention. Perhaps our railway trucker got carried away writing sms to his wife to put water on dumplings.
- The human factor cannot be rejected either, after all, “there are many paths, but there is only one.”
- Or maybe he specifically? Oh, these current hard times ...
In fact, the reason for this is one - this is the very possibility of the driver to go there. He spent a long time on the road and should not be burdened by the logistics of each station.
Fuh ... How good, after all, in the life of a machinist, he doesn’t translate the arrows himself, but the
station workers do it for him
.
But where is the authorized zone?
A little pain
Suppose we have a task: an authorized user must see one animation, and an anonymous another. In most applications that implement this logic, you will see something like the following:
if (userIsAuthourized) {
And you will see many times :(
At best, we will have two copies of each screen, “dependent” on the authorization state; with a common ancestor that implements the basic functionality. And this is not even delving into the business logic ...
You may have already guessed what I'm leading to.
Translating this situation to our station -
every driver (software component)
chooses which way to go to him (which animation to display).
And here it is important to understand that such an implementation is not exactly an authorized zone (the reason why I highlighted the word “real” in the preface). “Zone” means that only one specific functionality is available from the current object (or state) - either user or anonymous. In this implementation, the object itself decides what to address, and, in theory, can refer from one “zone” to the functionality of another “zone”. That's wrong.
And, again, this is logic, which should not be in our component.
Time for a change
Any functional, method or piece of code can be put into a class in order to be able to provide or replace its object in the future. We will render the animation in the UserAnimationFunction and AnonymousAnimationFunction, respectively. Well, as we already know how, we will inject.
Conversion 1
@Inject UserAnimationFunction userAnimationFunction; @Inject AnonymousAnimationFunction anonymousAnimationFunction;
From the ill-fated if, of course, we have not left yet.
if (userIsAuthourized) { userAnimationFunction.show(); } else { anonymousAnimationFunction.show(); }
Right thought:
The driver should simply report that he is arriving, and already the station workers will prepare for him a certain way.
(as well as)
The software component must inform about the need to show the animation, and the object responsible for the animation will decide which one.
This is nothing more than an interface. Define a single interface for our animations:
public interface AuthDependentAnimationFunction { void show(); }
Then we will only have to call the very workers of the station who will choose the path for us. Our component should come to mind:
Transform 2
@Inject AuthDependentAnimationFunction animationFunction; ... animationFunction.show();
Ask me "how?"
All magic becomes possible with the help of Subcomponents. As you remember, in the last article we used Subcomponents so that each screen has its own dependency graph. The lifetime of such a graph depended on the lifetime of the screen.
In our case, we will have 2 dependency graphs: one for the authorized zone and the other for the anonymous one. It is not difficult to guess that the lifetime will depend on the time of authorization.
Often, requests to the API include some user token, and therefore such a token can be taken as the
core of our authorized graph.
For an anonymous graph, you can omit the kernel (everything to your taste, you can omit the kernel for an authorized one).
Schematically, our system will look like this:
You probably noticed the AuthDependentComponent interface. Working with interfaces, in our software component we got rid of all cases of logic selection, except for one. Our choice of logic came down to the choice of the Component for injection:
if (userIsAuthourized) { App.getInstance().getUserComponent.inject(); } else { App.getInstance().getAnonymousComponent.inject(); }
And the AuthDependentComponent common interface for two Subcomponents allows to get rid of this code as well.
public interface AuthDependentComponent { void inject(SomeFragment fragment); }
Note. AuthDependentComponent is simply an interface without any “Component”, “Subcomponent” annotations, etc. We need it only as a common ancestor for the two Components. Also in it it is possible to describe inject-methods - Dagger implements them for each of the Components of the heirs.
@UserScope @Subcomponent(modules = UserModule.class) public interface UserComponent extends AuthDependentComponent { } @Module public class UserModule { private String userToken; public UserModule(String userToken) { this.userToken = userToken; } @UserScope @Provides AuthDependentAnimationFunction provideAnimationFunction() { return new UserAnimationFunction(); } } @AnonymousScope @Subcomponent(modules = AnonymousModule.class) public interface AnonymousComponent extends AuthDependentComponent { } @Module public class AnonymousModule { @AnonymousScope @Provides AuthDependentAnimationFunction provideAnimationFunction() { return new AnonymousAnimationFunction(); } } @Singletone @Component(modules = AppModule.class) public interface AppComponent { UserComponent userComponent(UserModule userModule); AnonymousComponent anonymousComponent(AnonymousModule anonymousModule); }
The only responsible, knowing the current state of authorization, becomes our App class, it is the station employee.
public class App extends Application { private AuthDependentComponent authDependentComponent; ... private void init() { ... onUserLoggedIn(); } public void onUserLoggedIn(String userToken) { authDependentComponent = appComponent.userComponent(new UserModule(userToken)); } public void onUserLoggedOut() { authDependentComponent = appComponent.anonymousComponent(new AnonimousModule()); } public AuthDependentComponent getAuthDependentComponent() { return authDependentComponent; } }
As a result, our screen will look like this:
@Inject AuthDependentAnimationFunction animationFunction; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); App.getInstance().getAuthDependentComponent().inject(this); } public void showAnimation() { animationFunction.show(); }
Regarding the transition between zones
In my practice, I have seen the complete re-creation of all components of the application when changing the state of authorization. In principle, this is normal, since often the differences in zones are significant. In the case of re-creation, the newly created screen will get its dependencies already from the corresponding zone, i.e. Our code does not need any modifications.
If your logic provides minor differences in logic, you can pay attention to the reinjection of dependencies. In fact, when changing the authorization state, simply force inject () for the program component:
App.getInstance().getAuthDependentComponent().inject(this);
So previously created dependencies from the old component will be overwritten by dependencies from the new one.
BONUS
It seems that I mentioned something about social networks?
The problem is that the developers of different social networks have implemented their API, who are in that much. Who has tried to support more than one social network at the same time, he knows. However, the essence of these API methods is about the same:
- Post publication;
- Publication of photos;
- Change of status;
- etc.
To make everything beautiful, take an example with an authorized zone and imagine that each social network is a separate zone. Thus, for each zone you will have your Component, provided depending on which of the social networks the user is authorized. And working with the API will occur again through the interface with the above functions, implemented for each social network differently. Just do it! :)
An example of the implementation of work with social networks on github
TOTAL TWO ARTICLES
How to find code sections where Dagger would be useful?
- First, these are branches of quite a large part of logic, be it business logic or UI / UX.
- Secondly, in most cases this is the creation of a new object, i.e. focus on the keyword new, or factory methods.
In general, I wish you all good luck in understanding Dagger and the Dependency Injection pattern.
Write your interesting cases and tasks in the comments, well, stay tuned!