An Annotation processor that allows binding two classes with each other, where the first class can listen to the updates of the second class, like in MVVM, when we need our View to subscribe on the View-Model, so when it's variables update, we want our views to be updated as well ... although this library is not limited to MVVM, and also it allows this behavior between any two Objects, but it will be explained on an MVVM example for Android
public class ViewModel extends android.arch.lifecycle.ViewModel {
@SubscriptionName("stringLiveData")
final MutableLiveData<String> stringLiveData = new MutableLiveData<>();
private final Subject<Integer> intSubject = BehaviorSubject.createDefault(0);
@SubscriptionName("intSubject")
Subject<Integer> getIntSubject(){
return intSubject;
}
@Override
public void onCleared(){
intSubject.complete();
}
}
We need to put @SubscriptionName above the source that we need to receive it's updates, weather on the non-private variable, or on the non-private getter, as shown above ... the value passed to the annotation should be unique per class, as shown in the example, the stringLiveData variable is annotated with @SubscriptionName("stringLiveData"), and the intSubject getter is annotated with @SubscriptionName("intSubject"), the values "stringSubject" and "intSubject" are unique, if one is repeated, an error will occur during compilation
@SubscriptionsFactory(ViewModel.class)
public class MainActivity extends AppCompatActivity {
private Binder<ViewModel> binder;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
ViewModel viewModel = ViewModelProviders.of(this).get(ViewModel.class);
binder = Binder.bind(this).to(viewModel);
}
@SubscribeTo("stringLiveData")
void stringLiveDataSubscriber(MutableLiveData<String> liveData) {
liveData.observe(this, text -> Log.e("MainActivity", "liveData : " + text));
}
@SubscribeTo("intSubject")
Disposable intSubscriber(Subject<Integer> subject) {
return subject.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(v -> Log.e("MainActivity", "intSubject : " + v));
}
@Override
protected void onDestroy() {
super.onDestroy();
binder.unbind();
}
}
The first step is to tell the Annotation Processor where it can find the Subscriptions sources (our View-Model), through the annotation @SubscriptionsFactory
Then we declare our methods that will be invoked in the subscription process, like the following method :
@SubscribeTo("stringLiveData")
void stringLiveDataSubscriber(MutableLiveData<String> liveData) {
liveData.observe(this, text -> Log.e("MainActivity", "liveData : " + text));
}
@SubscribeTo("intSubject")
Disposable intSubscriber(Subject<Integer> subject) {
return subject.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(v -> Log.e("MainActivity", "intSubject : " + v));
}
in our annotation @SubscribeTo, we pass the key that we declared in our View-Model's @SubscriptionName annotation, in this example we subscribe to the Subject intSubject that was declared in our View-Model
after the annotation step, our method should be :
- not private
- it can return an RxJava 2 Disposable to be able to dispose it in Binder.unbind()
- it can return any other type (or void), but in this case it's return value will be ignored and will not be affected by the call to Binder.unbind() (which is exactly what we want with LiveData in Android)
- it should expect a parameter of the same type of the declared variable in the View-Model
at the end we do the subscription process through calling the below lines :
binder = Binder.bind(this).to(viewModel);
the above code will do the binding process and return a Binder which will hold all the Disposables created by our methods, and we then can clear it in our onDestroy() by calling :
binder.unbind();
we can access the View-Model (our Subscriptions Factory) through this getter method :
binder.getSubscriptionsFactory();
Another way to initialize the binding process is to invoke the below lines :
binder = Binder.bind(this).toNewSubscriptionsFactory();
this way, the Binder will create a new instance of the Class mentioned in the @SubscriptionsFactory, but this class should have a default no-args constructor
Although the example is on an MVVM pattern for Android, this can be applied to any two classes, so in our example, we can then Bind our View-Model to our Inter-actor or Repositories, and so on
Step 1. Add the JitPack repository to your build file
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step 2. Add the dependency
dependencies {
compile 'com.github.Ahmed-Adel-Ismail.Binder:binding:1.1.0'
annotationProcessor 'com.github.Ahmed-Adel-Ismail.Binder:processor:1.1.0'
}
starting from version 0.1.0, there is Support for Android as follows :
dependencies {
compile 'com.github.Ahmed-Adel-Ismail.Binder:android:1.1.0'
annotationProcessor 'com.github.Ahmed-Adel-Ismail.Binder:processor:1.1.0'
}
@Override
public void onCreate() {
super.onCreate();
Binding.integrate(this);
}
You do not need to handle the Binding operations any more, just declare the annotations in your Activity or android.support.v4.app.Fragment as follows :
@SubscriptionsFactory(MainViewModel.class)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@SubscribeTo("stringSubject")
Disposable stringSubscriber(Subject<String> subject) {
return subject.share()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(v -> Log.e("MainActivity", "stringSubject : " + v));
}
}
@SubscriptionsFactory(MainViewModel.class)
public class MainFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
return inflater.inflate(R.layout.fragment_main,container);
}
@SubscribeTo("stringSubject")
Disposable stringSubscriber(Subject<String> subject) {
return subject.share()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(v -> Log.e("MainFragment", "stringSubject : " + v));
}
}
And declare the annotations in your ViewModel as follows :
public class MainViewModel extends android.arch.lifecycle.ViewModel {
@SubscriptionName("stringSubject")
final Subject<String> stringSubject = PublishSubject.create();
@Override
public void onCleared(){
stringSubject.onComplete();
}
}
Since the MainViewModel extends the new Architecture components ViewModel, it will be shared accross all the Fragments and there Activity, if it does not extend the android.arch.lifecycle.ViewModel, a new instance will be created for each, which means that an instance will be created for The MainActivity, and another will be created to MainFragment
If you want the MainViewModel to not extend the android.arch.lifecycle.ViewModel, and be shared between the Activity and it's Fragments, you can annotate the class with @SharedSubscriptionFactory, as follows :
@SharedSubscriptionFactory
public class MainViewModel {
@SubscriptionName("stringSubject")
final Subject<String> stringSubject = PublishSubject.create();
void clear(){
stringSubject.onComplete();
}
}
if you have a method that will clear / destroy your ViewModel, like the clear() method in the MainViewModel, you can annotate it with @OnSubscriptionsClosed, this will cause the Binder to call it when the Activity / Fragment is totally destroyed (not rotating), and if this ViewModel is shared between Activity and it's Fragments, this method will be invoked when the Activity is totally destroyed (not rotating), so our ViewModel will look like this :
public class MainViewModel {
@SubscriptionName("stringSubject")
final Subject<String> stringSubject = PublishSubject.create();
@OnSubscriptionsClosed
void clear(){
stringSubject.onComplete();
}
}
Notice that @OnSubscriptionsClosed will cause the clear() method to be invoked on any type of ViewModel, but for classes that extend android.arch.lifecycle.ViewModel, it is better to override the onCleared() method instead of using @OnSubscriptionsClosed
For Pro Guard, you may need to add those lines in the proguard-rules file :
# Keep default constructors inside classes
-keepclassmembers class * {
public protected <init>(...);
<init>(...);
}
# Keep generated classes names
-keep class **$$Subscribers { *; }
# keep classes with annotated members
-keepclasseswithmembers class * {
@com.binding.annotations.* <methods>;
@com.android.binding.* <methods>;
}
- Starting from version 1.0.0, package names were changed, so if you were using older version, just remove the import statements, and import the same classes from the new packages