NOTE: I’ve deprecated Flowless (and have abandoned using Flow) in favor of what I wrote based on that from scratch: Simple-Stack.
So you should keep in mind that I am no longer using this, because Simple-Stack solves quirks that I didn’t like about Flow’s design.
— — — — — — — — — — — — — — — — — — — — — — — — —
This is a follow-up article to Saying No To Fragments and Activities: Creating View-driven Applications with Flow.
While I claimed that that article will get to the point, it was more-so a comparison of how things are lately with Flow 1.0-alpha2 (and Flowless), versus how things used to be with Flow 0.8. So one could argue that it did not get straight to the point, after all.
So let’s get to it, shall we?
Flow: a backstack manager
As stated in a previous article, the primary task of Flow (and the
Flow instance itself) is to manage the backstack. All it really cares about is the
History (essentially a
List<Object>), and exposes operators that allow manipulating it.
Keys: representation of application state
The primary difference using Flow compared to just Activities is that instead of having an implicit backstack created by a series of Intents that open a bunch of Activities, you have a History of “key” objects that are just general Parcelable objects with
equals() that represent what view you’re supposed to be showing.
(In fact, it doesn’t have to be a view, if you’re a heretic, you can even use Fragments with it. No, really; just use the newly added
FragmentManager.commitNow() , and don’t use
addToBackStack(), defer backstack manipulation to Flow instead. Theoretically, it should work, although I must admit I haven’t tried it yet.)
A possible example key would be the following:
But if you don’t like extracting values from annotations, then you can just use interfaces and method calls. It’s up to you!
This allows simple initialization for what your application state should be.
And it also allows you to easily set whatever view you want to test with instrumentation tests.
You might be thinking, “wait, but this is a whole bunch of methods in the key, I thought this was going to make my life simpler?!”
Actually, it does. Exactly because this kind of “behavior” that is encoded here in the key used to be thrown randomly into Fragments, that were manipulating the Toolbar title in the Activity directly.
Here this is something you can globally do in your dispatcher implementation, and it’s set properly on back press too with minimal effort (in fact, it works out of the box).
Custom Views, and listening to the lifecycle
This is a Flowless-specific feature, partly piggybacking on top of the
InternalLifecycleIntegration fragment in the original Flow.
You generally need to know when your View is ready and its state is restored. You must also know when your View is killed, so that you can unsubscribe your subscriptions, or unregister from event buses.
Previous Flow examples used to show
onDetachedFromWindow as the callbacks to be used, but that is actually unreliable. There are cases when
onFinishInflate() is called, but
onAttachedToWindow()is not. In that case, you do not receive any callback to
onDetachedFromWindow(), which means that you can lose your state!
If you’re curious, this is most likely why the square/coordinators library exists.
Instead, Flowless introduces the
FlowLifecycles.ViewLifecycleListener interface, which if you implement, then provides you the callbacks:
Additional lifecycle callbacks
In other cases such as writing a
CameraView, you might need to listen to permission results, or activity results.
onPermissionResult, you need to delegate these callbacks to the Dispatcher manually.
A possible base activity that hides this from you would be the following:
Once the permission result delegation is added, the dispatcher forwards this event to the current active view.
This way, you can handle any lifecycle event callback you need in your view.
ServiceProvider: sharing services across the view hierarchy
This is a new class in Flowless, its counterpart used to be
Flow.Services.with its corresponding
ServiceProvider is pretty much just a
Map<Object, Map<String, Object>> that is stored inside Flow’s
InternalLifecycleIntegration to preserve them across configuration change, very similar to Mortar which is essentially a
Map<String, Map<String, Object>>. The initial key in this case of course is the
Key itself, and not a “scope name” specified as a String.
The difference is that in the original Flow 1.0-alpha2, the
Flow.Services were managed internally based on a reference count, based on interfaces such as
MultiKey. This could cause problems even if you weren’t using it, which is what caused the crash that made me fork Flow and create Flowless in the first place.
Instead, in Flowless, the
ServiceProvider expects you to set it up in the Dispatcher however you see fit.
Sharing a Dagger2 component
In the MVP example, this is how scoped components are provided down the view hierarchy. This allows us to obtain the Dagger2 component from any context — primarily within views of the custom view’s view hierarchy.
Dispatcher: which determines what happens on state change
The last and most important component of a Flow-based application is its Dispatcher implementation. This is what globally handles what should happen when you go from Key A to Key B.
The most typical implementation would persist the state of the previous view, inflate the new view, restore state to the new view, remove the previous view, add the new view, and callback to signal completion.
The following example also adds the Dagger2 component by
DaggerService.TAG and associate it with the given key, making the component accessible within the view hierarchy.
In the middle, you can see the Service being set up for the current key: the Dagger2 Component.
This is basically where the “magic” happens. In this case, it’s 60 lines with comments, compared to the FragmentManager that is 2000 lines. I guess there’s a win there in simplicity.
Hopefully this helped understand how to use Flowless, and how to create a simple MVP-based (or maybe even MVVM-based with Data-Binding or RxJava!) application without using fragments or any kind of additional “arcane folklore” beyond an explicit backstack.
The app is driven with just simple parcelable POJOs, and you write how to switch between the states only once. Simpler than either
The example cited multiple times during this article is available here. Flow is primarily responsible for the way the
presentation layer works.