[RFC] Improve support for auto-unsubscribing observables and newer language/API levels #12

Closed
mttkay opened this Issue Sep 10, 2014 · 66 comments

Comments

Projects
None yet
@mttkay
Collaborator

mttkay commented Sep 10, 2014

Currently, using Observables in Android components that have access to Context (or are themselves) requires one to carefully think about detaching from these sequences as part of the component life-cycle, since otherwise a closure the sequence holds a (strong) reference to might leak the current context until the time the sequence finishes and releases all subscribers.

A good example are configuration changes, where Android first destroys, then recreates an Activity, but observables might still be holding on to a subscriber created within the scope of that Activity. Even with retained fragments that detach from their host activity, it's easy enough to leak the attached context indirectly by holding on to a view first created through that context (every view in Android has a strong reference to the Activity context it was first created in.)

This ticket aims to find solutions to improve this situation, potentially by leveraging newer Android API levels where available, and/or add first class support for newer Java language levels (Java 8/Retrolambda)

Some suggestions that have been made already follow.

Android 14+ APIs

One option would be to make use of Activity life cycle callbacks:
http://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks.html

This might allows us to unsubscribe once Android is about to destroy an activity.

Java 8 lambdas and Retrolambda

Part of the problem is that every "lambda" in Java 7 or below is an anonymous inner class that will hold a strong reference to the outer class, even if it's a pure function that does not access state outside the lambda. Java 8 + Retrolambda could help here, since pure functions will be materialized as static methods that hold no reference back to their owner.

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html
https://github.com/orfjackal/retrolambda

Weak subscribers

We've taken several stabs at this and always dismissed the solution. The idea was to use WeakReferences to bind subscribers and lambdas. The problem with his approach is that it doesn't work with lambdas, since the only incoming reference would be weak, and they are eligible for GC the instant they're created. There might still be other ways to achieve this, however, I'll leave it open for discussion.

@Yarikx

This comment has been minimized.

Show comment
Hide comment
@Yarikx

Yarikx Sep 10, 2014

Contributor

I thinks there is no silver bullet here.
Weak subscribers is just leaky abstraction, that just don't solve the problem, but creates another one.

For me the only rule is: always unsubscribe from observable when you don't need it anymore (onDestroy, onViewDestroy, etc.).
The other question is how to do it right.

In my case I just usually create set of CompositeSubsctiptions on activity/fragment events (onCreate, onResume) and unsubscribe from them in corresponding callbacks (onDestroy, onPause).
I usually do it in 'base' activity/fragment, so all my fragments/activities should be inherited from it.
So this requires to have 'base' class, but it allows to use it in devices with api version < 14.

about Activity life cycle callbacks:
It's actually good improvement, and we can provide some helper functions as part of RxAndroid to auto-unsubscribe, but of course it will be useful only for api >= 14, and can be simply ported for lower versions.

Contributor

Yarikx commented Sep 10, 2014

I thinks there is no silver bullet here.
Weak subscribers is just leaky abstraction, that just don't solve the problem, but creates another one.

For me the only rule is: always unsubscribe from observable when you don't need it anymore (onDestroy, onViewDestroy, etc.).
The other question is how to do it right.

In my case I just usually create set of CompositeSubsctiptions on activity/fragment events (onCreate, onResume) and unsubscribe from them in corresponding callbacks (onDestroy, onPause).
I usually do it in 'base' activity/fragment, so all my fragments/activities should be inherited from it.
So this requires to have 'base' class, but it allows to use it in devices with api version < 14.

about Activity life cycle callbacks:
It's actually good improvement, and we can provide some helper functions as part of RxAndroid to auto-unsubscribe, but of course it will be useful only for api >= 14, and can be simply ported for lower versions.

@nsk-mironov

This comment has been minimized.

Show comment
Hide comment
@nsk-mironov

nsk-mironov Sep 10, 2014

Contributor

In my case I just usually create set of CompositeSubsctiptions on activity/fragment events (onCreate, onResume) and unsubscribe from them in corresponding callbacks (onDestroy, onPause).

I usually do the same. The only thing that worries me using this approach is absence of guarantees that Subscription.unsubscribe will do its job synchronously (see ReactiveX/RxJava#1590 for more details). Thats why I always use my custom safeSubscribe method instead of Observable.subscribe:

public static <T> Subscription safeSubscribe(final Observable<T> observable, final Observer<T> observer) {
    final SafeObserver<T> delegate = new SafeObserver<T>(observer);
    final Subscription subscription = observable.subscribe(delegate);

    return new SafeSubscription(subscription, delegate);
}

private static class SafeSubscription implements Subscription {
    private final Subscription subscription;
    private final SafeObserver observer;

    private SafeSubscription(final Subscription subscription, final SafeObserver observer) {
        this.subscription = subscription;
        this.observer = observer;
    }

    @Override
    public void unsubscribe() {
        subscription.unsubscribe();
        observer.unsubscribe();
    }

    @Override
    public boolean isUnsubscribed() {
        return subscription.isUnsubscribed();
    }
}

private static class SafeObserver<T> implements rx.Observer<T> {
    private static final Observer<?> EMPTY = new EmptyObserver();
    private volatile Observer<T> observer;

    private SafeObserver(final Observer<T> observer) {
        this.observer = observer;
    }

    public void unsubscribe() {
       observer = (Observer<T>) EMPTY;
    }

    @Override
    public void onCompleted() {
        observer.onCompleted();
    }

    @Override
    public void onError(Throwable e) {
        observer.onError(e);
    }

    @Override
    public void onNext(T t) {
        observer.onNext(t);
    }
}
Contributor

nsk-mironov commented Sep 10, 2014

In my case I just usually create set of CompositeSubsctiptions on activity/fragment events (onCreate, onResume) and unsubscribe from them in corresponding callbacks (onDestroy, onPause).

I usually do the same. The only thing that worries me using this approach is absence of guarantees that Subscription.unsubscribe will do its job synchronously (see ReactiveX/RxJava#1590 for more details). Thats why I always use my custom safeSubscribe method instead of Observable.subscribe:

public static <T> Subscription safeSubscribe(final Observable<T> observable, final Observer<T> observer) {
    final SafeObserver<T> delegate = new SafeObserver<T>(observer);
    final Subscription subscription = observable.subscribe(delegate);

    return new SafeSubscription(subscription, delegate);
}

private static class SafeSubscription implements Subscription {
    private final Subscription subscription;
    private final SafeObserver observer;

    private SafeSubscription(final Subscription subscription, final SafeObserver observer) {
        this.subscription = subscription;
        this.observer = observer;
    }

    @Override
    public void unsubscribe() {
        subscription.unsubscribe();
        observer.unsubscribe();
    }

    @Override
    public boolean isUnsubscribed() {
        return subscription.isUnsubscribed();
    }
}

private static class SafeObserver<T> implements rx.Observer<T> {
    private static final Observer<?> EMPTY = new EmptyObserver();
    private volatile Observer<T> observer;

    private SafeObserver(final Observer<T> observer) {
        this.observer = observer;
    }

    public void unsubscribe() {
       observer = (Observer<T>) EMPTY;
    }

    @Override
    public void onCompleted() {
        observer.onCompleted();
    }

    @Override
    public void onError(Throwable e) {
        observer.onError(e);
    }

    @Override
    public void onNext(T t) {
        observer.onNext(t);
    }
}
@mttkay

This comment has been minimized.

Show comment
Hide comment
@mttkay

mttkay Sep 12, 2014

Collaborator

@mironov-nsk that's an interesting idea. Perhaps this can be combined with bindFragment? Fragment binding usually has to happen as the last step (i.e. as the outermost operator) since otherwise usage of cache or replay are not safe, as it would mean that dropped notifications are "replayed". (i.e. missed again after resubscribing to a hot observable)

Since it's an Operator, it wraps a subscriber. If it were to use a delegate like the one above that ensures whatever is subscribed to bindFragment has its subscription disposed synchronously, then we could at least guarantee that #3 does not happen when bindFragment is used (binding activities and view could be dealt with in the same way?)

Collaborator

mttkay commented Sep 12, 2014

@mironov-nsk that's an interesting idea. Perhaps this can be combined with bindFragment? Fragment binding usually has to happen as the last step (i.e. as the outermost operator) since otherwise usage of cache or replay are not safe, as it would mean that dropped notifications are "replayed". (i.e. missed again after resubscribing to a hot observable)

Since it's an Operator, it wraps a subscriber. If it were to use a delegate like the one above that ensures whatever is subscribed to bindFragment has its subscription disposed synchronously, then we could at least guarantee that #3 does not happen when bindFragment is used (binding activities and view could be dealt with in the same way?)

@austynmahoney

This comment has been minimized.

Show comment
Hide comment
@austynmahoney

austynmahoney Oct 1, 2014

Contributor

I do something similar to @Yarikx

Adding something similar to this class and building around it may be something useful to have for new library users.

public abstract class RxFragment extends Fragment {

    private CompositeSubscription mSubscriptions;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mSubscriptions = new CompositeSubscription();
        subscribe();
    }

    @Override
    public void onDetach() {
        super.onDetach();
        unsubscribe();
    }

    private void subscribe() {
        for (Subscription subscription : createSubscriptions()) {
            mSubscriptions.add(subscription);
        }
    }

    private void unsubscribe() {
        if (mSubscriptions == null) { return; }
        mSubscriptions.clear();
        mSubscriptions = new CompositeSubscription();
    }

    protected void addSubscription(Subscription subscription) {
        mSubscriptions.add(subscription);
    }

    /**
     * Implement this to return all subscriptions that you want attached to this
     * fragments lifecycle. Each {@link rx.Subscription} will have
     * {@link rx.Subscription#unsubscribe() unsubscribe()} called when
     * {@link android.support.v4.app.Fragment#onDetach() onDetach()} is fired.
     * <p>The default implementation returns an empty array.</p>
     */
    protected List<Subscription> createSubscriptions() {
        return new ArrayList<>(0);
    }
}

The child class then implements createSubscriptions() and returns a list of any subscriptions the fragment should unsubscribe() from whenever it is removed from view (onDetach()).

Any subscription that is added after the fragment is created can be added using addSubscription(). It will then also be tied to the activity lifecycle.

AFAIK this resolves the Observer memory leak issue. Subscription#unsubscribe de-references the Observer in the Observable. If it doesn't then we have other issues.

Contributor

austynmahoney commented Oct 1, 2014

I do something similar to @Yarikx

Adding something similar to this class and building around it may be something useful to have for new library users.

public abstract class RxFragment extends Fragment {

    private CompositeSubscription mSubscriptions;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mSubscriptions = new CompositeSubscription();
        subscribe();
    }

    @Override
    public void onDetach() {
        super.onDetach();
        unsubscribe();
    }

    private void subscribe() {
        for (Subscription subscription : createSubscriptions()) {
            mSubscriptions.add(subscription);
        }
    }

    private void unsubscribe() {
        if (mSubscriptions == null) { return; }
        mSubscriptions.clear();
        mSubscriptions = new CompositeSubscription();
    }

    protected void addSubscription(Subscription subscription) {
        mSubscriptions.add(subscription);
    }

    /**
     * Implement this to return all subscriptions that you want attached to this
     * fragments lifecycle. Each {@link rx.Subscription} will have
     * {@link rx.Subscription#unsubscribe() unsubscribe()} called when
     * {@link android.support.v4.app.Fragment#onDetach() onDetach()} is fired.
     * <p>The default implementation returns an empty array.</p>
     */
    protected List<Subscription> createSubscriptions() {
        return new ArrayList<>(0);
    }
}

