Gabor Varadi

@Zhuinden

ELI5: What does Square’s Flow library do?

Flow (or my fork of it, Flowless) is a backstack. Well, technically it is a thing that holds the backstack, which is called History.

History is — you can guess it — the history of the application at a given time. This History is actually just a list of objects — albeit its indexing is inverted, where get(0) returns the last inserted element, and get(size-1) returns the first inserted element. A stack, if you will.

A  ← top element, .get(0)
-
B
-
C
-
D

As list:

[D,C,B,A]

In this list, the object D, C, B, A is just a simple instance of a class with some data fields and hashCode/equals methods; also called a “value object”. It’s just a class that looks like this though

public class A {
private final String data;

public A(String data) {
this.data = data;
}

@Override
public boolean equals(Object object) { // auto-generated
if(object == null) {
return false;
}
if(!(object instanceof A)) {
return false;
}
return StringUtils.equals(this, data, ((A) object).data);
}

@Override
public int hashCode() { // auto-generated
return getClass().hashCode() + 31 * (null == data ? 0 : data.hashCode());
}
}

So yeah, this A totally simple object that just contains a String and overrides equals/hashCode is called a Key. This is what represents where you are in your application.

In the case of Activities, this would mean that you have the following activities:

DActivity, CActivity, BActivity, AActivity

and DActivity, CActivity, BActivity would be in the background in onStop (but not destroyed), while AActivity is in the front (with onStart() and onResume() called).

When you start AActivity in BActivity, what happens is that BActivity is put to onPause(), AActivity boots up and is placed in… actually, let me grab the documentation

Activity A’s onPause() method executes.
Activity B’s onCreate(), onStart(), and onResume() methods execute in sequence. (Activity B now has user focus.)
Then, if Activity A is no longer visible on screen, its onStop() method executes.

But in this case, I had to:

1.) check the documentation to see what’s going on

2.) the backstack is implicit, so getting from AActivity to CActivity immediately is very very difficult. You need to tinker with CLEAR_TOP intent flag and hope it actually works. If it doesn’t, you might even set singleTop launchMode on your Activity to finally make it work, somehow.

— — — — -

Now in the case of Flow, this isn’t so complicated. When you call

Flow.get(this).set(A.create());

Then what you receive is called a “traversal” that is “dispatched”. Simply put, it tells you that something happened, and shows where you previously were in your application: [D,C,B], and where you are heading: [D,C,B,A].

If you were in [D,C,B,A] and you call Flow.get(this).set(C.create());, then you will get a traversal that says you were in [D,C,B,A], and you’re going to [D,C].

What happens (the stuff I grabbed from the documentation) is completely up to you to write, so you are in direct control of what happens.

This generally means that what you end up doing is:

- check if you’re still in the exact same state, if yes, then don’t do anything (tell Flow that you’ve handled the traversal)
- persist the state of the current layout
- remove current layout
- inflate a new layout
- restore the state of the new layout (if state exists)
- add new layout
- tell Flow that you’ve handled the traversal

(here’s an example for a simple dispatcher)

— — — — — —

This is actually great for multiple reasons:

1.) you don’t end up with views in the background in onPause() that are not destroyed, so you don’t have to ensure that you “reload the data and refresh” when you navigate back

2.) the views are destroyed, thus freeing up memory, which is more performant

3.) activity transitions have additional overhead, which makes them slower than view transitions (also, TransitionEverywhere works on Views, but not on Activities/Fragments)

4.) now that you have direct access to “where you are in the app”, but also “what happens when I go from here to there”, it’s actually pretty easy to set up a traversal like so:

[D,C,B,A] => [C,E,B]

Because it’s literally just

Flow.get(this).setHistory(
History.newBuilder()
.push(C.create())
.push(E.create())
.push(B.create())
.build()
);

How do you intend to do that with the Activity stack and intent flags? No damn clue!

— — — — — — — — — — — — — — — — — — — — — — —

— — — — — — — — — — — — — — — — — — — — — — —

TL;DR: Flow is a backstack library that allows you to easily persist/restore the view’s state, and allows you to handle changes between where you are at a given moment, and where you are heading. That’s pretty much all it does.

(Reddit discussion thread: https://www.reddit.com/r/androiddev/comments/5h0er6/eli5_what_does_squares_flow_library_do/ )

More by Gabor Varadi

Topics of interest

More Related Stories