Conductor: Thoughts on Conductor and the new Android architecture components

Created on 19 May 2017  Â·  25Comments  Â·  Source: bluelinelabs/Conductor

Hi,

Wondering what your thoughts are on the introduction of the new architecture components introduced by Google at I/O and how they would/could interact with Conductor? Especially the LifeCycle and ViewModel approaches.

I don't view them as being orthogonal to the Conductor offerings, but it would be interesting to have your take on how it would make sense to use them with conductor (if at all).

Most helpful comment

An experimental architecture components module has been added in the latest snapshot. Use compile 'com.bluelinelabs:conductor-arch-components-lifecycle:0.1.0-alpha1' to grab it. Like the new architecture components themselves, this will likely be evolving quite a bit from here. A demo of how this works is available in the demo app.

The mapping between Controller and Lifecycle is currently (subject to change):

constructor -> onCreate()
onCreateView() -> onStart()
onAttach() -> onResume()
onDetach() -> onPause()
onDestroyView() -> onStop()
onDestroy() -> onDestroy()

All 25 comments

Lifecycle was written to be disappointingly heavily tied to Activities and Fragments. The events available are the ones used in the lifecycles of those components (ON_CREATE, ON_START, ON_RESUME, etc). States also split out INITIALIZED and CREATED, which doesn't make sense in Conductor. I don't know why they had to do it this way, but they did.

LiveData is heavily tied to Lifecycle, so without Lifecycle, this isn't useful.

From what I've seen so far, ViewModel should work out of the box. I am, however, currently overseas with terrible WiFi and haven't been able to actually get my hands on it to try things out yet. Booo.

For now the options I see for using the new arch components with Conductor would be:

  • Do nothing. Use RxJava instead of LiveData and either RxLifecycle or AutoDispose instead of Lifecycle. Honestly I'd prefer Rx over LiveData anyway, so I don't see this as a bad thing at all.
  • Write something from scratch that uses all the same ideas, since Lifecycle is so heavily tied to other components.
  • Try to jury-rig Lifecycle into Conductor by mimicking the different states that would be called (ex: onAttach emits both the onStart and onResume events, onDetach emits both onPause and onStop).

Option 2 is not appealing to me at all. I believe that option 1 makes the most sense, at least for now. Definitely open to what others think about this though!

Your summary makes sense to me but there might be problem it the future when most of the classes like Location manager etc are actually lifecycle aware but we will have to manage everything by hand inside conductor compared to Fragment/Activities. If this will become a real standard for even third party lib to support lifecycle I would look at your option 3 again.

I'm curious about this as well. I took a cursory glance.

android.arch.lifecycle.Lifecycle is an abstract class, and while it's obviously designed with Activity & Fragment in mind, it wouldn't be too difficult to make ControllerLifecycle or something, would it? It says a single method can handle multiple annotations. We really only need attach & detach, right?

I might be missing something, so correct me if I'm wrong.

I would prefer option#3 then Conductor Controller components works with parity with Fragments

An experimental architecture components module has been added in the latest snapshot. Use compile 'com.bluelinelabs:conductor-arch-components-lifecycle:0.1.0-alpha1' to grab it. Like the new architecture components themselves, this will likely be evolving quite a bit from here. A demo of how this works is available in the demo app.

The mapping between Controller and Lifecycle is currently (subject to change):

constructor -> onCreate()
onCreateView() -> onStart()
onAttach() -> onResume()
onDetach() -> onPause()
onDestroyView() -> onStop()
onDestroy() -> onDestroy()

And what about ViewModel? Is there a way to get it for Controller?

Nothing special is needed for ViewModel. Just use it.

On Fri, Aug 11, 2017, 5:28 AM Yuriy Nalivayko notifications@github.com
wrote:

And what about ViewModel? Is there a way to get it for Controller?

—
You are receiving this because you modified the open/close state.

Reply to this email directly, view it on GitHub
https://github.com/bluelinelabs/Conductor/issues/302#issuecomment-321779022,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AB8c8nh8Bhw_Lk8QYiV9Xxj4xUt2vVnbks5sXCzOgaJpZM4NgJ0J
.