The child class then implements createSubscriptions() and returns a list of any subscriptions the fragment should unsubscribe() from whenever it is removed from view (onDetach()).

Any subscription that is added after the fragment is created can be added using addSubscription(). It will then also be tied to the activity lifecycle.

AFAIK this resolves the Observer memory leak issue. Subscription#unsubscribe de-references the Observer in the Observable. If it doesn't then we have other issues.

@austynmahoney

This comment has been minimized.

Show comment
Hide comment
@austynmahoney

austynmahoney Oct 1, 2014

Contributor

On a different note, I would not mind if support was only API 14+. Devices running a version below that only account for 12.1% and dropping.

Contributor

austynmahoney commented Oct 1, 2014

On a different note, I would not mind if support was only API 14+. Devices running a version below that only account for 12.1% and dropping.

@Yarikx

This comment has been minimized.

Show comment
Hide comment
@Yarikx

Yarikx Oct 1, 2014

Contributor

@austynmahoney I don't think we should add this (or similar) to library.
As this class is base class, we will enforce user to particular fragment hierarchy.
As users can have different base fragments (native Fragment, support Fragment, MySuperLibraryFragment), we can support all of them.
All above applies to Activity hierarchy too.

I think it's better to write documentation in wiki about this 'common' pattern, and left implementation up to the user.

Contributor

Yarikx commented Oct 1, 2014

@austynmahoney I don't think we should add this (or similar) to library.
As this class is base class, we will enforce user to particular fragment hierarchy.
As users can have different base fragments (native Fragment, support Fragment, MySuperLibraryFragment), we can support all of them.
All above applies to Activity hierarchy too.

I think it's better to write documentation in wiki about this 'common' pattern, and left implementation up to the user.

@austynmahoney

This comment has been minimized.

Show comment
Hide comment
@austynmahoney

austynmahoney Oct 1, 2014

Contributor

Very true, without multiple inheritance we don't want to enforce anything. In a sample would be nice.

Contributor

austynmahoney commented Oct 1, 2014

Very true, without multiple inheritance we don't want to enforce anything. In a sample would be nice.

@dpsm

This comment has been minimized.

Show comment
Hide comment
@dpsm

dpsm Oct 2, 2014

Contributor

@austynmahoney part of the problem is in #3 which the excerpt above does not cover. I like the sample idea where we demonstrate the "patterns" and best practices.

Contributor

dpsm commented Oct 2, 2014

@austynmahoney part of the problem is in #3 which the excerpt above does not cover. I like the sample idea where we demonstrate the "patterns" and best practices.

@DylanSale

This comment has been minimized.

Show comment
Hide comment
@DylanSale

DylanSale Oct 2, 2014

I have recently been doing the below to handle this.

public abstract class RxFragment extends Fragment {

    PublishSubject<Void> detached = PublishSubject.create();

    @Override
    public void onDetach() {
        super.onDetach();
        detached.onNext(null);
    }

    public <T> Observable<T> bindObservable(Observable<T> in) {
        return AndroidObservable.bindFragment(this, in).takeUntil(detached);
    }
}

The Take Until will cause onComplete to be called when onDetach is called, which will call unsubscribe (via SafeSubscriber). This means you just need to remember to wrap your observable in the bind method instead of adding it to a composite subscription.

I have recently been doing the below to handle this.

public abstract class RxFragment extends Fragment {

    PublishSubject<Void> detached = PublishSubject.create();

    @Override
    public void onDetach() {
        super.onDetach();
        detached.onNext(null);
    }

    public <T> Observable<T> bindObservable(Observable<T> in) {
        return AndroidObservable.bindFragment(this, in).takeUntil(detached);
    }
}

The Take Until will cause onComplete to be called when onDetach is called, which will call unsubscribe (via SafeSubscriber). This means you just need to remember to wrap your observable in the bind method instead of adding it to a composite subscription.

@mttkay

This comment has been minimized.

Show comment
Hide comment
@mttkay

mttkay Oct 5, 2014

Collaborator

@DylanSale that's actually the neatest solution I've seen so far. I also like to idea of documenting these patterns somewhere. I was wondering, maybe code samples would work even better than a wiki? I'm personally not a huge fan of documentation that's too detached from code.

There is already a samples module I started a while back, but it hasn't been integrated back into RxAndroid yet. The build setup is still giving me a headache, I might actually resort (again) to have subfolders inside the project that have their own Gradle wrappers, as the incompatibilities between the Android Gradle plugin and other plugins are getting in the way.

