Life Cycle is a light and powerful library to handle events and dependencies in your app.
Events in life cycle called signals. Let's see an example first.
public interface LoginSignal{
void onLogin(UserData userData);
}
public class LoginScreen{
public void onCreate(){
Signal<LoginSignal> loginSignal = SignalsBag.inject(LoginSignal.class);
loginSignal.addListener(userData -> {
setUserName(userData.name);
setUserImage(userData.imageUrl);
});
}
}
public class LoginModel{
private Signal<LoginSignal> loginSignal = SignalsBag.inject(LoginSignal.class);
public void setUserData(UserData userData){
loginSignal.dispatcher.onLogin(userData);
}
}
As you can see there is a full separation between the dispatcher, LoginModel
and the listener LoginScreen
.
Here are a few more ways to use a Signal
class LoginScreen{
public void init(){
Signal<LoginSignal> loginSignal = SignalsBag.inject(LoginSignal.class);
loginSignal.addListenerOnce(userData -> {}); // The listener will be automatically removed after the first dispatch
loginSignal.addListener(LoginScreen.class); // LoginScreen will be injected(see below what injection is) each time
// to handle the this event
loginSignal.addListenerOnce(LoginScreen.class); // he listener will be automatically removed after the first dispatch
// Unregister event
loginSignal.removeListener(LoginScreen.class);
loginSignal.removeListener(this);
}
}
Life cycle singletons allow you to create, remove and overwrite singletons easily
class LoginModel implements Singleton{
}
class LoginScreen{
LoginModele loginModule = Factory.inject(LoginModele.class);
}
Factory.inject
method will return an instance of the injected class. It is promised that if the class implements the singleton interface it will be created only once. Otherwise, a new instance of it will be created each injection.
It's also possible to pass constructors` params during the injection, however, it's highly unrecommended as it breaks hard-typing.
class LoginModel implements Singleton{
LoginModel(String userName){
}
}
class LoginScreen{
LoginModele loginModule = Factory.injectWithParams(LoginModele.class, "userName");
}
Registrars are Life Cycle dynamic injections mechanism. It allows you to manage dependencies as well as pre-register for signals.
The main Register class is called Bootstrap
, the registration process is starting when 'Bootstrap.initialize()' is called.
class MyApp extends Bootstrap {
@Override
protected void initClasses() {
registerClass(MyUserModel.class);
}
@Override
protected void initSignals(SignalsHelper signalsHelper) {
signalsHelper.addListener(LoginSignal.class, LoginScreen.class);
}
@Override
protected void initRegistrars() {
addRegistrar(new GameRegistrar());
}
}
registerClass
Allows you to override any injected class, even if it's a singleton. In the example above when a class that super MyUserModel, in other words, if MyUserModel is an instance of some class, then whenever that superclass is injected MyUserModel instance will be returned. If MyUserModel extends UserModel, then when UserModel will be injected, MyUserModel instance will be returned. This rule applies to all the injection levels.
initSignals
Allows you to register signals during the bootstrap phase. It's useful if you are building a library and want to define some signals ahead of time.
```initRegistrars` - 'Bootstrap' extends a Registrar by adding it the 'Bootstrap.initialize()' method. However, you might use this place to add other registers in your app or some other libraries, and since Bootstrap is a Registrar if a library has a Bootstrap it can be added as a register here.
In case you need more control over the injection you can use a builder, it will be invoked upon every injection of the provided class. Note that the actual injected class is calculated before the builder logic kicks in, so if A
extends B
extends C
and the builder was mapped to B
, then it will be invoked upon B
injections. If registerClass(B.class)
was called then it will be invoked upon C
injections as well. However if registerClass(A.class)
was called then it will no longer be invoked not for A injections and not for B injections.
class MyApp extends Bootstrap {
@Override
protected void initClasses() {
this.registerClass(MyUserModel.class);
addBuilder(MyUserModel.class, (classToInject, params) -> {
MyUserModel myUserModel = new MyUserModel();
return myUserModel;
});
}
}
Sometimes times you need using the Multiton pattern. For example, you are making an eCommerce app, and you got two products pages that share much functionality but needs to have their model and may even some minor changes. To address this need, a family was created.
class LoginScreen{
LoginModele loginModule = Factory.inject(LoginModele.class, 'bathFamily');
}
If LoginModel is a singleton, then it's promised to have only one instance if it for each family. Also, note that the injection works recursively. If the login model will make any injections during in its constructor and will not provide any family, it will default to the last used family, 'bathFamily'
in our case. It only works during the constructor time.
Consider the below example.
class LoginScreen{
LoginModele loginModule = Factory.inject(LoginModele.class);
}
class LoginModel{
LoginScreen loginScreen = Factory.inject(LoginScreen.class);
}
During the construction of LoginScreen
we are constructing a LoginModel
but before the construction of aether one of those is ended we are starting to construct LoginScreen
again from the LoginModel
. The result of this will be a StackOverflowException. So instead of telling you don't do it, try to keep a separation of control in your app and fallow the Single responsibility principle. I actually would like to show you a LifeCycle solution to this, it's called Injector
and used like this:
class LoginScreen{
LoginModele loginModule = Factory.inject(LoginModele.class);
}
class LoginModel implements Injector{
LoginScreen loginScreen;
@Override
public void injectionHandler(String family) {
loginScreen = Factory.inject(LoginScreen.class);
}
}
The Injector
will keep the family recursion policy that the constructors have and it will prevent the StackOverflowException by allowing LoginScreen to be constructed before it been injected. Also if you were looking for a way to get the current family information to perhaps associate it with file names or other persistent settings, using Injector
is the way to get it.