• Stars
    star
    141
  • Rank 259,971 (Top 6 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created over 8 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

[DEPRECATED] Based on Flow 1.0-alpha. To keep your life simple, use zhuinden/simple-stack instead.

[DEPRECATED]

Deprecated in favor of Simple-Stack.

Due to the way Flow initializes itself, the Backstack cannot be accessed before the bootstrap traversal, and doesn't exist before onPostCreate().

That, and restoring application state from a History added to an Intent using Flow.addHistoryToIntent() is a nightmare in terms of extendability, and this is entirely because only Flow's internal classes know how to restore the backstack from an intent directly.

A migration guide (along with the motivation behind the change) is available here.

BackstackDelegate has since been replaced (for most use-cases) with Navigator.

For replacing ServiceFactory and MultiKey/TreeKey, use ScopedServices and ScopeKey/ScopeKey.Child.

Flow(less)

"Name-giving will be the foundation of our science." - Linnaeus

"Memory is the treasury and guardian of all things." - Cicero

"It's better if you're good at one thing than if you're bad at many things just because you're trying too hard. Especially if you're a backstack library." - Zhuinden

Flow(less) gives names to your Activity's UI states, navigates between them, and remembers where it's been.

This used to be a fork of Flow 1.0-alpha by Square, with the "resource management" aspect removed. Now it provides more than that, both in terms of bug fixes and some additional features (specifically the dispatcher lifecycle integration) alike.

Features

Navigate between UI states. Support the back button easily without confusing your users with surprising results.

Remember the UI state, and its history, as you navigate and across configuration changes and process death.

Manage resources. UI states can create and share resources, and you can dispose of them when no longer needed.

Manage all types of UIs-- complex master-detail views, multiple layers, and window-based dialogs are all simple to manage.

Using Flow(less)

In order to use Flow(less), you need to add jitpack to your project root gradle:

buildscript {
    repositories {
        // ...
        maven { url "https://jitpack.io" }
    }
    // ...
}
allprojects {
    repositories {
        // ...
        maven { url "https://jitpack.io" }
    }
    // ...
}

and add the compile dependency to your module level gradle.

compile 'com.github.Zhuinden:flowless:1.0-RC2'

Then, install Flow into your Activity:

public class MainActivity {
    @BindView(R.id.main_root)
    ViewGroup root;

    TransitionDispatcher flowDispatcher; // extends SingleRootDispatcher

    @Override
    protected void attachBaseContext(Context newBase) {
        flowDispatcher = new TransitionDispatcher();
        newBase = Flow.configure(newBase, this) //
                .defaultKey(FirstKey.create()) //
                .dispatcher(flowDispatcher) //
                .install(); //
        flowDispatcher.setBaseContext(this);
        super.attachBaseContext(newBase);
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        flowDispatcher.getRootHolder().setRoot(root);
    }

    @Override
    public void onBackPressed() {
        if(!flowDispatcher.onBackPressed()) {
            super.onBackPressed();
        }
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        flowDispatcher.preSaveViewState(); // optional
        super.onSaveInstanceState(outState);
    }

    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        flowDispatcher.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        flowDispatcher.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

Defining UI states with key objects

Your Activity's UI states are represented in Flow by Objects, which Flow refers to as "keys". Keys are typically value objects with just enough information to identify a discrete UI state.

Flow relies on a key's equals and hashCode methods for its identity. Keys should be immutable-- that is, their equals and hashCode methods should always behave the same.

To give an idea of what keys might look like, here are some examples:

public enum TabKey {
  TIMELINE,
  NOTIFICATIONS,
  PROFILE
}

public final class HomeKey extends flowless.ClassKey {
}

public final class ArticleKey {
  public final String articleId;

  public ArticleKey(String articleId) {
    this.articleId = articleId;
  }

  public boolean equals(Object o) {
    return o instanceof ArticleKey
        && articleId.equals(((ArticleKey) o).articleId);
  }
  
  public int hashCode() {
    return articleId.hashCode();
  }
}

But if you want to be really cool, you can use Auto-Parcel to generate Parcelable immutable objects to define your keys.

public interface LayoutKey
        extends Parcelable {
    @LayoutRes int layout();

    FlowAnimation animation();
}

@AutoValue
public abstract class CalendarEventKey implements LayoutKey {
    abstract long eventId();

    public static CalendarEventKey create(long eventId) {
        return new AutoValue_CalendarEventKey(R.layout.path_calendarevent, FlowAnimation.SEGUE, eventId);
    }
}

Navigation and History

Flow offers simple commands for navigating within your app.

Flow#goBack() -- Goes back to the previous key. Think "back button".

Flow#set(key) -- Goes to the requested key. Goes back or forward depending on whether the key is already in the History.

Flow also lets you rewrite history safely and easily.

Flow#setHistory(history, direction) -- Change history to whatever you want.

To modify the history, you ought to use the operators provided by History. Here is an example:

History history = Flow.get(this).getHistory();
Flow.get(this).setHistory(history.buildUpon().pop(2).push(SomeKey.create()).build(), Direction.BACKWARD);

See the Flow class for other convenient operators.

As you navigate the app, Flow keeps track of where you've been. And Flow makes it easy to save view state (and any other state you wish) so that when your users go back to a place they've been before, it's just as they left it.

Controlling UI

Navigation only counts if it changes UI state. Because every app has different needs, Flow lets you plug in your own logic for responding to navigation and updating your UI.

The Dispatcher has the following tasks when a new state is set:

  • Check for short-circuit if new state is same as the old (DispatcherUtils.isPreviousKeySameAsNewKey()), and if true, callback and return
  • Inflate the new view with Flow's internal context using LayoutInflater.from(traversal.createContext(...))
  • Persist the current view (DispatcherUtils.persistViewToStateAndNotifyRemoval())
  • Restore state to new view (DispatcherUtils.restoreViewFromState())
  • Optionally animate the two views (with TransitionManager or AnimatorSet)
  • Remove the current view
  • Add the new view
  • Signal to Flow that the traversal is complete (callback.onTraversalCompleted())

Surviving configuration changes and process death

Android is a hostile environment. One of its greatest challenges is that your Activity or even your process can be destroyed and recreated under a variety of circumstances. Flow makes it easy to weather the storm, by automatically remembering your app's state and its history.

You supply the serialization for your keys, and Flow does the rest. The default parceler uses Parcelable objects. Flow automatically saves and restores your History (including any state you've saved), taking care of all of the Android lifecycle events so you don't have to worry about them.

Note: If you use the ContainerDispatcherRoot, you must call ForceBundler.saveToBundle(view) manually in the preSaveViewState() method on the child you wish to persist in your container, because this cannot be handled automatically.

Pre-set dispatchers for common use-cases

Two use-cases are supported out of the box. Both of them provide (optional) life-cycle hooks for easier usage within your custom viewgroups.

First is the SingleRootDispatcher, which works if your Activity has a single root, meaning you're changing the screens within a single ViewGroup within your Activity. This base class provides the default event delegation to the view inside your root. The dispatch() method has to be implemented by the user.

Second is the ContainerRootDispatcher. Its purpose is to delegate the dispatch() call and all other lifecycle method calls to your defined custom viewgroup. The Root provided to a container root dispatcher must implement Dispatcher. It must also delegate the lifecycle method call to its children. For easier access to all lifecycle methods, the FlowContainerLifecycleListener interface is introduced, and the FlowLifecycleProvider class tries to make delegation as simple as possible. Of course, delegation of the FlowLifecycles lifecycle methods are optional, and you can choose to delegate only what you actually need. An example is provided for this setup in the Master-Detail example.

Example Custom View Group

The most typical setup for a custom view group would look like so, using the Bundleable interface and listening to state restoration with ViewLifecycleListener.

public class FirstView
        extends RelativeLayout
        implements Bundleable, FlowLifecycles.ViewLifecycleListener {
    private static final String TAG = "FirstView";

    public FirstView(Context context) {
        super(context);
        init();
    }

    public FirstView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public FirstView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(21)
    public FirstView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    FirstKey firstKey;

    public void init() {
        if(!isInEditMode()) {
            firstKey = Flow.getKey(this);
        }
    }

    @OnClick(R.id.first_button)
    public void firstButtonClick(View view) {
        Flow.get(view).set(SecondKey.create());
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        ButterKnife.bind(this);
    }

    @Override
    public Bundle toBundle() {
        Bundle bundle = new Bundle();
        // persist state here
        return bundle;
    }

    @Override
    public void fromBundle(@Nullable Bundle bundle) {
        if(bundle != null) {
            // restore state here
        }
    }

    @Override
    public void onViewRestored() {
        // view was created and state has been restored
    }

    @Override
    public void onViewDestroyed(boolean removedByFlow) {
        // view is about to be destroyed, either by Activity death 
        // or the Flow dispatcher has removed it
    }
}

The view is created based on the key:

@AutoValue
public abstract class FirstKey
        implements LayoutKey {
    public static FirstKey create() {
        return new AutoValue_FirstKey(R.layout.path_first);
    }
}

And it's inflated based on the following XML:

<?xml version="1.0" encoding="utf-8"?>
<com.zhuinden.flowless_dispatcher_sample.FirstView 
             xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
    <Button
        android:id="@+id/first_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/go_to_second_view"/>
</com.zhuinden.flowless_dispatcher_sample.FirstView>

Managing resources (optional)

You can manage resources shared through your context manually using the ServiceProvider, which you can obtain through ServiceProvider.get().

This way, you can bind services you need when you initialize your View in its constructor (before onFinishInflate() is called) or before it's inflated in the Dispatcher itself, while also sharing them to additional views that belong to the same Context.

Here is a rather barebones implementation that creates services for elements that are currently within the history of keys.

ServiceProvider serviceProvider = ServiceProvider.get(newContext);

// destroyNotIn()
Iterator<Object> aElements = traversal.origin != null ? traversal.origin.reverseIterator() : Collections.emptyList().iterator();
Iterator<Object> bElements = traversal.destination.reverseIterator();
while(aElements.hasNext() && bElements.hasNext()) {
    BaseKey aElement = (BaseKey) aElements.next();
    BaseKey bElement = (BaseKey) bElements.next();
    if(!aElement.equals(bElement)) {
        serviceProvider.unbindServices(aElement);  // returns map of bound services
        break;
    }
}
while(aElements.hasNext()) {
    BaseKey aElement = (BaseKey) aElements.next();
    serviceProvider.unbindServices(aElement); // returns map of bound services
}
// end destroyNotIn

// create service for keys
for(Object destination : traversal.destination) {
    if(!serviceProvider.hasService(destination, DaggerService.TAG) {
        serviceProvider.bindService(destination, DaggerService.TAG, ((BaseKey) destination).createComponent());
    }
}

Which can now share the following service:

public class DaggerService {
    public static final String TAG = "DaggerService";

    @SuppressWarnings("unchecked")
    public static <T> T getComponent(Context context) {
        //noinspection ResourceType
        return (T) ServiceProvider.get(context).getService(Flow.getKey(context), TAG);
    }
}

And can be obtained like so:

private void init(Context context) {
    if(!isInEditMode()) {
        LoginComponent loginComponent = DaggerService.getComponent(context);
        loginComponent.inject(this);
    }
}

License

Copyright 2013 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

More Repositories

1

simple-stack

[ACTIVE] Simple Stack, a backstack library / navigation framework for simpler navigation and state management (for fragments, views, or whatevers).
Java
1,319
star
2

guide-to-kotlin

[GUIDE] This tutorial assumes all you know is Java, but you want to learn Kotlin.
1,288
star
3

jetpack-navigation-ftue-sample

[DEMO] Sample code to display "First-Time User Experience" in a Single-Activity app using Jetpack-Navigation, NavGraphs, Dagger, SavedStateHandle, Hilt, and EventEmitter - based on the FTUE example code in simple-stack-tutorials, but originally described by Google.
Kotlin
236
star
4

fragmentviewbindingdelegate-kt

[ACTIVE] A delegate for making managing the ViewBinding variable in a Fragment simpler.
Kotlin
151
star
5

espresso-helper

[STAGNANT?] Collection of Kotlin helpers for Espresso.
Kotlin
131
star
6

simple-stack-compose-integration

[ACTIVE/BETA] Compose integration for Simple-Stack.
Kotlin
98
star
7

realm-monarchy

[ACTIVE-ISH] A wrapper over Realm which exposes it as LiveData, managing Realm lifecycle internally.
Java
86
star
8

livedata-combinetuple-kt

[ACTIVE] Helper function to combine LiveData into tuples.
Kotlin
85
star
9

realm-book-example

This is an example rewrite of AndroidHive's messy tutorial, accompanying the following article on Realm.
Java
80
star
10

event-emitter

[ACTIVE] The event emitter allows you to register multiple observers, but enqueue events while there are no observers.
Java
76
star
11

live-event

[ACTIVE] Lifecycle-aware wrapper over EventEmitter, for modelling one-off events.
Kotlin
62
star
12

mvvm-aac-rxjava-retrofit-room

[DEMO] MVVM, AAC (ViewModel), RxJava2, Retrofit, Room
Kotlin
54
star
13

BottomNavChildFragmentExample

An example showing how to use bottom navigation with child fragments.
Kotlin
44
star
14

Jetpack-Navigation-Hilt-MultiModule-Example

A simple example showing multi-module navigation with safeargs and jetpack navigation.
Kotlin
42
star
15

ViewBindingExample

An example of using ViewBinding in Activity/Fragment.
Kotlin
37
star
16

Jetpack-Navigation-Multistack-Example

[DEMO] An example using Jetpack Navigation and bottom nav multi-stack using child fragments.
Kotlin
36
star
17

service-tree

[ABANDONED] A tree that stores services in its node for a given key, and allows traversing them.
Java
33
star
18

flow-combinetuple-kt

[ACTIVE] Helper function to combine Flow into tuples.
Kotlin
32
star
19

simple-stack-tutorials

[MOVED] Guide was merged into `simple-stack` repository.
Kotlin
30
star
20

xkcd-example

[SIMPLE DEMO] A super-simple no-architecture app with Retrofit, Realm, and Glide.
Java
29
star
21

state-bundle

[ACTIVE] A non-Android Parcelable replacement for Bundle.
Java
28
star
22

simple-stack-ftue-sample

[DEMO] Sample code to display "First-Time User Experience" in a Single-Activity app using Simple-Stack, based on the "Conditional Navigation" section by Google.
Kotlin
27
star
23

realm-helpers

[ABANDONED] A collection of helpers that are still all in an early stage, but some people could consider them helpful.
Java
23
star
24

MortarFlowSetup

OBSOLETE: USE `simple-stack` instead!
Java
22
star
25

sync-timer-app

[APP] Sync Timer allows multiple people to join, and see a shared countdown. It stops if someone stops it.
Java
20
star
26

DaggerViewModelExperiment

[DEPRECATED, EXPERIMENT] This is a PoC example for app->activity->fragment subscoping with Subcomponents + ViewModel by SavedStateHandle (using AutoFactory). You shouldn't use this setup, because it breaks scoping. Refer to https://github.com/Zhuinden/jetpack-navigation-ftue-sample for a proper setup.
Kotlin
19
star
27

jetpack-navigation-ftue-compose-sample

[DEMO] FTUE sample using Jetpack Navigation's Navigation-Compose, ViewModel, SavedStateHandle, Hilt
Kotlin
18
star
28

command-queue

[ACTIVE] A queue with a single receiver and if there is no receiver, the commands are enqueued.
Java
17
star
29

android-dev-challenge-compose-design

Kotlin
17
star
30

room-live-paged-list-provider-experiment

An experiment with using Room and LivePagedListProvider.
Java
16
star
31

navigator

[RELOCATED] The contents of this repository were merged into simple-stack 1.5.0.
Java
15
star
32

tuples-kt

[ACTIVE] Tuples from 4 to 16 arity in Kotlin.
Kotlin
15
star
33

realm-auto-migration

[ABANDONED] Automatic migration from the currently existing schema to the currently existing model classes.
Java
15
star
34

flow-ziptuple-kt

[ACTIVE] Helper functions to zip Flows into 3 to 11 arity tuples, and to array.
Kotlin
12
star
35

single-activity-instant-app-example

This is an experiment to set up a "single-activity" app in an instant-app setup.
Kotlin
10
star
36

compose-adopt-a-dog

Submission for #AndroidDevChallenge Week 1.
Kotlin
10
star
37

singleton-realm-manager

[MOVED] Moved to Realm-Helpers. The RealmManager class allows opening/closing the Realm instance, but also obtaining it without incrementing the cache's reference count.
Java
9
star
38

livedata-combineutil-java

[ACTIVE] Helper function to combine LiveDatas.
Java
9
star
39

rx-combinetuple-kt

[ACTIVE] Helper function to combine RxJava observables into tuples.
Kotlin
8
star
40

realm-databind-experiment

Making Realm work with Databinding.
Java
8
star
41

android-dev-challenge-compose-clock

Kotlin
7
star
42

rx-realm-recyclerview-experiment

Checking out RX with RecyclerView and Realm.
Java
6
star
43

MortarFlowInitialDemo

OBSOLETE: USE `Flowless` instead!
Java
6
star
44

compose-simple-stack-experiment

Initial experiment with Compose + Simple-Stack.
Kotlin
6
star
45

livedata-validateby-kt

[ACTIVE] Helper functions to combine multiple boolean streams into a single boolean.
Kotlin
6
star
46

flow-validateby-kt

[ACTIVE] Helper function for Flow, to combine multiple boolean values into a single boolean.
Kotlin
5
star
47

simple-stack-extensions

Extensions for the simple-stack library.
Kotlin
4
star
48

ExampleGithubClient

[DEPRECATED] This is an MVP example for Flowless, using Dagger2 subscoping and RxJava Single.
Java
4
star
49

simple-stack-ftue-compose-sample

[DEMO] Simple-stack FTUE sample using Compose integration (and Rx)
Kotlin
3
star
50

rx-validateby-kt

[ACTIVE] Helper to combine multiple boolean streams for simple validation.
Kotlin
3
star
51

navigation-example

Navigation example using simple-stack.
Java
3
star
52

simple-stack-multi-module-experiment

[EXPERIMENT] Experiment with simple-stack + Views in a multi-module setting using Dagger.
Kotlin
3
star
53

AndroidDiceGame

Just a very simple sample project based on a Verilog homework I had a while ago, for experimentations.
Kotlin
2
star
54

scope-manager

[OBSOLETE] This will never happen.
Java
2
star
55

AndroidReactor

AndroidReactor is a framework for a reactive and unidirectional Android application architecture.
Kotlin
2
star
56

FirstTestingApp

This is a repository in which I'm trying to get testing to work. Not much here yet.
Java
2
star
57

realm-samples

[TODO] Realm sample codes for tutorials (currently in production). Check back later!
Kotlin
1
star
58

ButtonShadowGravityProblem

This is so that Reddit can look at it and see how changing the gravity breaks the line at the bottom of the button. SPOILERS: The answer was `scrollX=594742`.
Kotlin
1
star
59

angular-first-app

First app with Angular4, following a tutorial and stuff.
TypeScript
1
star
60

vanilla-cat-example

An example without Dagger2, but with Retrofit and raw android SQLite. Not a good example, though. Too much hackery.
Java
1
star
61

validationk

[NONSENSE] ValidationK: A library that I'm not sure is needed by anyone (from the future: no), but it lets you chain predicates.
Kotlin
1
star
62

flow-sample

Flow-Sample by Square
Java
1
star
63

CasterIO-Simple-Stack

Sample code for Caster.io's Simple-Stack samples.
Kotlin
1
star