Viewmodels are tied to the activity/fragment. So we might be having a singleton viewmodel for single activity apps.

@gumil With the experimental branch, you can pass a Controller in when getting your ViewModel in place of an activity/fragment.

Not sure if the latest comments are outdated or I'm missing something, but ViewModel are created like this: vm = ViewModelProviders.of(this).get(AccountViewModel.class);

This method ViewModelProviders.of() only takes a fragment or an activity.

How would you use recommend passing the controller @duckinferno ?

Do we need to create a ViewModelStores so then we can create a ViewModelProvider for controllers?

Why do you want to use ViewModels from Architecture Components? Is there
really a reason why to use them? I mean, Controllers already survive
configuration changes, so does any instance variable referenced from
Controller. so what's the point of using Architecture Component's ViewModel
with Conductor?

On Mi., 18. Okt. 2017, 00:21 Quentin Colle notifications@github.com wrote:

Not sure if the latest comments are outdated or I'm missing something, but
ViewModel are created like this: vm =
ViewModelProviders.of(this).get(AccountViewModel.class);

This method ViewModelProviders.of() only takes a fragment or an activity.

How would you use recommend passing the controller @duckinferno
https://github.com/duckinferno ?

Do we need to create a ViewModelStores so then we can create a
ViewModelProvider for controllers?

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/bluelinelabs/Conductor/issues/302#issuecomment-337391952,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAjnrhs4neTMODfg9kB0w-l5s3GaT6uyks5stShpgaJpZM4NgJ0J
.

@quentin41500 it takes a LifecycleOwner (interface), Controller can simply implement the interface. The experimental branch implements this.

@sockeqwe In MVP or MVVM, Controller is the View. It's allowed to be sensitive to lifecycle events, such as onStop causing the view to detach (ew). ViewModel (in conjunction with LiveData) is separate from all that lifecycle nonsense, and lets you easily maintain data for a View without having to juggle its state. If Controller implemented LifecycleOwner, it can attach to a ViewModel as well, affording the same conveniences.

I think the benefit is easiest to understand if you consider a Controller to be a replacement for a Fragment.

Im totally aware of that an Im not saying that you shouldnt use MVVM and
put everything in Controller. Im just wondering why one needs
ViewModelProviders since the main reason you would use this is to make your
ViewModel survive config changes, which a Conductor Controller is already
doing.
Regarding Lifecycle owner: it's super easy to write a class that uses
Cobductor's Lifecycle Listener to implement a Lifecycle Owner class. Then
you could use it like this:

class MyController : Controller{
private val lifecycleOwner = Conductor LifecycleBridge(this)

override fun onCreateView(){
val vm = MyViewModel(....)
vm.data // some live data
.subscribe(lifecycleOwner) {
// Update UI
}
}
}

On Mi., 18. Okt. 2017, 00:46 Thomas Norman notifications@github.com wrote:

@quentin41500 https://github.com/quentin41500 it takes a LifecycleOwner
(interface), Controller can simply implement the interface. The
experimental branch implements this.

@sockeqwe https://github.com/sockeqwe In MVP or MVVM, Controller is the
View. It's allowed to be sensitive to lifecycle events, such as onStop
causing the view to detach (ew). ViewModel (in conjunction with LiveData)
is separate from all that lifecycle nonsense, and lets you easily maintain
data for a View without having to juggle its state. If Controller
implemented LifecycleOwner, it can attach to a ViewModel as well,
affording the same conveniences.

—
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/bluelinelabs/Conductor/issues/302#issuecomment-337398368,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAjnritF9ubKuDV-e9fsVqgMwA6b-Wvxks5stS48gaJpZM4NgJ0J
.

Yep, I was looking to use ViewModel as a layer to fetch data but I definitely don't need to retain them. Thanks for jumping in on this.

@EricKuck
I created a pull request related to this topic: Improved lifecycle integration of Conductor and Architecture Components

Basically I would suggest to use Conductor's new onContextAvailable() callback to align its lifecycle more closely to Google's Architecture Components Lifecycle.