Next week is really packed for me (I'll be in Stockholm from Wednesday throughout Sunday night), but I hope I can back to this the following week.

Collaborator

mttkay commented Oct 5, 2014

@DylanSale that's actually the neatest solution I've seen so far. I also like to idea of documenting these patterns somewhere. I was wondering, maybe code samples would work even better than a wiki? I'm personally not a huge fan of documentation that's too detached from code.

There is already a samples module I started a while back, but it hasn't been integrated back into RxAndroid yet. The build setup is still giving me a headache, I might actually resort (again) to have subfolders inside the project that have their own Gradle wrappers, as the incompatibilities between the Android Gradle plugin and other plugins are getting in the way.

Next week is really packed for me (I'll be in Stockholm from Wednesday throughout Sunday night), but I hope I can back to this the following week.

@hannesstruss

This comment has been minimized.

Show comment
Hide comment
@hannesstruss

hannesstruss Oct 5, 2014

@mttkay 👍 for building a cookbook! Not sure about pure source code samples. I think something like the OkHttp Recipes Page would be a lot more accessible.

@mttkay 👍 for building a cookbook! Not sure about pure source code samples. I think something like the OkHttp Recipes Page would be a lot more accessible.

@roman-mazur

This comment has been minimized.

Show comment
Hide comment
@roman-mazur

roman-mazur Oct 11, 2014

I usually avoid connecting Activity lifecycle with observables. We leverage loaders to get rid of activity lifecycle and the get an Observable that is built on top of loader callbacks (and thus is safe to subscibe with any internal objects holding references to activities/views/fragments).
https://github.com/stanfy/enroscar/tree/master/async
https://github.com/stanfy/enroscar/wiki/Enroscar-Async-Recipes#consuming-via-rxobservable
The basic idea is that we describe some async operations providing either an Observable or a Callable, then the library puts them behind a loader and provides us with means to subscribe to corresponding loader callbacks (also via an Observable).

I usually avoid connecting Activity lifecycle with observables. We leverage loaders to get rid of activity lifecycle and the get an Observable that is built on top of loader callbacks (and thus is safe to subscibe with any internal objects holding references to activities/views/fragments).
https://github.com/stanfy/enroscar/tree/master/async
https://github.com/stanfy/enroscar/wiki/Enroscar-Async-Recipes#consuming-via-rxobservable
The basic idea is that we describe some async operations providing either an Observable or a Callable, then the library puts them behind a loader and provides us with means to subscribe to corresponding loader callbacks (also via an Observable).

@roman-mazur

This comment has been minimized.

Show comment
Hide comment
@roman-mazur

roman-mazur Oct 11, 2014

@DylanSale note that onDetach in fragments is not called every time you invoke detach on a FragmentTransaction. detach puts your fragment to a state close to when it's put to the backstack. But it's still attached to the activity. And onDetach is invoked when fragment is destroyed and getActivity() returns null.

@DylanSale note that onDetach in fragments is not called every time you invoke detach on a FragmentTransaction. detach puts your fragment to a state close to when it's put to the backstack. But it's still attached to the activity. And onDetach is invoked when fragment is destroyed and getActivity() returns null.

@tbruyelle

This comment has been minimized.

Show comment
Hide comment
@tbruyelle

tbruyelle Oct 15, 2014

Very interesting thread ! I'm also scratching my head to find a fine way to handle Android objects' lifecycle in Rx.

I had the idea to deal directly with the Subscription object, in order to add to a CompositeSubscription in the activity. Then the activity invokes CompositeSubscription.unsubscribe() to unsub all subscripters when it's destroyed.

But I didn't find an operator which manipulates the Subscription object. Ideally I would like to have this kind of method in my activity :

public Observable bindObservable(Observable obs) {
   return obs.onSubscribe( new OnSubscribe() { // This operator does not exists !
      public void onSubscribe(Subscription sub) {
         MyActivity.this.mCompositeSubscription.add( sub );
      });
}

Does this kind of operator exist ?

Very interesting thread ! I'm also scratching my head to find a fine way to handle Android objects' lifecycle in Rx.

I had the idea to deal directly with the Subscription object, in order to add to a CompositeSubscription in the activity. Then the activity invokes CompositeSubscription.unsubscribe() to unsub all subscripters when it's destroyed.

But I didn't find an operator which manipulates the Subscription object. Ideally I would like to have this kind of method in my activity :

public Observable bindObservable(Observable obs) {
   return obs.onSubscribe( new OnSubscribe() { // This operator does not exists !
      public void onSubscribe(Subscription sub) {
         MyActivity.this.mCompositeSubscription.add( sub );
      });
}

Does this kind of operator exist ?

@nsk-mironov

This comment has been minimized.

Show comment
Hide comment
@nsk-mironov

nsk-mironov Oct 15, 2014

Contributor

@tbruyelle, I'm not sure if such operator exists, but it should be quite easy to implement. The following code should work:

public <T> Observable<T> bind(final Observable<T> observable) {
    return observable.lift(subscriber -> {
        MyActivity.this.mCompositeSubscription.add(subscriber);
        return subscriber;
    });
}
Contributor

nsk-mironov commented Oct 15, 2014

@tbruyelle, I'm not sure if such operator exists, but it should be quite easy to implement. The following code should work:

public <T> Observable<T> bind(final Observable<T> observable) {
    return observable.lift(subscriber -> {
        MyActivity.this.mCompositeSubscription.add(subscriber);
        return subscriber;
    });
}
@DylanSale

This comment has been minimized.

Show comment
Hide comment
@DylanSale

DylanSale Oct 15, 2014

@tbruyelle see my code snippit above.
#12 (comment)

It (or something like it using another lifecycle callback like onDestroy or onStop) avoids you having to deal with subscription objects at all.

@tbruyelle see my code snippit above.
#12 (comment)

It (or something like it using another lifecycle callback like onDestroy or onStop) avoids you having to deal with subscription objects at all.

@DylanSale

This comment has been minimized.

Show comment
Hide comment
@DylanSale

DylanSale Oct 15, 2014

@roman-mazur I don't actually use onDetach, I just used that in my example in comparison to the previous ones.

In reality I have a subject for each of the lifecycle callbacks in my fragment and activity base classes and bindObservable takes an enum selecting which one to take until.

@roman-mazur I don't actually use onDetach, I just used that in my example in comparison to the previous ones.

In reality I have a subject for each of the lifecycle callbacks in my fragment and activity base classes and bindObservable takes an enum selecting which one to take until.

@tbruyelle

This comment has been minimized.

Show comment
Hide comment
@tbruyelle

tbruyelle Oct 15, 2014

@mironov-nsk Thanks for the tip, I will give a try !

@DylanSale Yes I saw your code but fix me if I'm wrong it requires to use the SafeObserver to prevent the context leak. First I wanted to prevent usage of wrappers. What's wrong with the CompositeSubscription ?

@mironov-nsk Thanks for the tip, I will give a try !

@DylanSale Yes I saw your code but fix me if I'm wrong it requires to use the SafeObserver to prevent the context leak. First I wanted to prevent usage of wrappers. What's wrong with the CompositeSubscription ?

@DylanSale

This comment has been minimized.

Show comment
Hide comment
@DylanSale

DylanSale Oct 15, 2014

@tbruyelle my solution requires SafeSubscriber which Observable.subscribe wraps all Subscribers in. It will work correctly as long as you use subscribe and not unsafeSubscribe. If you do use unsafeSubscribe then you can just call unsubscribe in your Subscriber's onComplete method.

There isn't anything wrong with using CompositeSubscription, it is just a pain to always have to handle the Subscriptions.

RxJava may not the best solution if you want to avoid wrappers. It uses them a lot.

@tbruyelle my solution requires SafeSubscriber which Observable.subscribe wraps all Subscribers in. It will work correctly as long as you use subscribe and not unsafeSubscribe. If you do use unsafeSubscribe then you can just call unsubscribe in your Subscriber's onComplete method.

There isn't anything wrong with using CompositeSubscription, it is just a pain to always have to handle the Subscriptions.

RxJava may not the best solution if you want to avoid wrappers. It uses them a lot.

@tbruyelle

This comment has been minimized.

Show comment
Hide comment
@tbruyelle

tbruyelle Oct 15, 2014

@DylanSale By wrappers I mean custom wrapper. I'm quite new in Rx development and I prefer to work directly with library objects for now, wrappers may add complexity to my learning.
Thus I think I'm not enough familiar with Rx to understand well your solution (Subject is still something fuzzy for me).

@DylanSale By wrappers I mean custom wrapper. I'm quite new in Rx development and I prefer to work directly with library objects for now, wrappers may add complexity to my learning.
Thus I think I'm not enough familiar with Rx to understand well your solution (Subject is still something fuzzy for me).

@DylanSale

This comment has been minimized.

Show comment
Hide comment
@DylanSale

DylanSale Oct 15, 2014

Subjects are just observables that are also Subscribers. They are used (sparingly) to connect imperative callback code (like the android life cycle callbacks) to Observables.

My solution does not need any custom wrappers, SafeSubscriber is used internally (and automatically) by RxJava to make sure the Observable follows the Rx contract (no calls to oNext after onComplete etc).

Using a custom Operator that has a side effect is not great RX style (as a rule things inside the Observable chain should not have side effects, this helps keep things thread safe).

Subjects are just observables that are also Subscribers. They are used (sparingly) to connect imperative callback code (like the android life cycle callbacks) to Observables.

My solution does not need any custom wrappers, SafeSubscriber is used internally (and automatically) by RxJava to make sure the Observable follows the Rx contract (no calls to oNext after onComplete etc).

Using a custom Operator that has a side effect is not great RX style (as a rule things inside the Observable chain should not have side effects, this helps keep things thread safe).

@tbruyelle

This comment has been minimized.

Show comment
Hide comment
@tbruyelle

tbruyelle Oct 15, 2014

@DylanSale Oh ok, I didn't notice that SafeSubscriber was part of the library ! I thought it was a class I was having to add in my project...
Thanks for the Subject explanation, now I understand well your code. Sounds very fine, I just wonder if it's still necessary to use AndroidObservable.bind[Activity|Fragment]() ? Does the takeUntil() isn't enough?

@DylanSale Oh ok, I didn't notice that SafeSubscriber was part of the library ! I thought it was a class I was having to add in my project...
Thanks for the Subject explanation, now I understand well your code. Sounds very fine, I just wonder if it's still necessary to use AndroidObservable.bind[Activity|Fragment]() ? Does the takeUntil() isn't enough?

@DylanSale

This comment has been minimized.

Show comment
Hide comment
@DylanSale

DylanSale Oct 15, 2014

AndroidObservable.bind makes sure the subscription is observed on the main thread and that the fragment or activity is not finishing, so is still somewhat necessary.

AndroidObservable.bind makes sure the subscription is observed on the main thread and that the fragment or activity is not finishing, so is still somewhat necessary.

@tbruyelle

This comment has been minimized.

Show comment
Hide comment
@tbruyelle

tbruyelle Oct 15, 2014

@DylanSale Thanks for all the details, it's water clear now.

@DylanSale Thanks for all the details, it's water clear now.

@hamidp

This comment has been minimized.

Show comment
Hide comment
@hamidp

hamidp Nov 15, 2014

Contributor

From what I can gather the discussion here is centered around the create-destroy lifecycle. We should include resume-pause, start-stop as well as the fragment equivalents.

For our purposes at Trello @dlew quickly solved this by separating out the state tracking. Rough outline is below:

public class TFragment extends Fragment {

    private LifecycleSubscription mLifecycleSubscription = new LifecycleSubscription(true);

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mLifecycleSubscription.onAttach();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLifecycleSubscription.onCreate();
    }
    // and others. you get the idea

    public void bindSubscriptionToLifecycle(Subscription subscription) {
        mLifecycleSubscription.bindSubscriptionToLifecycle(subscription);
    }
}

LifecycleSubscription itself is fairly simple. Relevant parts below:

public class LifecycleSubscription {

    private enum State {
        OUTSIDE,
        ATTACH_DETACH,
        CREATE_DESTROY,
        CREATE_DESTROY_VIEW,
        START_STOP,
        RESUME_PAUSE
    }

    private boolean mIsFragment;
    private State mState = State.OUTSIDE;
    private Map<State, CompositeSubscription> mSubscriptions;

    public LifecycleSubscription(boolean isFragment) {
        mIsFragment = isFragment;
    }

    public void onAttach() {
        if (!mIsFragment) {
            throw new RuntimeException("Should not be calling onAttach() if not a Fragment!");
        }

        mState = State.ATTACH_DETACH;
    }

    // and others

    public void onDetach() {
        if (!mIsFragment) {
            throw new RuntimeException("Should not be calling onDetach() if not a Fragment!");
        }

        unsubscribe(State.ATTACH_DETACH);
        mState = State.OUTSIDE;
    }

    public void bindSubscriptionToLifecycle(Subscription subscription) {
        if (mState == State.OUTSIDE) {
            throw new RuntimeException("Cannot bind Subscription to lifecycle; we're currently outside of it!");
        }

        if (mSubscriptions == null) {
            mSubscriptions = new HashMap<>(State.values().length);
        }

        CompositeSubscription subscriptions = mSubscriptions.get(mState);
        if (subscriptions == null) {
            subscriptions = new CompositeSubscription();
            mSubscriptions.put(mState, subscriptions);
        }

        subscriptions.add(subscription);
    }

    private void unsubscribe(State state) {
        if (mSubscriptions == null) {
            return;
        }

        if (!mSubscriptions.containsKey(state)) {
            return;
        }

        mSubscriptions.remove(state).unsubscribe();
    }
}

The other nice thing about a class like LifecycleSubscription is that it can be consumed by itself as well without requiring extending a RxFragment.

Making AndroidObservable.bind do something similar would make it simpler to use obviously.

Contributor

hamidp commented Nov 15, 2014

From what I can gather the discussion here is centered around the create-destroy lifecycle. We should include resume-pause, start-stop as well as the fragment equivalents.

For our purposes at Trello @dlew quickly solved this by separating out the state tracking. Rough outline is below:

public class TFragment extends Fragment {

    private LifecycleSubscription mLifecycleSubscription = new LifecycleSubscription(true);

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mLifecycleSubscription.onAttach();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLifecycleSubscription.onCreate();
    }
    // and others. you get the idea

    public void bindSubscriptionToLifecycle(Subscription subscription) {
        mLifecycleSubscription.bindSubscriptionToLifecycle(subscription);
    }
}

LifecycleSubscription itself is fairly simple. Relevant parts below:

public class LifecycleSubscription {

    private enum State {
        OUTSIDE,
        ATTACH_DETACH,
        CREATE_DESTROY,
        CREATE_DESTROY_VIEW,
        START_STOP,
        RESUME_PAUSE
    }

    private boolean mIsFragment;
    private State mState = State.OUTSIDE;
    private Map<State, CompositeSubscription> mSubscriptions;

    public LifecycleSubscription(boolean isFragment) {
        mIsFragment = isFragment;
    }

    public void onAttach() {
        if (!mIsFragment) {
            throw new RuntimeException("Should not be calling onAttach() if not a Fragment!");
        }

        mState = State.ATTACH_DETACH;
    }

    // and others

    public void onDetach() {
        if (!mIsFragment) {
            throw new RuntimeException("Should not be calling onDetach() if not a Fragment!");
        }

        unsubscribe(State.ATTACH_DETACH);
        mState = State.OUTSIDE;
    }

    public void bindSubscriptionToLifecycle(Subscription subscription) {
        if (mState == State.OUTSIDE) {
            throw new RuntimeException("Cannot bind Subscription to lifecycle; we're currently outside of it!");
        }

        if (mSubscriptions == null) {
            mSubscriptions = new HashMap<>(State.values().length);
        }

        CompositeSubscription subscriptions = mSubscriptions.get(mState);
        if (subscriptions == null) {
            subscriptions = new CompositeSubscription();
            mSubscriptions.put(mState, subscriptions);
        }

        subscriptions.add(subscription);
    }

    private void unsubscribe(State state) {
        if (mSubscriptions == null) {
            return;
        }

        if (!mSubscriptions.containsKey(state)) {
            return;
        }

        mSubscriptions.remove(state).unsubscribe();
    }
}

The other nice thing about a class like LifecycleSubscription is that it can be consumed by itself as well without requiring extending a RxFragment.

Making AndroidObservable.bind do something similar would make it simpler to use obviously.

@mttkay

This comment has been minimized.

Show comment
Hide comment
@mttkay

mttkay Nov 19, 2014

Collaborator

The other nice thing about a class like LifecycleSubscription is that it can be consumed by itself as well without requiring extending a RxFragment.

That's a very strong argument actually (although I still like the Rx-yness of the takeUntil approach.) While not exactly for Rx, we use a very similar pattern in our app where we inject various LifeCycleComponents into fragments and activities. Their life-cycle will be then bound to the fragment/activity and execute automatically. Think RoboGuice Events, just minus the reflection. We've had a lot of success applying this pattern to keep Activities and Fragments lean and clean, and it facilitates a clear separation of concerns and easier unit tests.

I'm curious to hear @JakeWharton's thoughts on this, I believe you mentioned before you don't like the idea of binding observables to context life-cycle, but from the brief exchange on Twitter I wasn't fully able to collect how you guys do it at Square?

Collaborator

mttkay commented Nov 19, 2014

The other nice thing about a class like LifecycleSubscription is that it can be consumed by itself as well without requiring extending a RxFragment.

That's a very strong argument actually (although I still like the Rx-yness of the takeUntil approach.) While not exactly for Rx, we use a very similar pattern in our app where we inject various LifeCycleComponents into fragments and activities. Their life-cycle will be then bound to the fragment/activity and execute automatically. Think RoboGuice Events, just minus the reflection. We've had a lot of success applying this pattern to keep Activities and Fragments lean and clean, and it facilitates a clear separation of concerns and easier unit tests.

I'm curious to hear @JakeWharton's thoughts on this, I believe you mentioned before you don't like the idea of binding observables to context life-cycle, but from the brief exchange on Twitter I wasn't fully able to collect how you guys do it at Square?

@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 19, 2014

Collaborator

The only thing that really frustrates me about the solution posted above (which I wrote, so I'm giving myself a hard time) is that it requires subclassing Activity, Fragment, and their many subclasses. Once we start subclassing those we go down quite the rabbit hole, especially in terms of compatibility with existing code bases (and support libraries).

We could create something like IActivity and IFragment which your Activity and Fragment subclass, then have LifecycleSubscription use that instead; it messes with the visibility of all the lifecycle methods, though.

If people think this is a good idea I can cook up a PR.

By the way, I don't think all your observables should be bound to the lifecycle. It just happens that this makes sense for most subscriptions on Android. Semi-automatic unsubscribing relies on the lifecycle, but without a setup like above you might end up being confused about when it unsubscribes.

Collaborator

dlew commented Nov 19, 2014

The only thing that really frustrates me about the solution posted above (which I wrote, so I'm giving myself a hard time) is that it requires subclassing Activity, Fragment, and their many subclasses. Once we start subclassing those we go down quite the rabbit hole, especially in terms of compatibility with existing code bases (and support libraries).

We could create something like IActivity and IFragment which your Activity and Fragment subclass, then have LifecycleSubscription use that instead; it messes with the visibility of all the lifecycle methods, though.

If people think this is a good idea I can cook up a PR.

By the way, I don't think all your observables should be bound to the lifecycle. It just happens that this makes sense for most subscriptions on Android. Semi-automatic unsubscribing relies on the lifecycle, but without a setup like above you might end up being confused about when it unsubscribes.

@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 20, 2014

Collaborator

I just checked out the takeUntil pattern in depth - looks pretty cool. Some hybrid of my bind method + takeUntil could get you a fairly decent lifecycle bind. I'm going to work on something like that soon (conference season is in full swing this week...)

Collaborator

dlew commented Nov 20, 2014

I just checked out the takeUntil pattern in depth - looks pretty cool. Some hybrid of my bind method + takeUntil could get you a fairly decent lifecycle bind. I'm going to work on something like that soon (conference season is in full swing this week...)

@mttkay

This comment has been minimized.

Show comment
Hide comment
@mttkay

mttkay Nov 20, 2014

Collaborator

How would you feel about creating a topic branch where you add a sample impl to the samples module? It would be nice to look at it in practice. It does sound like a decent solution.

Collaborator

mttkay commented Nov 20, 2014

How would you feel about creating a topic branch where you add a sample impl to the samples module? It would be nice to look at it in practice. It does sound like a decent solution.

@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 20, 2014

Collaborator

SGTM.

Collaborator

dlew commented Nov 20, 2014

SGTM.

@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 21, 2014

Collaborator

Here's a very simple (and incomplete) sample implementation: https://github.com/dlew/RxAndroid/tree/dlew/lifecycle-subscriptions

In particular, check out LifecycleBindingActivity. Minus bindLifecycle(), the Activity leaks. With it, the subscription automatically disappears at the right time!

You can see the implementation of it here.

The nice thing about this solution is that it's a single method call to add lifecycle binding. The negative is that it requires subclassing Activity (and eventually Fragment whenever we implement that). Luckily, the current setup doesn't require you to use a built-in extended class (you could do all the footwork yourself with LifecycleManager).

There's a few open questions with this implementation:

  • Where should the implementation classes go? Right now it's all just in the same package but that is clearly wrong, but I'm not familiar enough with the current structure to say where classes should go.
  • How many base Activity and Fragments should we include? Should we include support library versions? (This makes package naming even harder...)
  • Is it bad that the listeners listen to all lifecycle events? If so, we could try to customize it such that each listener only listens to one event, to avoid excessive method invocation.
  • Android has been on a tear against enums recently due to perf issues; should we use ints instead?
Collaborator

dlew commented Nov 21, 2014

Here's a very simple (and incomplete) sample implementation: https://github.com/dlew/RxAndroid/tree/dlew/lifecycle-subscriptions

In particular, check out LifecycleBindingActivity. Minus bindLifecycle(), the Activity leaks. With it, the subscription automatically disappears at the right time!

You can see the implementation of it here.

The nice thing about this solution is that it's a single method call to add lifecycle binding. The negative is that it requires subclassing Activity (and eventually Fragment whenever we implement that). Luckily, the current setup doesn't require you to use a built-in extended class (you could do all the footwork yourself with LifecycleManager).

There's a few open questions with this implementation:

  • Where should the implementation classes go? Right now it's all just in the same package but that is clearly wrong, but I'm not familiar enough with the current structure to say where classes should go.
  • How many base Activity and Fragments should we include? Should we include support library versions? (This makes package naming even harder...)
  • Is it bad that the listeners listen to all lifecycle events? If so, we could try to customize it such that each listener only listens to one event, to avoid excessive method invocation.
  • Android has been on a tear against enums recently due to perf issues; should we use ints instead?
@omo

This comment has been minimized.

Show comment
Hide comment
@omo

omo Nov 21, 2014

Contributor

This is neat!
Talking as an Rx activist, this might be tweaked to become a bit more primitive and composable by...

  • Let LifecycleManager emit all the lifecycle events and let its clients filter() the stream to listen the whatever event type they want.
  • OperatorLifecycleEvent could become a implementation detail. Instead, we could have something like Observable<LifecycleEvent> LifecycleManager#observable().
  • We could even have Observable<LifecycleEvent> XxxObservable::lifecycle(Activity) for newer SDK versions, which uses LifecycleManager internally.
Contributor

omo commented Nov 21, 2014

This is neat!
Talking as an Rx activist, this might be tweaked to become a bit more primitive and composable by...

  • Let LifecycleManager emit all the lifecycle events and let its clients filter() the stream to listen the whatever event type they want.
  • OperatorLifecycleEvent could become a implementation detail. Instead, we could have something like Observable<LifecycleEvent> LifecycleManager#observable().
  • We could even have Observable<LifecycleEvent> XxxObservable::lifecycle(Activity) for newer SDK versions, which uses LifecycleManager internally.
@zsiegel

This comment has been minimized.

Show comment
Hide comment
@zsiegel

zsiegel Nov 22, 2014

This is a pretty cool solution indeed.

What if instead of providing the RxActivity we just provided the LifecycleManager? It would allow people to implement their own base classes and also only register the events they would like to use.

I would imagine we could push the public <T> Observable<T> bindLifecycle(Observable<T> source) into the LifecycleManager possibly?

zsiegel commented Nov 22, 2014

This is a pretty cool solution indeed.

What if instead of providing the RxActivity we just provided the LifecycleManager? It would allow people to implement their own base classes and also only register the events they would like to use.

I would imagine we could push the public <T> Observable<T> bindLifecycle(Observable<T> source) into the LifecycleManager possibly?

@Takhion

This comment has been minimized.

Show comment
Hide comment
@Takhion

Takhion Nov 22, 2014

It's so interesting how different people can converge to the same solution independently!
We use the exact same LifecycleManager pattern and all the base classes are simply wrappers around it that call the equivalent method, like:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    lifecycleManager.onCreate(savedInstanceState);
}

Also, in response to @dlew:

Is it bad that the listeners listen to all lifecycle events? If so, we could try to customize it such that each listener only listens to one event, to avoid excessive method invocation.

Like you we have an interface with a single callback for every state change:

public void onLifecycleEvent(@LifecycleEvent int event, @Nullable Bundle extras);

but when you add the listener you can specify which events you're interested in and it places it in different "queues" based on that, essentially just a SparseArray<Set<LifecycleListener>> where the key is the event ID and the value the set of listeners.

Android has been on a tear against enums recently due to perf issues; should we use ints instead?

We use ints everywhere, but it's more of a habit. I recall reading that enums are not so bad anymore, though being how these calls could potentially be used in different places, it's probably worth the effort.
Oh and, like I did in the interface above, it becomes quite nice if you use the @MagicConstant annotation (with Gradle: provided 'com.intellij:annotations:+@jar')!

Takhion commented Nov 22, 2014

It's so interesting how different people can converge to the same solution independently!
We use the exact same LifecycleManager pattern and all the base classes are simply wrappers around it that call the equivalent method, like:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    lifecycleManager.onCreate(savedInstanceState);
}

Also, in response to @dlew:

Is it bad that the listeners listen to all lifecycle events? If so, we could try to customize it such that each listener only listens to one event, to avoid excessive method invocation.

Like you we have an interface with a single callback for every state change:

public void onLifecycleEvent(@LifecycleEvent int event, @Nullable Bundle extras);

but when you add the listener you can specify which events you're interested in and it places it in different "queues" based on that, essentially just a SparseArray<Set<LifecycleListener>> where the key is the event ID and the value the set of listeners.

Android has been on a tear against enums recently due to perf issues; should we use ints instead?

We use ints everywhere, but it's more of a habit. I recall reading that enums are not so bad anymore, though being how these calls could potentially be used in different places, it's probably worth the effort.
Oh and, like I did in the interface above, it becomes quite nice if you use the @MagicConstant annotation (with Gradle: provided 'com.intellij:annotations:+@jar')!

@Takhion

This comment has been minimized.

Show comment
Hide comment
@Takhion

Takhion Nov 22, 2014

Ah, in a related thought, I was thinking of generalizing the LifecycleManager even further by:

  • Making everything that wraps one implement a specific interface like LifecycleManagerProvider; imagine creating a View and being able to observe the lifecycle of the underlying Context just by using this.
  • Specifying the ID(s) of "terminating" event(s) (like onDestroy()) that would automatically call a releaseResources() callback and unsubscribe all listeners, so that from an Rx point of view it wouldn't matter if it was an Activity, Fragment, Service or anything else!

Takhion commented Nov 22, 2014

Ah, in a related thought, I was thinking of generalizing the LifecycleManager even further by:

  • Making everything that wraps one implement a specific interface like LifecycleManagerProvider; imagine creating a View and being able to observe the lifecycle of the underlying Context just by using this.
  • Specifying the ID(s) of "terminating" event(s) (like onDestroy()) that would automatically call a releaseResources() callback and unsubscribe all listeners, so that from an Rx point of view it wouldn't matter if it was an Activity, Fragment, Service or anything else!
@dpsm

This comment has been minimized.

Show comment
Hide comment
@dpsm

dpsm Nov 22, 2014

Contributor

@JakeWharton do you think annotation processing might help us with avoiding having to write lifecycleManager.onLifecycleEvent(..) everywhere in dlew@5d2bf5b#diff-9eea8acd7dcb31aafc301e5488329a6aR35

If we were to generate a subclass of an annotated activity to add the invocations to onLifecycleEvent(..) we'd need to add the generated subclass to the AndroidManifest :( Other ideas?

Contributor

dpsm commented Nov 22, 2014

@JakeWharton do you think annotation processing might help us with avoiding having to write lifecycleManager.onLifecycleEvent(..) everywhere in dlew@5d2bf5b#diff-9eea8acd7dcb31aafc301e5488329a6aR35

If we were to generate a subclass of an annotated activity to add the invocations to onLifecycleEvent(..) we'd need to add the generated subclass to the AndroidManifest :( Other ideas?

@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 22, 2014

Collaborator

Thanks for the great feedback. I'm going to spend more time on this today.

Would it be easier to do this as a PR (without intention to merge)?

Also, I'm beginning to realize that the discussion of subclassing Activity and Fragment can be separated from the LifecycleManager implementation itself.


Regarding enum - There's some advice that used to exist in the docs about int vs. enum which has since been removed. Apparently the reason it was removed was because its performance claims were not backed up by actual benchmarks.

I'm mostly concerned about the performance in use - the extra filesize + class initialization are not big concerns for me. So if those proved not to be a real issue, then the enum seems fine. (We could always change it later, too...)

Collaborator

dlew commented Nov 22, 2014

Thanks for the great feedback. I'm going to spend more time on this today.

Would it be easier to do this as a PR (without intention to merge)?

Also, I'm beginning to realize that the discussion of subclassing Activity and Fragment can be separated from the LifecycleManager implementation itself.


Regarding enum - There's some advice that used to exist in the docs about int vs. enum which has since been removed. Apparently the reason it was removed was because its performance claims were not backed up by actual benchmarks.

I'm mostly concerned about the performance in use - the extra filesize + class initialization are not big concerns for me. So if those proved not to be a real issue, then the enum seems fine. (We could always change it later, too...)

@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 22, 2014

Collaborator

After much pacing-around-the-room-and-thinking, I've come to the conclusion that what we really want are two separate things:

  1. An Observable<LifecycleEvent> for any given Activity or Fragment
  2. A way to use an Observable<LifecycleEvent> to determine which LifecycleEvent to use with takeUntil().

Viewed in this light, my current code is quite bad. LifecycleManager acts as a solution for both at once (and in a non-reactive way).

The first problem is proving tricky to write in an elegant manner (aka, not having to extend every Activity and Fragment in existence). Inelegantly, it could just be a PublishSubject inside of each lifecycle class. I would rather we figured out a good way to do it minus the Subject, but best would be without even having to extend everything.

The second problem can be solved fairly simply once the first is solved.


I, too, would be interested if @JakeWharton thinks whether the first problem is solvable via annotations. I'm not experienced in writing annotation processing libs.

Collaborator

dlew commented Nov 22, 2014

After much pacing-around-the-room-and-thinking, I've come to the conclusion that what we really want are two separate things:

  1. An Observable<LifecycleEvent> for any given Activity or Fragment
  2. A way to use an Observable<LifecycleEvent> to determine which LifecycleEvent to use with takeUntil().

Viewed in this light, my current code is quite bad. LifecycleManager acts as a solution for both at once (and in a non-reactive way).

The first problem is proving tricky to write in an elegant manner (aka, not having to extend every Activity and Fragment in existence). Inelegantly, it could just be a PublishSubject inside of each lifecycle class. I would rather we figured out a good way to do it minus the Subject, but best would be without even having to extend everything.

The second problem can be solved fairly simply once the first is solved.


I, too, would be interested if @JakeWharton thinks whether the first problem is solvable via annotations. I'm not experienced in writing annotation processing libs.

@roman-mazur

This comment has been minimized.

Show comment
Hide comment
@roman-mazur

roman-mazur Nov 22, 2014

ReactiveCocoa has a very interesting solution. They provide means to create an observable (signal in their terminology) that emits an event when a method specified via Objective-C selector is invoked. In our case we could have an annotation processor that generates such Observables for requested methods.

// Rx_MyActivity is a generated class by a processor that handles @Observable annotation.
// onDestoryMethodObservable() is a generated method in Rx_MyActivity class.
class MyActivity extends Rx_MyActivity {

  @Override
  protected void onCreate(Bundle b) {
    super.onCreate(b);
    someObservable().takeUntil(onDestoryMethodObservable()).subscribe(...);
  }

  @Observable
  @Override
  protected void onDestroy() {
    super.onDestroy();
  }
}


Such a processor should not be Android specific. It might exist as a separate project in ReactiveX group. What do you think about such a design? /cc @benjchristensen

ReactiveCocoa has a very interesting solution. They provide means to create an observable (signal in their terminology) that emits an event when a method specified via Objective-C selector is invoked. In our case we could have an annotation processor that generates such Observables for requested methods.

// Rx_MyActivity is a generated class by a processor that handles @Observable annotation.
// onDestoryMethodObservable() is a generated method in Rx_MyActivity class.
class MyActivity extends Rx_MyActivity {

  @Override
  protected void onCreate(Bundle b) {
    super.onCreate(b);
    someObservable().takeUntil(onDestoryMethodObservable()).subscribe(...);
  }

  @Observable
  @Override
  protected void onDestroy() {
    super.onDestroy();
  }
}


Such a processor should not be Android specific. It might exist as a separate project in ReactiveX group. What do you think about such a design? /cc @benjchristensen

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Nov 22, 2014

Member

No you cannot use an annotation processor for modifying code and the @Observable annotation is out of the question unless you want to do AOP (at compile time, since it's not supported at runtime).

Member

JakeWharton commented Nov 22, 2014

No you cannot use an annotation processor for modifying code and the @Observable annotation is out of the question unless you want to do AOP (at compile time, since it's not supported at runtime).

@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 23, 2014

Collaborator

I've finished a second draft based on Observable<LifecycleEvent>. I think it's a lot better than my first attempt. Check it out: https://github.com/dlew/RxAndroid/tree/dlew/lifecycle-observable

In particular, look at LifecycleObservable, which now follows the pattern of the other binds in the system.

Still no great solution to the Activity and Fragment problem...

If people like this, I'll work up some tests + javadocs for PR.

Collaborator

dlew commented Nov 23, 2014

I've finished a second draft based on Observable<LifecycleEvent>. I think it's a lot better than my first attempt. Check it out: https://github.com/dlew/RxAndroid/tree/dlew/lifecycle-observable

In particular, look at LifecycleObservable, which now follows the pattern of the other binds in the system.

Still no great solution to the Activity and Fragment problem...

If people like this, I'll work up some tests + javadocs for PR.

@roman-mazur

This comment has been minimized.

Show comment
Hide comment
@roman-mazur

roman-mazur Nov 23, 2014

I was not proposing to modify the code. Just to generate a super class. Yet
you'll need to pass an 'initial' super class to a class annotation.

On 02:33, Sun, Nov 23, 2014 Daniel Lew notifications@github.com wrote:

I've finished a draft based on Observable. I think it's a
lot better than my first attempt. Check it out:
https://github.com/dlew/RxAndroid/tree/dlew/lifecycle-observable

LifecycleObservable
https://github.com/dlew/RxAndroid/blob/dlew/lifecycle-observable/rxandroid/src/main/java/rx/android/lifecycle/LifecycleObservable.java
now follows the pattern of the other binds in the system.

Still no great solution to the Activity and Fragment problem...

If people like this, I'll work up some tests + javadocs for PR.


Reply to this email directly or view it on GitHub
#12 (comment).

I was not proposing to modify the code. Just to generate a super class. Yet
you'll need to pass an 'initial' super class to a class annotation.

On 02:33, Sun, Nov 23, 2014 Daniel Lew notifications@github.com wrote:

I've finished a draft based on Observable. I think it's a
lot better than my first attempt. Check it out:
https://github.com/dlew/RxAndroid/tree/dlew/lifecycle-observable

LifecycleObservable
https://github.com/dlew/RxAndroid/blob/dlew/lifecycle-observable/rxandroid/src/main/java/rx/android/lifecycle/LifecycleObservable.java
now follows the pattern of the other binds in the system.

Still no great solution to the Activity and Fragment problem...

If people like this, I'll work up some tests + javadocs for PR.


Reply to this email directly or view it on GitHub
#12 (comment).

@dpsm

This comment has been minimized.

Show comment
Hide comment
@dpsm

dpsm Nov 23, 2014

Contributor

@JakeWharton what if for the a class such as:

@ManagedLifecycle
public class MyActivity extends Activity {

    public void onCreate(...) {
        // Some code here
    }   
}

The annotation processor generated something like:

//Auto generated by annotation processor
public class MyActivityManaged extends MyActivity {

    public void onCreate(...) {
        super.onCreate(...);
        lifecycleManager.onLifecycleEvent(...)
    }

    public void onStart(...) {
        super.onStart(...);
        lifecycleManager.onLifecycleEvent(...)
    }

}

With this approach we don't mess up with changing code and still allow for the "injection". The only downside would be that the manifest entry for the activity needs to be changed to the XXManaged class.

Contributor

dpsm commented Nov 23, 2014

@JakeWharton what if for the a class such as:

@ManagedLifecycle
public class MyActivity extends Activity {

    public void onCreate(...) {
        // Some code here
    }   
}

The annotation processor generated something like:

//Auto generated by annotation processor
public class MyActivityManaged extends MyActivity {

    public void onCreate(...) {
        super.onCreate(...);
        lifecycleManager.onLifecycleEvent(...)
    }

    public void onStart(...) {
        super.onStart(...);
        lifecycleManager.onLifecycleEvent(...)
    }

}

With this approach we don't mess up with changing code and still allow for the "injection". The only downside would be that the manifest entry for the activity needs to be changed to the XXManaged class.

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Nov 23, 2014

Member

Yeah AndroidAnnotations behaves like this, and it's.. well, kind of gross.

My vote is to use the activity lifecycle callbacks and not re-invent the wheel. Pre-14 people can fend for themselves.

Member

JakeWharton commented Nov 23, 2014

Yeah AndroidAnnotations behaves like this, and it's.. well, kind of gross.

My vote is to use the activity lifecycle callbacks and not re-invent the wheel. Pre-14 people can fend for themselves.

@dpsm

This comment has been minimized.

Show comment
Hide comment
@dpsm

dpsm Nov 23, 2014

Contributor

Fair enough. I don't think there are enough advantages over the life cycle
callbacks to justify the complicated "solution" either.
On Nov 23, 2014 10:09 AM, "Jake Wharton" notifications@github.com wrote:

Yeah AndroidAnnotations behaves like this, and it's.. well, kind of gross.

My vote is to use the activity lifecycle callbacks and not re-invent the
wheel. Pre-14 people can fend for themselves.


Reply to this email directly or view it on GitHub
#12 (comment).

Contributor

dpsm commented Nov 23, 2014

Fair enough. I don't think there are enough advantages over the life cycle
callbacks to justify the complicated "solution" either.
On Nov 23, 2014 10:09 AM, "Jake Wharton" notifications@github.com wrote:

Yeah AndroidAnnotations behaves like this, and it's.. well, kind of gross.

My vote is to use the activity lifecycle callbacks and not re-invent the
wheel. Pre-14 people can fend for themselves.


Reply to this email directly or view it on GitHub
#12 (comment).

@roman-mazur

This comment has been minimized.

Show comment
Hide comment
@roman-mazur

roman-mazur Nov 23, 2014

Here's a simple illustration that compiles, done in 1 hour.

https://github.com/roman-mazur/rx-annotations/blob/master/test/src/main/java/rx/annotations/sample/Component.java

If somebody is interested...
Run

git clone git@github.com:roman-mazur/rx-annotations.git
cd rx-annotations
./gradlew :test:compileJava
cat test/build/classes/main/rx/annotations/sample/Rx_Component.java 

To see generated class.

Here's a simple illustration that compiles, done in 1 hour.

https://github.com/roman-mazur/rx-annotations/blob/master/test/src/main/java/rx/annotations/sample/Component.java

If somebody is interested...
Run

git clone git@github.com:roman-mazur/rx-annotations.git
cd rx-annotations
./gradlew :test:compileJava
cat test/build/classes/main/rx/annotations/sample/Rx_Component.java 

To see generated class.

@roman-mazur

This comment has been minimized.

Show comment
Hide comment
@roman-mazur

roman-mazur Nov 23, 2014

AOP approach would be simpler in implementation and usage though...

AOP approach would be simpler in implementation and usage though...

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Nov 23, 2014

Member

Yeah but it's massively worse conceptually. Although the people who tolerate Retrolamba probably wouldn't care!

Member

JakeWharton commented Nov 23, 2014

Yeah but it's massively worse conceptually. Although the people who tolerate Retrolamba probably wouldn't care!

@roman-mazur

This comment has been minimized.

Show comment
Hide comment
@roman-mazur

roman-mazur Nov 23, 2014

Spent another 30 minutes :)
https://github.com/roman-mazur/rx-annotations/blob/master/test/src/main/java/rx/annotations/sample/Component.java

@SuperClass(
    value = SomeFrameworkComponent.class,
    methods = "onLowMemory"
)
public class Component extends Rx_SomeFrameworkComponent {

  @Override
  protected void onCreate() {
    super.onCreate();
    Observable.never().takeUntil(onDestroyObservable());
    Observable.never().takeUntil(onLowMemoryObservable());
  }

  @RxObservable
  @Override
  protected void onDestroy() {
    super.onDestroy();
    System.out.println("Custom implementation");
  }

}

It might be not necessary to override methods.

git clone git@github.com:roman-mazur/rx-annotations.git
cd rx-annotations
./gradlew :test:compileJava
cat test/build/classes/main/rx/annotations/sample/Rx_SomeFrameworkComponent.java 

@JakeWharton Do you think it reduces boilerplate or actually introduces it? :)

Spent another 30 minutes :)
https://github.com/roman-mazur/rx-annotations/blob/master/test/src/main/java/rx/annotations/sample/Component.java

@SuperClass(
    value = SomeFrameworkComponent.class,
    methods = "onLowMemory"
)
public class Component extends Rx_SomeFrameworkComponent {

  @Override
  protected void onCreate() {
    super.onCreate();
    Observable.never().takeUntil(onDestroyObservable());
    Observable.never().takeUntil(onLowMemoryObservable());
  }

  @RxObservable
  @Override
  protected void onDestroy() {
    super.onDestroy();
    System.out.println("Custom implementation");
  }

}

It might be not necessary to override methods.

git clone git@github.com:roman-mazur/rx-annotations.git
cd rx-annotations
./gradlew :test:compileJava
cat test/build/classes/main/rx/annotations/sample/Rx_SomeFrameworkComponent.java 

@JakeWharton Do you think it reduces boilerplate or actually introduces it? :)

