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?
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.
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 hashCode()
and 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).
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 onAttachedToWindow
and 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: onViewRestored()
and onViewDestroyed()
.
In other cases such as writing a CameraView
, you might need to listen to permission results, or activity results.
For onActivityResult
and 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.
This is a new class in Flowless, its counterpart used to be Flow.Services
.with its corresponding ServiceFactory
. The 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 TreeKey
or 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.
Through the magic of redefining [getSystemService()](https://github.com/Zhuinden/realm-book-example/commit/619546401dcade1fbc02001e8b12d71bfcec2952)
, the ServiceProvider
allows you to obtain any service from your context, just like you can obtain Flow via Flow.get(context)
.
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.
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 FragmentTransaction
s or Intent
s.
The example cited multiple times during this article is available here. Flow is primarily responsible for the way the presentation
layer works.