Previous mapping was:

  • () -> ON_CREATE -> CREATED
  • preCreateView -> ON_START -> STARTED
  • preAttach -> ON_RESUME -> RESUMED
  • preDetach -> ON_PAUSE -> STARTED
  • preDestroyView -> ON_STOP -> CREATED
  • preDestroy -> ON_DESTROY -> DESTROYED

Proposed mapping would be:

  • () -> INITIALIZED
  • postContextAvailable -> ON_CREATE -> CREATED
  • postCreateView -> ON_START -> STARTED
  • postAttach -> ON_RESUME -> RESUMED
  • preDetach -> ON_PAUSE -> STARTED
  • preDestroyView -> ON_STOP -> CREATED
  • _preContextUnavailable -> // do nothing_
  • preDestroy -> ON_DESTROY -> DESTROYED

Apart from that, I updated to the latest android.arch.lifecycle:runtime version, made some simplifications to ControllerLifecycleOwner (former ControllerLifecycleRegistryOwner) and updated the related demo.

What do you think?

Regarding ViewModelProviders.of(): I've read the related comments above from @sockeqwe, @quentin41500 and @duckinferno. Basically you're saying that in case of Conductor we don't need ViewModelProviders at all ...

... since the main reason you would use this is to make your
ViewModel survive config changes, which a Conductor Controller is already
doing.

Okay. But apart from survival of config changes ViewModelProviders enables two more things:

  1. ViewModelProviders serves as a kind of global registry for all the ViewModels in my app. I can access it statically and then use/share the same ViewModel accross different places (The view model should probably be bound to a common parent context and only be shared across child contexts in order to prevent memory leaks).

  2. ViewModelProviders indirectly invokes ViewModel.onCleared() for all ViewModels that are grouped under the owner I pass to ViewModelProviders.of() when the latter gets destroyed. Afterwards it removes the ViewModel from the registry which should leave it for GC.

In my opinion (1) and (2) would still be useful when using conductor. What do you think?

Unfortunately, in 1.0.0 stable ViewModelProviders.of() only accepts android.support.v4.app.Fragment and android.support.v4.app.FragmentActivity. Can anyone explain me why they don't accept android.arch.lifecycle.LifecycleOwner (implemented by controller)?

@duckinferno mentioned an "experimental brach" where this apparently is possible. Where can I find this branch? Is this something that will come in the future?

If I directly use a ViewModel as a member of my controller, I should probably call it's onCleared() method in the controller's onDestroy(). However, onCleared() is protected in ViewModel and needs to be publically overridden (eg. forced by my own IViewModel interface) in order to call it from my controller.

In the end I think ViewModel is only useful for conductor together with ViewModelProviders (which is currently not possible, see above). Otherwise I could just use any other plain Java wrapper class for my LiveData. Or did I miss something?

ViewModel uses retained Fragment internally to survive configuration change(basically same as what Conductor is doing behind the scenes).
So it needs FragmentActivity or Support Fragment.

@stephanschuster Disregard most of what I've said in this thread, I was wrong. :)

I fully agree re: the usefulness of ViewModelProviders for the reasons already described, particularly its ability to be shared between contexts. I wonder if conductor could expose a provider backed by its retained fragment? [actually, doesn't look like conductor uses a retained fragment?]

Regardless I think that integrating Conductor with google's arch components should be made a priority, ViewModel+LiveData in the style of MVVM removes so much headache dealing with lifecycle stuff, Controller has fallen behind Fragment in this area (didn't know I'd ever say that!).

This appears to work fine from a Controller:

val viewModel by lazy { ViewModelProviders.of(activity as AppCompatActivity).get(MyViewModel::class.java) }

It keeps a strong reference to the ViewModel, but the ViewModel is expected to survive config changes etc, and Activity itself is expected to be able to have a strong reference to a ViewModel, so I believe this should be safe. The Activity context is only used to fetch a ViewModelProvider and is not retained, so is also safe.

Using the experimental branch, this + LiveData appears to work almost identically is if used with an Activity. The only difference I've noted from testing is that the controller continues to receive LiveData emissions after the Activity is stopped (in contrast to an Activity's LiveData which would stop receiving emissions until started again), but the view is still attached so it's not a deal breaker. Other lifecycle events are handled fine (config changes etc).