@DylanSale

This comment has been minimized.

Show comment
Hide comment
@DylanSale

DylanSale Nov 23, 2014

One thing to note with using takeUntil that came up recently for me is that it actually causes the Observable to complete, i. e. onComplete is called. This caused an issue because "completing" and "unsubscribing due to lifecycle callbacks" had different semantics. I'm not sure if they should really be distinguishable or not, or if there is even a nice way to distinguish the two (that doesn't include using onError).

I tweaked the Subscriber to not have that distinction, so it wasn't ultimately a big deal, just something that didn't crop up before moving to using takeUntil.

One thing to note with using takeUntil that came up recently for me is that it actually causes the Observable to complete, i. e. onComplete is called. This caused an issue because "completing" and "unsubscribing due to lifecycle callbacks" had different semantics. I'm not sure if they should really be distinguishable or not, or if there is even a nice way to distinguish the two (that doesn't include using onError).

I tweaked the Subscriber to not have that distinction, so it wasn't ultimately a big deal, just something that didn't crop up before moving to using takeUntil.

@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 23, 2014

Collaborator

@JakeWharton Even if we used the activity lifecycle callbacks that would still not solve the problem for Fragment users. (inb4 yet another debate about whether to use Fragments)

I agree that this version of annotation processing seems to smell. Anytime you're explicitly using classes that don't actually exist until you build worries me.

