Abandoning the Activity Stack Using Flow/Mortar 0.12 in Android (Part 1)

DZone 's Guide to

Abandoning the Activity Stack Using Flow/Mortar 0.12 in Android (Part 1)

The Android Framework offers an Activity in your UI. Here's a quick guide to abandoning the Activity stack with Flow/Mortar in 0.12, with what to do, and why you'd want to pursue​ this action.

· Mobile Zone ·
Free Resource

What the Android Framework Gives You

As you most likely know if you've stumbled here, the Android Framework gives you "Activity" to show your UI. In an Activity, you call `setContentView(R.layout.something)` to inflate a view/viewgroup hierarchy based on the specified layout XML that describes the root, and the views/viewgroups within.

What Activities also know is that as "Activity" contexts, they are allowed to ask the system to create other instances of Activities to show a different set of views, using Intents. Intents can be given parameters - primitives, serializables and parcelables.

The Activities that are left in the background might get destroyed by the system to reclaim resources, and are definitely destroyed and recreated in the case of configuration changes and then navigating back. As in; if you rotate your screen in Activity C, then you navigate back to Activity B, then your Activity B is recreated. The recreated Activity has access to its previous state from `onSaveInstanceState(Bundle)`, and accessing that Bundle in either `onCreate(Bundle)` or `onRestoreInstanceState(Bundle)` (which runs afterwards).

What's also worth noting is that your stack of Activities is preserved along with the Intents that were used to create them even during "Process Death", which means that when Android is low on memory, it mercilessly murders your application and recreated anew, but from the Activity that you put in background, and reloads the state of the Activity like during any "configuration change".

So What Does it Give You, Again? TL;DR for Me, Please!

  1. The Activity BACKSTACK that is persisted across configuration changes and process deaths (which Activity is on the screen, and which ones you came from previously).

  2. The ability to provide PARAMETERS to the new Activity (Intent parameters, or Arguments bundle in case of Fragments), and these parameters being retained through configuration changes and process deaths.

  3. A callback that allows you to SAVE and RESTORE your STATE in case your Activity is being reconstructed (configuration change, process death).

  4. The PRESERVATION of VIEWSTATE of the views that are displayed in your Activity automatically along with your Activity state (the result of saveHierarchyState()).

  5. The ability to specify ANIMATIONS between activities using `overridePendingTransition()`.

What's the Catch?

Not sure if you've noticed, but in some cases, the instantiation of Activities is... slow. Very slow. It's actually quite ridiculous if you have a splash screen up, you start the next activity, but before the next activity is shown, you get 3 seconds of wait for no real reason with the screen being black.

Sure, Fragments are faster, but then they give you weird IllegalStateExceptions every now and then (especially DialogFragments, ugh!)

So What can You Do?

Well, you can replace the whole system by turning your application into a Single-Activity setup, where: 

  • you use Custom Viewgroups in place of Activities, and have a backstack of these using Flow

  • you use Paths in place of Intents

  • you can use Mortar's ViewPresenters to use a state persistance bundle for your Custom Viewgroups

  • you can store the ViewState within your Paths (which are by default serialized into the History stack maintained by Flow)

  • you use View animations manually for the animation between your custom viewgroups.

That's...Interesting. So What are You Doing, Exactly?

As you probably figured out by now, the Mortar/Flow setup consists of 3+1 components.

- Flow: provides the activity backstack through the History stack. It's also responsible for serializing Paths to preserve the chain, along with the state preserved within the Path (normally just its parameters, but we'll also use this to store the presenter state in the history for surviving process deaths).

- Flow-Path: provides the context chain for which Custom Viewgroup still exists and which no longer exists. This is required to maintain which Mortar scope is meant to exist and which should be destroyed. It also handles saving the viewstate with the help of the Path Container, and paths also act as the new Intent mechanism as its parameters are serialized into the History stack.

- Mortar: provides the ability to persist Custom Viewgroup's state into Bundle instead of BaseSavedState, and also provides scopes that survive configuration changes.

- Dagger2: provides a Component that will provide a scoped instance of the presenter, then we will throw this Component into Mortar's scoping system to provide dependencies directly through the Path.

Show Code Instead. You Can't Have an Article Without Code!

Well, instead of the code, first I'll give you a warning.

This guide is about Flow 0.12 and Flow-Path 0.12. It is important to know that you mustn't directly use these libraries from the jcenter() or mavenCentral() repositories by this version, because Flow-Path contains a horrible breaking bug that I managed to figure out and fix. So if you use these libraries, use your own fork and apply the fix I specified in this particular Github issue.

By default, Flow-Path 0.12 has a bug, which is that if you navigate forward, your previous mortar scopes are destroyed, and only the current one is kept. This is due to a bug in how the elements() of the path's PathContext chain were not maintained whatsoever, and the PathContainer implementation I provide fixes that.

This means your presenter state was completely destroyed and your viewstate was lost just by navigating FORWARD. If you stepped backwards, your views had lost all state. Quite a breaking bug, no? Well, it's fixed now, so no worries, just make sure you use either the fix above, or the code from my repository. Speaking of which...

Code. Now.

Okay, okay! The previous version of my setup was here and showed how to set up the activity but it's quite outdated, so the new version is right here in my Github repository.

How does it work? 

Well, it's getting late and I'm not sure I can really stick around to explain that at the moment. Curses!

You can't be Serious. Explain.

Okay fine, I'll explain it really quickly!

  • The MainActivity is responsible for the management and initial configuration of Flow, and the CustomApplication along with MainActivity is responsible for the setup of Mortar to create the mortar scopes that can preserve Dagger components across configuration changes, thus not having to recreate your dependencies from scratch each time to rotate the screen.

  • The BasePath class is a Parcelable which is responsible for specifying the layout inflated by the SimplePathContainer, storing the state at which the application is currently in (which custom viewgroup is visible), and also has the view-scoped presenter injected within, thus allowing the Activity to communicate with the inflated view group through the presenter. This is important to store the Presenter State along with the Path's own parameters into the Flow History Stack. Please note that the BasePath has a Mortar Scope associated with it created in the ScreenScoper, which is defined by the TAG that must be unique across Path instances within the History stack.

  • Each Path has 5 classes associated with them. The Path itself, the Custom Viewgroup that contains the views, the ViewPresenter which can save the state of the Custom Viewgroup, a Dagger2 module that provides the view-scoped presenter instance, and the Dagger2 component that creates the view-scoped presenter based on the module (and then the component is bound into the mortar scope associated with the path, thus preserving presenter state across configuration change). The Dagger2 component inherits all application-level dependencies.

  • To change the views, you just need access to Flow (typically using the view's context), and then set the path manually

Flow.get(getContext()).set(new WorldPath());

But if you need to clear the whole thing, you can just set a new History.

                                        .push(new HelloPath())
  • The animation is provided in the SimplePathContainer class, it was taken directly from Square's samples.

Anything Else You Want to Add...?

Yes, if you want to see a master-detail flow, you'll have to see the previously removed flow-sample made by Square, which luckily you can also find on my Github page. I think it's interesting, albeit a bit confusing, that's for certain.

If you feel like something is missing from this explanation, leave a comment here or on Reddit! I'll make sure it'll be part of the next article if I get to writing it.

activity, android, flow, fragments, mortar

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}