Thoughts?

@yshrsmz:

ViewModel uses retained Fragment internally to survive configuration change(basically same as what Conductor is doing behind the scenes).

I know. But in my opinion this should be an implementation detail and nothing an app developer should care about or even notice. In fact, if Google provides something like a LifecycleOwner interface which anybody can implement (e.g. Conductor's controller), then this should be accepted in ViewModelProviders.of(). Again, the fact that this interface (or the lifecycle underneath) is backed by a retained fragment should not matter.

@duckinferno:

I wonder if conductor could expose a provider backed by its retained fragment?

Even though this would work, I think this is not a good idea since it would expose an implementation detail (see above). Furthermore, all ViewModels would be bound to Controller's retained LifecycleHandler-Fragment. What we actually want is a global view model registry where each view model is bound to the lifecycle of a specific controller. Exactly what we would get if ViewModelProviders.of() accepted LifecycleOwner. Then we could have one ParentViewModel bound to the ParentController but also used in ChildController1 and ChildController2 each of which additionally have their own view models bound to their own lifecycle.

Of course, we could more or less easily implement ViewModelProviders ourselves, but this seems a bit redundant to me (especially as some underlying classes like ViewModelStore cannot be reused for Conductor as they have some package protected methods we need to access).

Another approach would be to have no global registry but use an IOC container (e.g. toothpick) to provide and inject scoped ViewModels. Is that preferable over a global registry?

@duckinferno

ViewModel+LiveData in the style of MVVM removes so much headache dealing with lifecycle stuff, Controller has fallen behind Fragment in this area (didn't know I'd ever say that!).

Google Architecture Components are surely a step foward in PLAIN app development. But if you already separate the data you push to the view via a Conductor controller (like I usually did so far), what is the real big advantage of LiveData and ViewModel? And what lifecycle issues are you facing with conductor that you think fragments would be better meanwhile?

ViewModelProviders.of(activity as AppCompatActivity)

In my opinion this is also not what we need or want (see above). 1. This enforces my conductor app to be based on a AppCompatActivity/FragmentActivity instead of a plain Activity (enough in my case). 2. All view models are bound to the outermost lifecycle (activity).

Using the experimental branch

WTF? You said it again. :) Which _experimental branch_ are you referring to? Can you give me a link? The rest I don't understand. Sorry.

Ugh I keep using that term. I mean the _experimental arch components module_, here.

ViewModel is currently persisted under the hood via retained fragment, which appears* to be the "single repository" - ViewModelProviders ultimately checks these retained fragments to return the appropriate ViewModel. If Conductor had an alternative way of supplying a ViewModel via its own mechanisms, how would other lifecycle components (Activity, Fragment) know to access Conductor when looking for an existing ViewModel? I think it all comes back to the design of arch components - they are built around retaining fragments and that's unlikely to change, I think Conductor has no choice but to hook into the same mechanisms.

It is a shame that the providers require a FragmentActivity, but for the same reason as above, I don't think this requirement will ever change. AppCompatActivity is mercifully ubiquitous, is there a hard reason for avoiding it or is it just for separation of concerns?

what lifecycle issues are you facing with conductor that you think fragments would be better meanwhile?

As the experimental module is currently set up, binding a LiveData to a controller does not provide 1:1 behaviour/parity with Activity/Fragment. Events will be emitted to the Observer while the Controller (or rather its underlying activity) is stopped, whereas a LiveData's Observer bound to an Activity/Fragment will not have anything delivered until the activity is started again. Not _huge_, but has the potential provide gotchas; for instance, if an event indicates that a new activity should be started, that new activity could potentially pop up while the device user is in a completely different app.

what is the real big advantage of LiveData and ViewModel?

These classes are the absolute core of arch components and serve to propel MVVM well into 'first place' amongst architectures (in my opinion). They allow you to ignore the View's (Activity/Fragment/Controller) lifecycle idiosyncracies almost entirely when designing your 'view controller', for lack of a better term. I assume you are familiar with MVP and MVVM but will briefly describe both to provide context when making my case for LiveData and ViewModel. I'm not sure how familiar you are with AC, so please don't take offense if my explanation makes assumptions.