Collaborator

dlew commented Nov 23, 2014

@JakeWharton Even if we used the activity lifecycle callbacks that would still not solve the problem for Fragment users. (inb4 yet another debate about whether to use Fragments)

I agree that this version of annotation processing seems to smell. Anytime you're explicitly using classes that don't actually exist until you build worries me.

@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 23, 2014

Collaborator

@DylanSale That's a really good point. I can see a lot of devs being tripped up by it. For example, if I create an Observable<RetrofitResponse> I would not expect to see onComplete() before onNext(). We're using a side effect here - the fact that on complete causes unsubscription.

OperatorTakeUntil is one of the simpler operators; we could create OperatorTakeUntilThenUnsubscribe that combines its functionality with something similar to OperatorConditionalBinding.

Collaborator

dlew commented Nov 23, 2014

@DylanSale That's a really good point. I can see a lot of devs being tripped up by it. For example, if I create an Observable<RetrofitResponse> I would not expect to see onComplete() before onNext(). We're using a side effect here - the fact that on complete causes unsubscription.

OperatorTakeUntil is one of the simpler operators; we could create OperatorTakeUntilThenUnsubscribe that combines its functionality with something similar to OperatorConditionalBinding.

@mr-archano

This comment has been minimized.

Show comment
Hide comment
@mr-archano

