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
StateChangerimplementation 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
Navigating between views with the Backstack
In order to navigate from
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
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!
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?
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() trick not from an Activity, but from a custom
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
With this solution, we can send any Parcelable data to our custom viewgroup, and its entire view hierarchy.
Most of this
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,
Also, as previously mentioned:
- state changes can still not be queued while another state change is in progress
- state changes can still occur after
- view state is still not persisted
These are for another time. For now, the source code is available here.