Consider MVP: your Presenter isn't free of lifecycle concerns because it maintains a direct reference to its View, and that view is not available at all times. The Presenter therefore needs to either be subservient to the View by stopping what it's doing when the View disappears, or have boilerplate to maintain state and cache so that intermediate work can be reapplied when the View attaches.

Consider MVVM: your ViewModel (which can be thought of as the equivalent of a Presenter in MVP) controls your View, but doesn't have a direct reference to it. Instead the View observes changes to properties in ViewModel and updates itself. View maintains this observer relationship, meaning you are free to completely ignore the lifecycle inside the ViewModel; ViewModel simply reacts to events passed to it by the View + updates provided by the model, and updates its own properties regardless of the presence of a View.

LiveData is a mechanism to provide such an observer relationship without thought/boilerplate on our part. So the ViewModel can expose a LiveData, your lifecycle-sensitive View can observe it, and the view is now suddenly guaranteed only to receive observable events when the underlying context is valid. LiveData will also re-emit events when necessary, eg. after config changes have recreated the view.

AC's ViewModel is simply an android-specific implementation of a ViewModel which exists beyond the reach of the lifecycle. Not as groundbreaking as LiveData, but essential.

If Conductor had an alternative way of supplying a ViewModel via its own mechanisms, how would other lifecycle components (Activity, Fragment) know to access Conductor when looking for an existing ViewModel?

Good point, but I think outer scopes (like other activities or fragments you use in addition to conductor's single activity) should NOT have access to inner scopes (view models of nested controllers). If at all, then to the sibling scope (Conductor's single activity). That being said, I don't want an alternative ViewModelProviders mechanism for Conductor. I want to use the same as the one from AC via ViewModelProviders.of(LifecycleOwner) which can be used from activities/fragments/controllers/.... But of course this "all comes back to the design of arch components".

I'm not sure how familiar you are with AC ...

All good. I'm not too experienced in Android (of cource I've read "all" about AC) but I've 10+ years of MV(C/P/VM/whatever) experience in Java, Web and C# development. So I'm well aware of the concepts and their implementation. Anyway, thank you for your explanation. They make completely sense to me.

I'm also a big fan of true MVVM. But to apply true MVVM you need a very strong DataBinding library, otherwise you will sooner or later break the rules. From what I've seen, Android's data binding is way behind the stuff I've used in C# and Web. And even there I reached the limits. Regarding AC DataBinding is currently not even supported for LiveData, right? In short: DataBinding is not a valid option for me right now.

Currently a valid option for me would be to use Conductor's controllers (primarily because of their predictable lifecycle and nested backstacks) + AC ViewModels with LiveData, observe the changes of LiveData in the controller and push them to the view. So you get the best of both worlds, right? That's why I still don't understand when you're saying:

Controller has fallen behind Fragment

They say that DataBinding + LiveData is on the cards, but after the 1.0 release. So hopefully we'll have some stuff there. I'm just happy to have the underlying mechanisms available, it's not _too_ much boilerplate to implement LiveData observables in code and I think it's still well worth the effort given the alternatives.

Controller+VM+LiveData is absolutely how I'd do things too. I only say that Controller has fallen behind Fragment because of two reasons:

  1. the (minor) loss of parity re: LiveData's behaviour (onStop does not interrupt observation)
  2. LiveData's interaction with Activity and Fragment is so powerful that a huge swath of the arguments against Fragment are now obsolete.

Controller is still simpler to use, and has vastly superior custom animation handling, but I'm just saying that the gap has closed significantly. In terms of compatibility with AC, Fragment is ahead.

I simply consider AC to be _so strong_ that even minor trouble integrating it with other libs should be addressed as a priority. I forsee AC to be a strong part of the future of Android development and it's important to keep up in the formative stages.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

PaulWoitaschek picture PaulWoitaschek  Â·  5Comments

moezbhatti picture moezbhatti  Â·  7Comments

lenguyenthanh picture lenguyenthanh  Â·  5Comments

cbnewham picture cbnewham  Â·  8Comments

EmkaMk picture EmkaMk  Â·  10Comments