Architecture-components-samples: We need examples of how Lifecycles work with global objects that "live" in Application scope

Created on 11 Jun 2017  路  7Comments  路  Source: android/architecture-components-samples

Hi guys,
I can't find an example of how to handle a situation when a callback needs to be dispatched from a global object that lives in Application scope.

Given the potential multiplicity of Lifecycles involved, is this possible at all?

I would like to refactor this code to use Lifecycles:

public class MyActivity extends Activity implements UserStateManagerListener {

    private UserStateManager mUserStateManager;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        mUserStateManager = ((MyApplication)getApplication()).getUserStateManager();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mUserStateManager.registerListener(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        mUserStateManager.unregisterListener(this);
    }

    @Override
    public boolean onUserStateChanged(UserState userState) {
        ...
    }
}

P.S. I also opened similar issue in googlecodelabs: https://github.com/googlecodelabs/android-lifecycles/issues/12

Most helpful comment

there was a good conversation on reddit when this was post there but I cannot find it.

tl;dr; you can use anything wrong, that does not make it bad or wrong. In fact, in this case, it is really hard to mis-use these because the receiver knows it is a Lifecycle.

The example mentions keeping a reference to a lifecycle knowing that it is a lifecycle. If developer wants to shoot themselves on the foot, we cannot stop that :) . This was always the case, Lifecycles just makes it harder to leak it (because instead of receiving a callback, you receive a callback with a lifecycle so you know what you are getting).

On top of this, we have classes like LiveData that totally abstracts this handling. In the GithubBrowser demo in this repo, you can see that the Repositories return LiveData's. They don't keep a reference to the LiveData objects but they could have and it would still not leak.

Unlike calling methods on onStop/onStart, LiveData handles unexpected calls as well, which is one of the major reasons for leaks. For instance, you might be observing a value on user click, and you would think that you are safe because in onStop, you are unsubscribing. You are actually not safe because onClick callback may arrive after onStop. We see lots of errors with that code. Because LiveData knows about the Lifecycle, it easily handles such subscriptions, even when they happen after onStop. So it is a lot more bullet proof then custom onStart / onStop implementation.

About the loss of encapsulation argument, I don't agree with that either. The UI does not need to know when the PostManager should be stopped or when it should work. It only cares about a particular data and if data is there it reacts. On the argument of not knowing data is loading, it is as simple as fixing the object it is dispatching and make it a value object that includes status (the same way the Resource class works in the GithubBrowser app). With that, you've completely de-coupled the UI from the business, can easily mock that LiveData etc.
Jake has nice talk on this topic: http://jakewharton.com/the-state-of-managing-state-with-rxjava/

Making global classes controlled by non-permanent UI Controllers seems like a very bad argument to me.

Btw, to be clear, LiveData does not solve every single use case. e.g. the one here, about responding to changes, is not easy to implement (unless you create your own LiveData). We will probably create more components like it but LiveData is the 80% of the use cases so we prefer to focus on that and see usage patterns before making more stuff on top of it.

All 7 comments

Now I saw that LifecycleObserver's methods can receive the LifecycleOwner that generated the lifecycle event. Looks like the scenario of multiple observed lifecycles was considered.

I think that it can be possible to hold a mapping from LifecycleOwner to its respective callback, and use this information in lifecycle methods.

However, it is just my guess. Since it looks like this scenario was at least considered, it would be best to see how the authors intended this to work...

If your application global class (UserStateManager) provides its data in form of a LiveData, the Activity can observe it with its lifecycle and you won't need to cleanup manually.

Yigit, thanks for your reply.

I see what you mean - using LiveData as a proxy between global object and its observers can be very convenient in some cases.

However, please note the signature of onUserStateChanged(UserState) method in the code I posted - it returns boolean (the semantics of this return value is not important). By using non-void callbacks, the global object can not only notify its observers, but also receive data from them upon notification. To my best understanding, this flow can't be implemented using LiveData (correct me if I'm wrong).

Additional limitation of LiveData is that it restricts all observers to receive notifications when started. To my best understanding, there is no way to make some of the observers receive notifications only when resumed.

Following your comment it occurred to me that LiveData by itself is an example of a single lifecycle-aware object that works with multiple lifecycles, therefore I read its source once again. Unfortunately, I find this code a bit too complicated to be considered a reference implementation.

Therefore, I think that this issue shouldn't be closed yet.

Yes, LiveData is just observable, you cannot return a value from observers.
You could technically pass a callback where the observer can call to do something, thought that looks quite ugly. Something like:

LiveData<Pair<UserState, Callback>>
where Callback has `doSomething(boolean)`

LiveData limits to onStart because there is hardly ever a case where you want resume instead of observe and choosing one simplifies it for the developer.

LiveData should does not depend on any private APIs (besides the ObservableSet which allows iteration w/ modifications but that is a choice that you don't have to follow in a custom class).

I'm not sure what is extra complex about LiveData. It manages multiple lifecycles and observers (so there is an inherited complexity) so if you have a concrete recommendation, we are happy to consider changing it.

I don't have recommendations about LiveData - it pretty much does what it needs to do.

I'm interested in Lifecycles and global objects because I'm trying to understand all limitations and complexities related to Lifecycle.

From the official Lifecycle documentation:

Observer methods can receive zero or one argument. If used, the first argument must be of type LifecycleOwner. Methods annotated with ON_ANY can receive the second argument, which must be of type Lifecycle.Event.

And then:

These additional parameters are provided to allow you to conveniently observe multiple providers and events without tracking them manually.

However, even implementation of LifeData does not use this approach. Instead, it bundles LifecycleOwner and LifecycleObserver into a single wrapper class, and stores a list of such wrappers.

In addition, the example of how to use Lifecycle from Handling Lifecycles official page is kind of trivial, and would become better if refactored to use LiveData instead.

If multiple lifecycle aware component's internal implementation is inherently very complex (as LiveData is), then I would say that developers should avoid implementing such components. The alternative of using the standard Observer design pattern is much simpler and less error-prone.

In addition, I also believe that implementation of custom lifecycle aware components increases the complexity of the design and introduces semantic coupling between unrelated application components. These thoughts summarized here.

Now, if even the official example is better off using LiveData, and the complexity and limitations of observing multiple lifecycles are enormous, maybe it is a sign that developers should be discouraged from using Lifecycle manually?

What do you think?

@yigit @techyourchance
Can't wait to see your considerations, Yigit, about Vasiliy's article where he summarizes thoughts (the post before).
Could you maybe comment on those?

there was a good conversation on reddit when this was post there but I cannot find it.

tl;dr; you can use anything wrong, that does not make it bad or wrong. In fact, in this case, it is really hard to mis-use these because the receiver knows it is a Lifecycle.

The example mentions keeping a reference to a lifecycle knowing that it is a lifecycle. If developer wants to shoot themselves on the foot, we cannot stop that :) . This was always the case, Lifecycles just makes it harder to leak it (because instead of receiving a callback, you receive a callback with a lifecycle so you know what you are getting).

On top of this, we have classes like LiveData that totally abstracts this handling. In the GithubBrowser demo in this repo, you can see that the Repositories return LiveData's. They don't keep a reference to the LiveData objects but they could have and it would still not leak.

Unlike calling methods on onStop/onStart, LiveData handles unexpected calls as well, which is one of the major reasons for leaks. For instance, you might be observing a value on user click, and you would think that you are safe because in onStop, you are unsubscribing. You are actually not safe because onClick callback may arrive after onStop. We see lots of errors with that code. Because LiveData knows about the Lifecycle, it easily handles such subscriptions, even when they happen after onStop. So it is a lot more bullet proof then custom onStart / onStop implementation.

About the loss of encapsulation argument, I don't agree with that either. The UI does not need to know when the PostManager should be stopped or when it should work. It only cares about a particular data and if data is there it reacts. On the argument of not knowing data is loading, it is as simple as fixing the object it is dispatching and make it a value object that includes status (the same way the Resource class works in the GithubBrowser app). With that, you've completely de-coupled the UI from the business, can easily mock that LiveData etc.
Jake has nice talk on this topic: http://jakewharton.com/the-state-of-managing-state-with-rxjava/

Making global classes controlled by non-permanent UI Controllers seems like a very bad argument to me.

Btw, to be clear, LiveData does not solve every single use case. e.g. the one here, about responding to changes, is not easy to implement (unless you create your own LiveData). We will probably create more components like it but LiveData is the 80% of the use cases so we prefer to focus on that and see usage patterns before making more stuff on top of it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ShahakM picture ShahakM  路  13Comments

hasnain-ahmad picture hasnain-ahmad  路  16Comments

iammert picture iammert  路  14Comments

easyandroid-cc picture easyandroid-cc  路  13Comments

fabioCollini picture fabioCollini  路  84Comments