Object sharing to custom views with ContextWrappers and getSystemService: Creating a Flow-like custom backstack (Part 2)

We’ve created a backstack that at first glance seems to do its job. Currently, we’d like to solve the following problem:

  • how to create a StateChanger implementation that inflates custom viewgroups associated with the current state, and provide parameters to them in a reasonable manner

Handling the state change (naively)

The first part is rather easy. Let’s just remove the current view and add the new view in its place. No view-state persistence yet, that’s for a later time. Just swap ‘em.

Now we can easily inflate the view we want to show, the FirstView — it has nothing but a button which is meant to go to SecondView.

Navigating between views with the Backstack

In order to navigate from FirstView to SecondView, we would like the following to work:

But how should we get the Backstack to the custom viewgroup?

0.) make the backstack global

If we put the Backstack into the Application class, then we’ll be able to access it with ((CustomApplication)context.getApplicationContext()).getBackstack() or via a @Singleton scoped Dagger component.

However, that doesn’t make us exempt from persisting the backstack state in an Activity’s onSaveInstanceState(). Considering the current solution aims to make the backstack Activity-scoped (similarly to having an Activity-scoped component), I won’t go into details for this particular solution (for now).

1.) set the backstack to the view in the state changer callback

With this solution, the custom viewgroup needs to implement BackstackHolder, so that the state changer can set the backstack on it.

So if we forget to implement this interface, we get a ClassCastException.

Also, any other custom viewgroup inflated inside FirstView would also not be able to easily access the backstack. A rather fragile solution.

2.) the Activity implements the interface, and the View obtains the Backstack from it

You can’t assume that the Context returned by view.getContext() is an Activity, you must scan for it. This looks a bit messy — is there a way to access something inside the Activity, without needing to manually obtain a reference to the Activity itself?

The answer is yes!

3.) redefining getSystemService() to provide the Backstack

We can skip the whole “Activity searching” thing if we use Android’s hierarchical service locator to provide our own services as well. This solution is pretty much courtesy of Square originally, but a really nice solution to this problem.

The great thing about this solution is that any inflated view will now be able to access the backstack. Even inside a view inflated by a RecyclerView’s Adapter, you can obtain the Backstack from the Context parameter.

This is typically how the Presenter, or the scoped Component can be shared across the view hierarchy without directly passing it to them. This is also how Mortar shares its services.

Providing parameters to custom viewgroups

There are times when we’d like to send parameters to our new view, similarly to placing things into intent.getExtras() — primary keys and the like. Of course, we want these parameters to persist across process death, just like any setArguments(Bundle) or Intent does.

The solution is to encode this into the state itself based on which the view is initialized. If the parameter is a Parcelable, then we are good to go.

But how are we supposed to access the state to which the given view belongs?

The getSystemService() trick won’t work out of the box, because this parameter is view-specific. So it cannot be provided by the Activity. We’ll need to use a slightly different approach.

1.) set the Key as a parameter to the View

This solution has two problems:

  • the key is not accessible before and during onFinishInflate(), only after
  • the key is not accessible by other custom viewgroups in the hierarchy (same problem as before until we used getSystemService())

2.) use getSystemService() trick not from an Activity, but from a custom ContextWrapper

What we CAN do is that we create a new ContextWrapper class. This ContextWrapper is created with the Activity being set as its base context, but with a cloned LayoutInflater — a ContextWrapper that also has a Key parameter that it can return via getSystemService().

With this solution, we can send any Parcelable data to our custom viewgroup, and its entire view hierarchy.

What’s next?

Most of this getSystemService() and ContextWrapper magic should be provided by the library itself. After all, who actually does this manually? It’d be nicer if we could just say, Backstack.get(Context) and Backstack.getKey(Context).

Also, as previously mentioned:

These are for another time. For now, the source code is available here.

Like what you read? Give Gabor Varadi a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.