mr-archano Nov 23, 2014

My 2c after reading the entire discussion few times and have seen @dlew's PR:

  • 👍 for the idea of a separate component and the reactive approach to deal with automatic unsubscribing, especially using a custom operator that explicitly models the unsubscribing in onNext()
  • 👎 for the idea to bring an Observable<SomeEvent>: I see this bringing the problem of how to generate such Observable in an elegant way in your Activity/Fragment, and to be honest I see it as too convoluted. Especially the filter/matching mechanism in LifecycleObservable#107 is definitely not appealing to me.

I would love to see a simpler solution, and I quickly drafted something in a gist. It is basically a mix of some of the ideas above:

  • uses the 'reactive' approach proposed by @DylanSale for the unsubscribing mechanism
  • uses @dlew OperatorSubscribeUntil to model the onNext() -> unsubscribe
  • uses composition vs inheritance

My 2c after reading the entire discussion few times and have seen @dlew's PR:

  • 👍 for the idea of a separate component and the reactive approach to deal with automatic unsubscribing, especially using a custom operator that explicitly models the unsubscribing in onNext()
  • 👎 for the idea to bring an Observable<SomeEvent>: I see this bringing the problem of how to generate such Observable in an elegant way in your Activity/Fragment, and to be honest I see it as too convoluted. Especially the filter/matching mechanism in LifecycleObservable#107 is definitely not appealing to me.

