paint-brush
Coordinators: solving a problem you didn’t even know you hadby@Zhuinden
13,172 reads
13,172 reads

Coordinators: solving a problem you didn’t even know you had

by Gabor VaradiJanuary 30th, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Square has a library that most people don’t know about. It’s called <a href="https://github.com/square/coordinators" target="_blank"><strong>coordinators</strong></a>. Not a very descriptive name, and there isn’t all that much info about it on the <a href="https://hackernoon.com/tagged/github" target="_blank">Github</a> page either.

Company Mentioned

Mention Thumbnail
featured image - Coordinators: solving a problem you didn’t even know you had
Gabor Varadi HackerNoon profile picture

Square has a library that most people don’t know about. It’s called coordinators. Not a very descriptive name, and there isn’t all that much info about it on the Github page either.

Simple lifecycle for your MVWhatever on Android. No kidding.

And some example code:

class ExampleCoordinator extends Coordinator {



@Override public void attach(View view) {// Attach listeners, load state, whatever.}




@Override public void detach(View view) {// Unbind listeners, save state, go nuts.}}

And that you can install a Coordinator to any View:


// Bind a Coordinator to a View.Coordinators.bind(view, coordinatorProvider);

It really doesn’t say anything about what it is, what it does, and how it should be used, right?

Well, in order to understand this problem, we must walk through the evolution of custom viewgroups, because this is, apparently, the next step.

The evolution of displaying screens on Android

(Feel free to skip forward to the good stuff if the Activity/Fragment stuff bores you!)

Activity

Everybody knows Activities. They supposedly represent a single screen in the app, and is technically an “entry point” according to specific intent filters you can define.

In order to start a new Activity, you use an Intent, like activity.startActivity(activity, OtherActivity.class).

They also cannot be nested. Well okay, that’s not entirely true, there’s LocalActivityManager and ActivityGroup, but they’re quite deprecated since API Level 11. We may as well pretend they don’t exist.

Activities display view groups with setContentView(). People generally assumed that it only shows one particular type of viewgroup specified with its layout id (R.layout.activity_main), and then if you ever needed a different screen, you’d just make a new Activity instead.

Then once you had to put two different screens on the same “page”, all hell broke loose.

Fragments

Fragment/Activity lifecycle from https://github.com/xxv/android-lifecycle by Steve Pomeroy

Fragments are, well, “fragments” of a screen. They technically display a custom viewgroup, and are managed by the FragmentManager inside the Activity. The Fragment is associated with a FragmentController, which is associated with the “host Activity”.

They were designed primarily so that you can create “sub-screens” that function like “sub-activities”, inheriting all important lifecycle callbacks such as onCreate(), onActivityResult(), onPermissionResult(), and even more importantly onSaveInstanceState(Bundle bundle);.

They also add their own set of lifecycle callbacks, such as onAttach(), onCreateView(), onActivityCreated(), onDestroyView(), and onDetach().

You can create Fragments with getSupportFragmentManager().beginTransaction().add(R.id.container, new MyFragment()).commit();.

Most of the time though, you just need to access the Activity you’re bound to, somehow (typically done with onAttach()), and the pairs onCreateView() and onDestroyView().

That, and now with all the additional complexity, you can nest a Fragment inside a Fragment, by using the Fragment’s [getChildFragmentManager()](http://stackoverflow.com/a/13391359/2413303) — which can sometimes cause confusion in whether you need getFragmentManager() or getChildFragmentManager().

One may ask, do we really NEED all these lifecycle callbacks in our “subview” inside the Activity?

We may also ask, if we can have “subviews” in our Activity, then why create multiple Activities in the first place?

Custom Viewgroups

You know what Activities and Fragments have in common? They display Viewgroups that are already inherently nestable ( ViewGroup and View), they’re all declared in XML, and they also have some nice callbacks.

Makes you wonder why we were trying to use them to show different screens in the first place, when you can just swap out views and get the same result.

We can extend the viewgroup of our choice ( DrawerLayout, RelativeLayout, FrameLayout, LinearLayout, etc.) and use the new custom viewgroup of our choice as whatever we’re inheriting from.

Because we use layout inflation using the R.layout.custom_viewgroup to inflate the custom viewgroup, the views we bind in @OnClick or @BindView are always guaranteed to exist in onFinishInflate().

So in essence, we have something akin to onViewCreated(), onStart() and onStop() callbacks out of the box.

Instead of getSupportFragmentManager().beginTransaction().blah().commit(), we can easily just inflate, addView or removeView.

But even though we’ve made a subscreen that’s no longer directly bound to a messy blob of unnecessary callbacks, orchestrated by the arcane FragmentManagerImpl — there’s one thing that might still bother us.

The custom viewgroup is created via inflation, and it’s not a POJO. It has 4 constructors, and needs to extend an Android-specific class to work.

Coordinators

In order to detach ourselves from the confines of a Custom Viewgroup, we can move all “view controller” logic outside of the viewgroup itself, and attach it as a tag.

This is how it works:

So what happens is that you can create a Coordinator, which can be pretty much any POJO class, and can be attached to any custom viewgroup without having to extend it.

And with Coordinators, you receive a callback for attach(View)and detach(View) . in place of the original viewgroup’s onAttachedToWindow() and onDetachedFromWindow() callbacks.

This Coordinator instance is then associated with the ViewGroup by storing it as a tag. This way, the coordinator can be obtained as Coordinator coordinator = (Coordinator)getTag(R.id.coordinator), which is of course provided by Coordinators class.

What is the benefit of this? Now that our class is a POJO, we can actually inject this class directly with Dagger, without having to specify a void inject(MyCustomViewGroup group); method in the Component.

With that, we’ve obtained the following benefits:

  • we do not need to inherit from a viewgroup and define 4 constructors in order to define our own “view controller” logic
  • we can directly inject our newly made Coordinator directly from Dagger, without need for void inject(MyCoordinator coordinator)
  • we no longer need a Context to create an instance of the “view”, we can just create a Coordinator instead

Conclusion

Personally, I like the direction that Coordinators is taking. I’m not entirely sure if the onAttachedToWindow() and onDetachedFromWindow() callbacks themselves are truly sufficient, but defining an attach(View) and detach(View) method of our own in our own class, then associating it with a tag isn’t exactly difficult either.

In return, we receive a POJO view controller that can be directly created and injected via Dagger, without need of creating 4 constructors and hard-wiring the custom viewgroup in its own XML file.

The new Coordinator approach works especially well if the navigation logic is detached from Activities/Fragments, and is moved to the Presenter layer. It makes for much cleaner code — application state transition isn’t something that the views should manage, after all.

I took coordinators on a test drive in https://github.com/Zhuinden/simple-stack/tree/master/simple-stack-example-mvp.