I would love to see a simpler solution, and I quickly drafted something in a gist. It is basically a mix of some of the ideas above:

  • uses the 'reactive' approach proposed by @DylanSale for the unsubscribing mechanism
  • uses @dlew OperatorSubscribeUntil to model the onNext() -> unsubscribe
  • uses composition vs inheritance
@dlew

This comment has been minimized.

Show comment
Hide comment
@dlew

dlew Nov 24, 2014

Collaborator

From your gist it looks like your solution still requires inheritance. More
of it than mine does.

That solution looks a lot like my original work. The problem is that it
adds a lot of unnecessary framework. It stores state outside of a reactive
context. It depends on smart overrides of methods in the life cycle.

When I really reduced the problem to its core parts I realized the
Observable of the life cycle was all I really needed.

On Sun, Nov 23, 2014, 15:06 Antonio Bertucci notifications@github.com
wrote:

My 2c after reading the entire discussion few times and have seen @dlew
https://github.com/dlew's PR:

  • [image: 👍] for the idea of a separate component and the reactive
    approach to deal with automatic unsubscribing, especially using a custom
    operator that explicitly models the unsubscribing in onNext()
  • [image: 👎] for the idea to bring an Observable: I see
    this will bring the problem of how to generate such Observable in an
    elegant way in your Activity/Fragment, and to be honest I see it as too
    convoluted. Especially the filter/matching mechanism in
    LifecycleObservable#107
    https://github.com/ReactiveX/RxAndroid/pull/75/files#diff-c990fb835e09a42f9171ab3a4b7acdacR107
    is definitely not appealing to me.

I would love to see a simpler solution, and I quickly drafted something in
a gist https://gist.github.com/mr-archano/0d1c5885229229bed637. It is
basically a mix of some of the ideas above:


Reply to this email directly or view it on GitHub
#12 (comment).

Collaborator

dlew commented Nov 24, 2014

From your gist it looks like your solution still requires inheritance. More
of it than mine does.

That solution looks a lot like my original work. The problem is that it
adds a lot of unnecessary framework. It stores state outside of a reactive
context. It depends on smart overrides of methods in the life cycle.

When I really reduced the problem to its core parts I realized the
Observable of the life cycle was all I really needed.

On Sun, Nov 23, 2014, 15:06 Antonio Bertucci notifications@github.com
wrote:

My 2c after reading the entire discussion few times and have seen @dlew
https://github.com/dlew's PR:

  • [image: 👍] for the idea of a separate component and the reactive
    approach to deal with automatic unsubscribing, especially using a custom
    operator that explicitly models the unsubscribing in onNext()
  • [image: 👎] for the idea to bring an Observable: I see
    this will bring the problem of how to generate such Observable in an
    elegant way in your Activity/Fragment, and to be honest I see it as too
    convoluted. Especially the filter/matching mechanism in
    LifecycleObservable#107
    https://github.com/ReactiveX/RxAndroid/pull/75/files#diff-c990fb835e09a42f9171ab3a4b7acdacR107
    is definitely not appealing to me.

I would love to see a simpler solution, and I quickly drafted something in
a gist https://gist.github.com/mr-archano/0d1c5885229229bed637. It is
basically a mix of some of the ideas above:


Reply to this email directly or view it on GitHub
#12 (comment).

@omo

This comment has been minimized.

Show comment
Hide comment
@omo

omo Nov 24, 2014

Contributor

I love to have Observable. I almost did it when I worked on bindView(), and was thinking about the generalisation. I'm eager to rewrite bindView() using LifecycleObservable.

Contributor

omo commented Nov 24, 2014

I love to have Observable. I almost did it when I worked on bindView(), and was thinking about the generalisation. I'm eager to rewrite bindView() using LifecycleObservable.

@mr-archano

This comment has been minimized.

Show comment
Hide comment
@mr-archano

mr-archano Nov 24, 2014

@dlew I don't want to pollute this thread too much, so I put some comment on my gist. We might want to continue the discussion there.

@dlew I don't want to pollute this thread too much, so I put some comment on my gist. We might want to continue the discussion there.

@austynmahoney

This comment has been minimized.

Show comment
Hide comment
@austynmahoney

austynmahoney Nov 25, 2014

Contributor

Not sure if their implementation is all that great, but the Facebook SDK uses a lifecycle helper class that is similar to the suggested implementation here. A lot of developers may already be used to having to call a helper in onCreate and the other lifecycle methods.

I'm not sure you can make it any nicer if you aren't subclassing Activity or Fragment.

Contributor

austynmahoney commented Nov 25, 2014

Not sure if their implementation is all that great, but the Facebook SDK uses a lifecycle helper class that is similar to the suggested implementation here. A lot of developers may already be used to having to call a helper in onCreate and the other lifecycle methods.

I'm not sure you can make it any nicer if you aren't subclassing Activity or Fragment.

@Alexander--

This comment has been minimized.

Show comment
Hide comment
@Alexander--

Alexander-- Nov 28, 2014

I'm not sure you can make it any nicer if you aren't subclassing Activity or Fragment.

We all really must accept a need to use AOP on Android. And here is how to do it right.

What we have

There are plenty of similar tools: AndroidAnnotations, ButterKnife, FragmentArgs, IcePick and now this "LifecycleManager", inserting a line of code in well-known locations to greatly improve code quality, turning Java code into a mix of powerful domain-specific languages. Those lines are small, but their count grows, showing tendency to turn arbitrary object lifecycle callbacks into small event buses, distributing events between multiple libraries. Those buses don't need commonly accepted event identifiers and notification strategies, instead they rely on common API, provided by class authors, and operate by means of bytecode itself.

Every lifecycle method of any object amounts to an event.

What we need

Imagine something like Mimic in reverse - like AspectJ advices, but without diverting into different language or going down to bytecode level:

@PluginFor({Fragment.class})
public abstract class ButterknifeFragmentInjection extends Fragment {

    @PlugInto(PluggingMode.AT_BEGINNING)
    public void onViewCreated(View view) {
        ButterKnife.inject(this, view);
    }
}

Simple, typesafe and straightforward. Extending Fragment here is just a convenience: the code can be written in Java, but rather than being turned into bytecode during compilation it's source code would be distributed with library to act as template for actual class, created by annotation processor:

class ExampleFragment$$Helper {
    private final WeakReference<ExampleFragment> instance;

    ExampleFragment$$Helper(ExampleFragment instance) {
        this.instance = new WeakReference(instance);
    }

    public void onViewCreated(View view) {
        final ExampleFragment self = instance.get();
        ButterKnife.inject(self, view);
        ...
        self = null;
    }

    public void onSaveInstanceState(Bundle state) {
        final ExampleFragment self = instance.get();
        Icepick.saveInstanceState(self, state);
        self = null;
    }
    ...
}

The only things, that would have to be inserted directly in bytecode of ExampleFragment, are calls to generated methods (and, perhaps, constructor of generated helper). Slightly tricky part is choosing which classes to put those calls into, but that is unlikely to pose much trouble.

What should be done

Pretty much everything needed is already here: ready-to-go annotation processing and bytecode manipulation tools already exists. The only real challenge is avoiding obscure side effects and general lack of transparency, that may come from abusing AOP, but with source code being before developer's eyes that would not be as much of issue.

PS Wow, it escalated into a plan of little NIH-induced AOP framework faster than I expected. Still, I believe, that this has at least indirect relation to main topic of this issue, so please comment on the idea, if you have anything to say. Similar solutions, ideas, perhaps?

I'm not sure you can make it any nicer if you aren't subclassing Activity or Fragment.

We all really must accept a need to use AOP on Android. And here is how to do it right.

What we have

There are plenty of similar tools: AndroidAnnotations, ButterKnife, FragmentArgs, IcePick and now this "LifecycleManager", inserting a line of code in well-known locations to greatly improve code quality, turning Java code into a mix of powerful domain-specific languages. Those lines are small, but their count grows, showing tendency to turn arbitrary object lifecycle callbacks into small event buses, distributing events between multiple libraries. Those buses don't need commonly accepted event identifiers and notification strategies, instead they rely on common API, provided by class authors, and operate by means of bytecode itself.

Every lifecycle method of any object amounts to an event.

What we need

Imagine something like Mimic in reverse - like AspectJ advices, but without diverting into different language or going down to bytecode level:

@PluginFor({Fragment.class})
public abstract class ButterknifeFragmentInjection extends Fragment {

    @PlugInto(PluggingMode.AT_BEGINNING)
    public void onViewCreated(View view) {
        ButterKnife.inject(this, view);
    }
}

Simple, typesafe and straightforward. Extending Fragment here is just a convenience: the code can be written in Java, but rather than being turned into bytecode during compilation it's source code would be distributed with library to act as template for actual class, created by annotation processor:

class ExampleFragment$$Helper {
    private final WeakReference<ExampleFragment> instance;

    ExampleFragment$$Helper(ExampleFragment instance) {
        this.instance = new WeakReference(instance);
    }

    public void onViewCreated(View view) {
        final ExampleFragment self = instance.get();
        ButterKnife.inject(self, view);
        ...
        self = null;
    }

    public void onSaveInstanceState(Bundle state) {
        final ExampleFragment self = instance.get();
        Icepick.saveInstanceState(self, state);
        self = null;
    }
    ...
}

The only things, that would have to be inserted directly in bytecode of ExampleFragment, are calls to generated methods (and, perhaps, constructor of generated helper). Slightly tricky part is choosing which classes to put those calls into, but that is unlikely to pose much trouble.

What should be done

Pretty much everything needed is already here: ready-to-go annotation processing and bytecode manipulation tools already exists. The only real challenge is avoiding obscure side effects and general lack of transparency, that may come from abusing AOP, but with source code being before developer's eyes that would not be as much of issue.

PS Wow, it escalated into a plan of little NIH-induced AOP framework faster than I expected. Still, I believe, that this has at least indirect relation to main topic of this issue, so please comment on the idea, if you have anything to say. Similar solutions, ideas, perhaps?

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Nov 28, 2014

Member

AspectJ already does both type-safe declaration in Java and weaving in the bytecode.

Also, no. Just no. You'll never get everyone on board with this.

Member

JakeWharton commented Nov 28, 2014

AspectJ already does both type-safe declaration in Java and weaving in the bytecode.

Also, no. Just no. You'll never get everyone on board with this.

@Alexander--

This comment has been minimized.

Show comment
Hide comment
@Alexander--

Alexander-- Nov 28, 2014

AspectJ already does both type-safe declaration in Java and weaving in the bytecode.

RxAndroid-AspectJ integration, when?

Also, no. Just no. You'll never get everyone on board with this.

I am sorry, if I have created an impression of someone seeking to steal your time here :) Would you, please, describe a solution to problem in question, using AspectJ? I am not a tool fanatic, so if that solution had even half of features I need, I would gladly use it. The concept, described at #93 is fine, but sadly useless in most cases, because it means giving up on inheritance altogether.

AspectJ already does both type-safe declaration in Java and weaving in the bytecode.

RxAndroid-AspectJ integration, when?

Also, no. Just no. You'll never get everyone on board with this.

I am sorry, if I have created an impression of someone seeking to steal your time here :) Would you, please, describe a solution to problem in question, using AspectJ? I am not a tool fanatic, so if that solution had even half of features I need, I would gladly use it. The concept, described at #93 is fine, but sadly useless in most cases, because it means giving up on inheritance altogether.

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Nov 28, 2014

Member

I meant that AspectJ already allows you to write directly in Java and weave class files at compile-time rather than at runtime.

An AOP-based approach is compelling if you have the tolerance for bytecode-manipulating tools. I just think it would be better served as a standalone project than something directly in a library like this one.

Member

JakeWharton commented Nov 28, 2014

I meant that AspectJ already allows you to write directly in Java and weave class files at compile-time rather than at runtime.

An AOP-based approach is compelling if you have the tolerance for bytecode-manipulating tools. I just think it would be better served as a standalone project than something directly in a library like this one.

@Alexander--

This comment has been minimized.

Show comment
Hide comment
@Alexander--

Alexander-- Nov 28, 2014

Look at this code:

public FooActivity extends Activity {
    @InjectView(R.id.text) TextView text;

    public void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.text);
    }

    public void onPostCreate(Bundle savedInstanceState) {
        text.setText("Is this good enough?");
    }
}

Let's say, that it works. Does it matter, if the code relies on ActivityLifecycleCallbacks implemented somewhere else, or employs a bytecode manipulation to have job done? If former, would that be good enough to be used in practice? If later, so what?

I meant that AspectJ already allows you to write directly in Java and weave class files at compile-time rather than at runtime.

@JakeWharton, It still does too little to be trully useful. If I were to tell you, that I 'd like to see a better version of AspectJ without separate language elements (please, don't pretend, that those do not exist), with pointcuts and most of joint types removed and source code of aspects being properly viewable and debuggable, would that be descriptive enough? Just move those code blobs from magical inner classes into source files, generated by annotation processor, and it would be exactly, what I described above. If creating something like that were possible on top of AspectJ in it's current state..

Look at this code:

public FooActivity extends Activity {
    @InjectView(R.id.text) TextView text;

    public void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.text);
    }

    public void onPostCreate(Bundle savedInstanceState) {
        text.setText("Is this good enough?");
    }
}

Let's say, that it works. Does it matter, if the code relies on ActivityLifecycleCallbacks implemented somewhere else, or employs a bytecode manipulation to have job done? If former, would that be good enough to be used in practice? If later, so what?

I meant that AspectJ already allows you to write directly in Java and weave class files at compile-time rather than at runtime.

@JakeWharton, It still does too little to be trully useful. If I were to tell you, that I 'd like to see a better version of AspectJ without separate language elements (please, don't pretend, that those do not exist), with pointcuts and most of joint types removed and source code of aspects being properly viewable and debuggable, would that be descriptive enough? Just move those code blobs from magical inner classes into source files, generated by annotation processor, and it would be exactly, what I described above. If creating something like that were possible on top of AspectJ in it's current state..

@mr-archano

This comment has been minimized.

Show comment
Hide comment
@mr-archano

mr-archano Nov 28, 2014

@Alexander-- I think a lot of people (including me) would like to see/use annotations as a solution, but the aim here is to maintain this module as closer as possible to nuts and bolts.
An rxandroid-framework module has been defined to include some (opinionated) utilities on top of RxAndroid. You could propose to introduce a new external module (eg rxandroid-annotations) to this repo, and kick off the discussion in another GH issue. Or just start it as a separate project and be in touch with the maintainers here.

@Alexander-- I think a lot of people (including me) would like to see/use annotations as a solution, but the aim here is to maintain this module as closer as possible to nuts and bolts.
An rxandroid-framework module has been defined to include some (opinionated) utilities on top of RxAndroid. You could propose to introduce a new external module (eg rxandroid-annotations) to this repo, and kick off the discussion in another GH issue. Or just start it as a separate project and be in touch with the maintainers here.

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Nov 28, 2014

Member

You can't generate inner-classes with an annotation processor. Also I've lost track of whatever you're getting at so unless there's a concrete proposal you're better off playing in a separate project. This project has a hard enough time figuring out what should actually be included without conflating other opinionated technologies.

Member

JakeWharton commented Nov 28, 2014

You can't generate inner-classes with an annotation processor. Also I've lost track of whatever you're getting at so unless there's a concrete proposal you're better off playing in a separate project. This project has a hard enough time figuring out what should actually be included without conflating other opinionated technologies.

@hamidp

This comment has been minimized.

Show comment
Hide comment
@hamidp

hamidp Jan 2, 2015

Contributor

Do we consider #75 as good enough of a solution for now?

Contributor

hamidp commented Jan 2, 2015

Do we consider #75 as good enough of a solution for now?

@JakeWharton

This comment has been minimized.

Show comment
Hide comment
@JakeWharton

JakeWharton Jun 30, 2015

Member

As part of #172 the auto-unsubscribing things have been removed for future release. At this time, there's a wide variety of ways people create streams and want to unsubscribe from them. There's also a lot of potentially useful magic solutions that will appeal to a subset of users but not all. Knowledge of the strong references that are kept and continually trying to improve the pattern of using RxJava on Android will be the path forward for now unless some amazingly awesome solution that everyone can agree on and use.

Member

JakeWharton commented Jun 30, 2015

As part of #172 the auto-unsubscribing things have been removed for future release. At this time, there's a wide variety of ways people create streams and want to unsubscribe from them. There's also a lot of potentially useful magic solutions that will appeal to a subset of users but not all. Knowledge of the strong references that are kept and continually trying to improve the pattern of using RxJava on Android will be the path forward for now unless some amazingly awesome solution that everyone can agree on and use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment