Git Product home page Git Product logo

flow's Introduction

Deprecated

Flow had a good run and served us well, but new use is strongly discouraged. The app suite at Square that drove its creation is in the process of replacing Flow with Square Workflow.

Flow

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

"The winds and waves are always on the side of the ablest navigators." — Gibbon

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

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

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 with set-up/tear-down hooks invoked for each UI state. UI states can easily share resources, and they'll be disposed 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

Gradle:

compile 'com.squareup.flow:flow:1.0.0-alpha3'

Install Flow into your Activity:

public class MainActivity {
  @Override protected void attachBaseContext(Context baseContext) {
    baseContext = Flow.configure(baseContext, this).install();
    super.attachBaseContext(baseContext);
  }
}

By default, Flow will take over your Activity's content view. When you start your Activity, you should see a "Hello world" screen. Of course you'll want to change this-- that's covered under Controlling UI below.

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 flow.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();
  }
}

See the Sample Projects below for more example keys.

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.

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.

See the Basic Sample, Tree Sample, and MultiKey Sample below for examples.

Managing resources

Your app requires different resources when it's in different states; sometimes those resources are shared between states. Flow makes it easy to associate resources with keys so they're set up when needed and torn down (only) when they're not anymore.

See the Tree Sample for an example.

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. 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.

Sample projects

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.

flow's People

Contributors

chrisjenx avatar chrisrenke avatar edenman avatar holmes avatar jakewharton avatar jameswald avatar jdreesen avatar jrodbx avatar lexs avatar loganj avatar manuelpeinado avatar mattprecious avatar maxandron avatar nickai avatar novachevskyi avatar pforhan avatar pyricau avatar rjrjr avatar wuman avatar zach-klippenstein avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flow's Issues

decide whether to short-circuit identity traversals in Flow

Currently every dispatcher we have short-circuits if asked to perform a traversal that results in no change to the history (really, the top of the history, but that's too limited). KeyDispatcher (about to land) implements this check, for instance.

Do we want to move this short-circuit into Flow itself?

Save view (or mortar presenter) in backstack

Hi,

I have troubles saving the state of a previous view and presenter in Flow's backstack.
My view A shows a google map. When I navigate to view B and then go back to view A through backstack, i find no way to restore the previous google map with all its data.
The current result is that the map is recreated from scratch.

I would appreciate any sample or lead to find a way to restore the previous map.
Really like how Flow and Mortar work together, but this is the real deal breaker for me so far.

Thanks for the help.

Updating backstack without changing GUI

Do you have any suggestions how I can integrate Flow with my app built using a ViewPager? When the user swipes to a new page, I want to replace the top entry on the backstack with a new screen, but I don't want any navigation to happen because the user has already done the navigation.

I've worked around this by just ignoring replace navigations like so:

if (flowDirection != Flow.Direction.REPLACE) {
    view.showScreen(newScreen, flowDirection);
}

but that seems a bit hacky.

How to animate replaceTo?

So the sample app animates goTo in SimplePathContainer. I thought this would also apply to replaceTo, but it does not.

selectHistory prioritizes intent history over savedinstancestate

Hi

I'm using setHistoryExtra as a way to initialize a history from for example notifications.

onCreate will correctly find the savedinstancestate history in that state, but it will be overridden by selectHistory which prioritizes the history from the intent. That seems like an odd thing? I don't see any case where I would want the intent history (which is static) to override the savedinstancestate history?

Unable to build flow-sample

I suck at maven, but when trying to build with mvn clean install from the project root, I'm getting this error: https://gist.github.com/matthewmichihara/7454713

mvn --version returns

› mvn --version
Apache Maven 3.1.0 (893ca28a1da9d5f51ac03827af98bb730128f9f2; 2013-06-27 19:15:32-0700)
Maven home: /usr/local/Cellar/maven/3.1.0/libexec
Java version: 1.6.0_65, vendor: Apple Inc.
Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
Default locale: en_US, platform encoding: MacRoman
OS name: "mac os x", version: "10.9", arch: "x86_64", family: "mac"

goUp() is a drag

HasParent, goUp(), and any baggage hanging from them (e.g. replaceTo) are lousy, and should be dropped from the library.

Flow.Callback is difficult to honor across configuration changes.

The reentrance fix introduced last week requires the Flow.Listener to call Flow.Callback#onComplete after animation and such has settled down. This is a difficult contract to honor if a configuration change happens in midflight, and the sample app doesn't even try.

OnWindowAttachListener would be a nice way to handle this without changing the ScreenSwitcher API, but it requires level 18 and we really can't abandon ICS users yet.

Cant encode EditText on 4.3

We are having issues on Android 4.3 when Gson tries to encode a basic EditText field.

We have an edit text...

    <EditText
        android:id="@+id/email_address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

We then run the application and hit the home button to trigger saving the state which then throws...

     Caused by: java.io.IOException: Incomplete document
            at com.google.gson.stream.JsonWriter.close(JsonWriter.java:527)
            at com.square.flow.GsonParcer.encode(GsonParcer.java:73)
            at com.square.flow.GsonParcer.wrap(GsonParcer.java:42)
            at flow.Backstack$ParcelableBackstack$Memory.writeToParcel(Backstack.java:229)
            at android.os.Parcel.writeParcelable(Parcel.java:1254)
            at android.os.Parcel.writeValue(Parcel.java:1173)
            at android.os.Parcel.writeMapInternal(Parcel.java:591)
            at android.os.Bundle.writeToParcel(Bundle.java:1646)
            at android.os.Parcel.writeBundle(Parcel.java:605)
            at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:2318)
            at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3100)
            at android.os.Handler.handleCallback(Handler.java:730)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:5103)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:525)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
            at dalvik.system.NativeStart.main(Native Method)

If we look into the JsonWriter we see the following output streams of json.

4.3

{"com.client.ble.app.screens.LoginScreens$SignUp":{"viewState":{"mValues":[null,{"text":{"mText":"[email protected]","mSpanData":[0,11,8388626,0,11,18,0,11,18,11,11,546,11,11,34,10,10,17,10,11,33,0,11,33,0,11,33,0,0,0,0,0,0,0,0,0,0,0,0],"mSpans":[

4.4

{"com.client.ble.app.screens.LoginScreens$SignUp":{"viewState":{"mValues":[null,{"text":{"mFilters":[],"mText":["f","o","o","@","b","a","r",".","c","o","m","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000","\u0000"],"mSpans":[null],"mSpanStarts":[0],"mSpanFlags":[0],"mSpanEnds":[0],"mSpanCountBeforeAdd":0,"mSpanCount":0,"mGapStart":11,"mGapLength":15},"selEnd":7,"selStart":7,"frozenWithFocus":true},null,null,null,null,null,null,null,null,null,null,null],"mKeys":[2131230799,2131230800,2131230801,2131230802,2131230803,0,0,0,0,0,0,0,0],"mSize":5,"mGarbage":false}}}

If we look into the objects further it seems 4.3 has an array with mSpan objects where as on 4.4 the same edit text does not. Gson then has trouble encoding all of the various span objects 4.3 has in the mSpans array.

Wondering if you have run into this?

Flow sample without Flow.Path

As you divided Flow to Flow and Flow.Path it would be great to create sample application without using Path because now it's not clear why anyone should use Path and the idea behind it. Documentation in Path class on that theme is missing. Thanks

Saving/Restoring history and persistant storage

It looks to me like History instances can be serialized and unserialized to parcels/bundles but not to String, which is surprising because IMO saving/restoring History to persistant storage should be a pretty common need (say, you want users to be able to find the app in the same state it was before a power down/a previous session)

In addition of StateParceler, Paths could optionaly implement
public interface StateToString {
String wrap(Object instance);
Object unwrap(String sring);
}
which would allow the implementation of 2 utillity methods to serialize/deserialize from String

Also, Gson should already be able to serialize/deserialize simple paths...but those utility methods aren't implemented in the GsonParceler

mmmhhh iterate the History and serialize each entry as an Object...and deserialize a list of Object into an History with a builder

What This Libs Do?

hi
i saw this lib that was taged as an amazing useful lib for android
but i dont know what this lib exactly do, can anyone explain me (or just attach a screenshot)
thnx

dispatch is called twice on startup

Hi

dispatch gets called twice on startup in the sample.

onResume has the following code:
if (!dispatcherSet) {
dispatcherSet = true;
flow.setDispatcher(dispatcher);
}

The boolean is never set in construction, this triggers setDispatch twice.

I can submit a PR, but I'm uncertain about whether or not the boolean should be set. Or setDispatch should check if it's set to the same as it was before. (if (this.dispatch == dispatch) return;).

Although I wouldn't neccesarly expect a callback immediately after setDispatch so a this.dispatch==dispatch check would work fine for us. I'm not sure on what other situations this might be used. I guess updating the boolean is a more safe option.

Flow.get

I would like to have a method that takes a context and returns a path (if there is one) or (null or somme other NO_PATH constant path value) if there is not. But it is quite dirty to implement with the current Flow.get method implementation :

public static T get(Context context) {
LocalPathWrapper wrapper = LocalPathWrapper.get(context);
if (wrapper == null) {
throw new IllegalArgumentException("Supplied context has no Path");
}
// If this blows up, it's on the caller. We hide the cast as a convenience.
//noinspection unchecked
return (T) wrapper.localScreen;
}

As LocalPathWrapper is a private class, you have to use Flow.get inside a try/catch block to return null/a sentinel value...

Isn't it an usual situation to wonder where you are ? (and to want to guard against the fact that you are not insifde a flow screen ?)

Save view state in a ViewPager adapter

Hey,

I'm using mortar 0.17-SNAPHOT+flow0.9-SNAPSHOT+dagger2 in my project and i have some problems saving view state after I return from screen2. My screen1 is a ViewPager that holds 2 Paths and one of them has a RecyclerView with some data. After clicking one of the items, it goes to screen2, but when I return, all data and scroll progress is gone. When I do the same, but without a ViewPager, coming back from screen2 works fine, scroll progress isn't disappeared, so I thing that a ViewPager adapter losses all the stuff. How can I solve this problem ? any example would be perfect.

Here is my ViewPager adapter code:
private final SparseArray screens = new SparseArray<>();

@Override public Object instantiateItem(ViewGroup container, int position) {
    Path screen = screens.get(position);
    String childName = screen.getClass().getSimpleName() + "#" + position;

    MortarScope newChildScope = screenScoper.getScreenScope(mContext, childName, screen);

    Context childContext = newChildScope.createContext(mContext);
    View newChild = Layouts.createView(childContext, screen);
    container.addView(newChild);
    return newChild;
}

@Override public void destroyItem(ViewGroup container, int position, Object object) {
    View view = ((View) object);
    MortarScope childScope = getScope(view.getContext());
    container.removeView(view);
    childScope.destroy();
    screens.remove(position);
}

Saving state between screens

How is it possible to save a state between screens when moving forward and backward in the flow?
For example, how do we maintain the list scroll position if we scroll down the conversation list in the sample code, click an item to show a conversation, and then navigate back.

A possibility to use Parcelable screens and get rid of the StateParceler?

Right now we use POJOs to represent screens, and then some kind of binary/text serialization to wrap a History into a Parcelable

What about making History Parcelable-aware, thus eliminating an extra serialization/deserialization overhead and getting rid StateParceler interface?

For example top() would be parametrized like this:

 public <T extends Parcelable> T top() {
  //noinspection unchecked
  return (T) peek(0);
}

smaler more concrete sample

Any chance of a simple single case demo of flow? Going from one view to another and then back again should be sufficient as a simple case.

Looking at the current sample is a bit demotivating, I'm sure it demonstrates more than is neccesary to get it up and running, but a 1500+ codeline sample is quite a lot of code requirement to be added into a project. And at first glance it looks like roughly 1k of those are util functions that are actually needed in order to use the library itself.

finalize KeyChanger's signature and make it public

KeyChanger (about to land) is an abstract class. Its constructor is package-private, to prevent extension while the signature is being finalized.

Finalize the signatures and remove the package-private constructor, maybe make KeyChanger an interface.

Flow#resetTo unintuitive behavior on screen not existing in backstack

final Flow flow = new Flow(Backstack.single(new FooScreen(), someListener)); flow.resetTo(new SomeOtherScreen()); //flow.getBackstack().size() is currently 2

SomeOtherScreen is not in the backstack. Flow#resetTo as a result pushes SomeOtherScreen onto the backstack along with any preexisting Screen(s) (FooScreen). I wasn't expecting this type of behavior with the /** Reset to the specified screen. Pops until the screen is found (inclusive) and appends. */ note on Flow#resetTo.

Don't know what the default behavior should be... throw an exception or create a new backstack of only the param screen.

Flow and actionBar

In a single Activity + flow setting (with optionnaly mortar + dagger).
What is the prefered way/best practice to handle the actionBar/Menu ?

delegate the onCreateOptionsMenu and onPrepareOptionsMenu methods of the activity
and handling them depending on the current view ?

If a flow app naviguates between CustomViews, which are UI states, aren't menu configuration supposed to be part of that state ?

goTo() should be resetTo()

resetTo() does what goTo() should do: pops back to the given screen, or if it is not found appends that screen to the backstack. Meanwhile, goTo() allows the same screen to be pushed onto the stack twice, which is basically never useful. So kill the existing goTo() and rename resetTo().

Also, resetTo's javadoc is wrong, claming that the backstack is replaced if the screen wasn't already there.

java.lang.NullPointerException: Expected to find a PathContext but did not.

I am in the process to convert on of my project to dagger2 + mortar + flow + kotlin.
I mostly copied code from the mortar HelloDagger2 samples, flow samples and kotlin+dagger samples

I manage to display a "Search" screen through
flowSupport = FlowDelegate.onCreate(nonConfig, getIntent(), savedInstanceState, parceler, History.single(Search()), this)

Then I click on a button to display a "Build" screen through
Flow.get(this@FlowActivity).set(Build())
and I get the exception : java.lang.NullPointerException: Expected to find a PathContext but did not.

I guess that the offending view got inflated from a Context that has not been wrapped in a PathContext though it should have been.

To understand why the exception happened I added a logging line to see if both views where instanciated with a PathContext (they are)

The weird thing is that : without the logging line on the Search view, the exception happens, with the logging line, it doesn't happen... (timing/thread issue ? o.O)

public class SearchView(context: Context, attrs: AttributeSet) : DrawerLayout(context, attrs) {
var searchPresenter: SearchPresenter? = null
[Inject] set

init {
    DaggerService.getDaggerComponent<Search.Component>(context).inject(this)
   // if (context.getSystemService("PATH_CONTEXT") == null) throw Exception("not a path context $context ${(context as? ContextWrapper)?.getBaseContext()}") 
}

....

}

The complete stack trace (first line helps me track which view is the offender--> the first View)

05-17 15:10:43.120 14120-14707/org.lakedaemon.japanese.dictionary.pro E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: org.lakedaemon.japanese.dictionary.pro, PID: 14120
java.lang.RuntimeException: org.lakedaemon.android.flow.pathview.FramePathContainerView{4287e228 V.E..... ........ 0,0-720,1118 #7f0e00c4 app:id/container} org.lakedaemon.android.flow.view.BuildView{42dbb758 VFE..... ........ 0,0-720,1118 #1020012 android:id/tabhost}
java.lang.NullPointerException: Expected to find a PathContext but did not.
at org.lakedaemon.android.flow.pathview.SimplePathContainer.performTraversal(SimplePathContainer.kt:55)
at flow.path.PathContainer.executeTraversal(PathContainer.java:89)
at org.lakedaemon.android.flow.pathview.FramePathContainerView.dispatch(FramePathContainerView.kt:51)
at org.lakedaemon.android.flow.FlowActivity.dispatch(FlowActivity.kt:163)
at flow.Flow$PendingTraversal.dispatch(Flow.java:315)
at flow.Flow$2.doExecute(Flow.java:197)
at flow.Flow$PendingTraversal.execute(Flow.java:323)
at flow.Flow.move(Flow.java:232)
at flow.Flow.set(Flow.java:162)
at org.lakedaemon.android.flow.FlowActivity$onCreateOptionsMenu$1.onMenuItemClick(FlowActivity.kt:132)
at android.support.v7.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:149)
at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:949)
at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:939)
at android.support.v7.widget.ActionMenuView.invokeItem(ActionMenuView.java:598)
at android.support.v7.internal.view.menu.ActionMenuItemView.onClick(ActionMenuItemView.java:139)
at android.view.View.performClick(View.java:4741)
at android.view.View$PerformClick.run(View.java:19384)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:146)
at android.app.ActivityThread.main(ActivityThread.java:5679)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException: Expected to find a PathContext but did not.
at flow.path.Preconditions.checkNotNull(Preconditions.java:30)
at flow.path.PathContext.get(PathContext.java:100)
at org.lakedaemon.android.flow.pathview.SimplePathContainer.performTraversal(SimplePathContainer.kt:50)
            at flow.path.PathContainer.executeTraversal(PathContainer.java:89)
            at org.lakedaemon.android.flow.pathview.FramePathContainerView.dispatch(FramePathContainerView.kt:51)
            at org.lakedaemon.android.flow.FlowActivity.dispatch(FlowActivity.kt:163)
            at flow.Flow$PendingTraversal.dispatch(Flow.java:315)
            at flow.Flow$2.doExecute(Flow.java:197)
            at flow.Flow$PendingTraversal.execute(Flow.java:323)
            at flow.Flow.move(Flow.java:232)
            at flow.Flow.set(Flow.java:162)
            at org.lakedaemon.android.flow.FlowActivity$onCreateOptionsMenu$1.onMenuItemClick(FlowActivity.kt:132)
            at android.support.v7.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:149)
            at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:949)
            at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:939)
            at android.support.v7.widget.ActionMenuView.invokeItem(ActionMenuView.java:598)
            at android.support.v7.internal.view.menu.ActionMenuItemView.onClick(ActionMenuItemView.java:139)
            at android.view.View.performClick(View.java:4741)
            at android.view.View$PerformClick.run(View.java:19384)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:146)
            at android.app.ActivityThread.main(ActivityThread.java:5679)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
            at dalvik.system.NativeStart.main(Native Method)

Provide a sample unit test

I'm using mortar+flow and I want to test some transition logic in a Presenter, so my first instinct was to mock the Flow class and verify .goTo() is called appropriately. This won't easily work because I can't mock final classes such as Flow.

Do you have a recommendation on how to write such a test instead?

Pull pathview out of sample into a library

It would be nice to pull the pathview portion of the sample out into a library so we can drop it into projects. It's easy enough to drop all the classes into a project, but it would be easier to just add a dependency. Some changes would need to be made to container views to give library consumers more control of transitions.

This may be out of the scope of this project, so please let me know if that's the case. I'm happy to create my own library to accomplish this abstraction, but the sample code is already a great start.

Unable to build with Maven?

I just tried to build the project with mvn from the command line. I finishes successfully, and the APK can be installed on my device, but cannot be launched. The only suspicous output during the build were a lot of messages like the following:

[INFO] trouble processing:
[INFO] bad class file magic (cafebabe) or version (0033.0000)
[INFO] ...while parsing flow/Backstack$ParcelableBackstack$1.class
[INFO] ...while processing flow/Backstack$ParcelableBackstack$1.class

But as I said, mvn reports "BUILD SUCCESS".

I think the relevant portion of the logcat output is the following:

D/SlideAside(31288): [MainService.java:201:handleNewActivity()] oooooo User executes an App : com.example.flow (filtered)
I/ActivityManager( 935): Start proc com.example.flow for activity com.example.flow/.MainActivity: pid=6973 uid=10506 gids={50506}
V/ActivityManager( 935): Moving to STOPPING: ActivityRecord{435be3b0 u0 com.teslacoilsw.launcher/com.android.launcher2.Launcher t1} (stop requested)
V/ActivityManager( 935): Moving to STOPPED: ActivityRecord{435be3b0 u0 com.teslacoilsw.launcher/com.android.launcher2.Launcher t1} (stop complete)
I/MicrophoneInputStream( 2963): mic_close fib@431d87f8
V/AudioRecord( 2963): stop()
D/AudioRecord( 2963): AudioRecord->stop()
V/AudioFlinger( 276): RecordHandle::stop()
V/AudioFlinger( 276): RecordThread::stop
D/dalvikvm( 6973): Debugger has detached; object registry had 1 entries
V/ActivityManager( 935): Moving to RESUMED: ActivityRecord{43aaf978 u0 com.example.flow/.MainActivity t135} (starting new instance)
V/audio_hw_primary( 276): in_standby: enter
D/PhoneApp( 1614): getIsInUseVoLTE : false
D/HyLog ( 6973): I : /data/font/config/dfactpre.dat, No such file or directory (2)
W/System.err( 6973): java.lang.RuntimeException: Unable to instantiate application com.example.flow.DemoApp: java.lang.ClassNotFoundException: Didn't find class "com.example.flow.DemoApp" on path: DexPathList[[zip file "/data/app/com.example.flow-1.apk"],nativeLibraryDirectories=[/data/app-lib/com.example.flow-1, /vendor/lib, /system/lib]]
W/System.err( 6973): at android.app.LoadedApk.makeApplication(LoadedApk.java:526)
W/System.err( 6973): at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4390)
W/System.err( 6973): at android.app.ActivityThread.access$1500(ActivityThread.java:139)
W/System.err( 6973): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1260)
W/System.err( 6973): at android.os.Handler.dispatchMessage(Handler.java:102)
W/System.err( 6973): at android.os.Looper.loop(Looper.java:136)
W/System.err( 6973): at android.app.ActivityThread.main(ActivityThread.java:5105)
W/System.err( 6973): at java.lang.reflect.Method.invokeNative(Native Method)
W/System.err( 6973): at java.lang.reflect.Method.invoke(Method.java:515)
W/System.err( 6973): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
W/System.err( 6973): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
W/System.err( 6973): at dalvik.system.NativeStart.main(Native Method)
W/System.err( 6973): Caused by: java.lang.ClassNotFoundException: Didn't find class "com.example.flow.DemoApp" on path: DexPathList[[zip file "/data/app/com.example.flow-1.apk"],nativeLibraryDirectories=[/data/app-lib/com.example.flow-1, /vendor/lib, /system/lib]]
W/System.err( 6973): at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
W/System.err( 6973): at java.lang.ClassLoader.loadClass(ClassLoader.java:497)
W/System.err( 6973): at java.lang.ClassLoader.loadClass(ClassLoader.java:457)
W/System.err( 6973): at android.app.Instrumentation.newApplication(Instrumentation.java:975)
W/System.err( 6973): at android.app.LoadedApk.makeApplication(LoadedApk.java:521)
W/System.err( 6973): ... 11 more

I used mvn 3.2.3 and Java 6 for this, but I have also used mvn 3.1.1 and Java 7 with the same results.

Is there something wrong with my mvn paths as some classes are obviously not included in the apk?

Thanks for any help!

Create an interface for Path

A Path is really just a list of keys. Extract an interface (or change Path into one) so that it can be more flexibly implemented, for instance via Kotlin data classes.

sample with AppCompatActivity

As the method onRetainNonConfigurationInstance() is final in AppCompatActivity,

  1. Does flow work with AppCompat ?
  2. if yes, how should one modify the MainActivity class (in the sample) to work with AppCompatActivity ?

Can I use Flow when there is no activity exists ?

Recently I was working on a project with a float window. The float window shows when some special app are running. It has complex UI and business logical.
The first edition we try to accomplish this is using Activity with multiple Fragments, but this solution freezes the running app when it shows. Since it shows an Activity and the apps running into onPause.

So I was wondering is there any solutions to using Flow without Activity ?

release 0.9?

The last commit of the "API quake" upgrade was 3 months ago, would be good to see an actual release :)

viewState.save is not run on rotate in sample

Hi

The view is not detached before onSaveInstanceState, therefor viewState.save is not run.

I can't really see any clear good way to solve this either... Yes one could run that method manually, but we have a presenter like pattern where we do state saving/cleanup in onDetachedFromWindow. This function does trigger, but it triggers after serialization, which is obviously a problem.

This worked for us in 0.8 since the objects before and after rotation were the same instance instead of being serialized/deserialized like they are in my tests now. (Which I'm a little surprised happens, but I haven't dug into why yet).

Timing issue for Flow.get()

Hi, I encounter a problem.
I call Flow.get(context) in onLoad of the first path and it will lead to a NullPointerException.
Since when we call in MainActivity:

flowSupport = FlowDelegate.onCreate(nonConfig, getIntent(), savedInstanceState, parceler,
        History.single(new Paths.ConversationList()), this);

The first path "Paths.ConversationList()" will be created and its onLoad is called in FlowDelegate's onCreate():

  flow = new Flow(selectHistory(intent, savedHistory, defaultHistory, parceler));

But we return the flowSupport two lines after.
We can't get the flow services in that time since flowSupport is null:

  @Override public Object getSystemService(String name) {
    Object service = null;
    if (flowSupport != null) {
      service = flowSupport.getSystemService(name);
    }
    return service != null ? service : super.getSystemService(name);
  }

any good suggestion?

Many Thanks

Navigation with Flow destroys Mortar-Scopes on FORWARD transition due to path.elements() not being maintained by anything

The PathContext instances are built as [ROOT; CURRENT_PATH] and [ROOT; NEXT_PATH] as the paths actually have no knowledge of the previous paths, even though the elements() method is called. The elements() only contains the ROOT and the path itself.

I've fixed this by mirroring some classes from Flow

public final void executeTraversal(PathContainerView view, Flow.Traversal traversal, final Flow.TraversalCallback callback) {
    final View oldChild = view.getCurrentChild();
    Path path = traversal.destination.top();
    int i;
    if(traversal.direction == Flow.Direction.FORWARD) {
        Iterator<Object> _destinationPaths = traversal.destination.reverseIterator();
        i = 1; // 0 is ROOT in PathContext! DO NOT CHANGE TO 0
        while(_destinationPaths.hasNext()) {
            Path destinationPath = (Path) _destinationPaths.next();
            if(destinationPath != path) {
                path.elements().add(i, destinationPath);
            }
            i++;
        }
    }
    //...

and

/**
 * Finds the tail of this path which is not in the given path, and destroys it.
 */
public void destroyNotIn(PathContext path, PathContextFactory factory) {
    Iterator<Path> aElements = this.path.elements().iterator();
    Iterator<Path> bElements = path.path.elements().iterator();
    while(aElements.hasNext() && bElements.hasNext()) {
        Path aElement = aElements.next();
        Path bElement = bElements.next();
        if(!aElement.equals(bElement)) {
            factory.tearDownContext(contexts.get(aElement));
            break;
        }
    }
    while(aElements.hasNext()) {
        Path aElement = aElements.next();
        factory.tearDownContext(contexts.get(aElement));
    }
}

I should probably make a pull request about this, but I'm using 0.12 and I'm not sure if anything has changed since then.

Backstack entry's view state is never saved on rotation

Precondition

According so sample, in case of screen rotation NonConfigurationInstance is used to save backstack

Problem

Backstack entry's viewState is not saved on ration, so when recreating flow with backstack, topmost entry doesn't have what to restore and views data (such as text for EditText) is lost.

flow-sample app crashes when launched on a tablet in landscape mode

Hey, I was just poking around the sample, trying to better understand how flow works. I built the flow-sample app, and it seems to work just fine in portrait mode, but in landscape mode, when the MasterPathContainerView is used, the app crashes on startup.

Here's the stack trace:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.flow/com.example.flow.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'flow.History flow.Flow.getHistory()' on a null object reference
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2325)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
            at android.app.ActivityThread.access$800(ActivityThread.java:151)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5254)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'flow.History flow.Flow.getHistory()' on a null object reference
            at com.example.flow.pathview.MasterPathContainerView.dispatch(MasterPathContainerView.java:21)
            at com.example.flow.view.TabletMasterDetailRoot.dispatch(TabletMasterDetailRoot.java:76)
            at com.example.flow.MainActivity.dispatch(MainActivity.java:146)
            at flow.Flow$PendingTraversal.dispatch(Flow.java:315)
            at flow.Flow$1.doExecute(Flow.java:141)
            at flow.Flow$PendingTraversal.execute(Flow.java:323)
            at flow.Flow.move(Flow.java:232)
            at flow.Flow.setHistory(Flow.java:139)
            at flow.Flow.setDispatcher(Flow.java:107)
            at flow.FlowDelegate.onCreate(FlowDelegate.java:104)
            at com.example.flow.MainActivity.onCreate(MainActivity.java:72)
            at android.app.Activity.performCreate(Activity.java:5990)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
            at android.app.ActivityThread.access$800(ActivityThread.java:151)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5254)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)

I haven't changed any source files, just the gradle-mvn-push.gradle file to get rid of some gradle warnings (missing POM_DEVELOPER_ID). I tried digging into it for an hour or so, but I couldn't find a trivial solution to the problem. Am I doing anything wrong?

Thanks a lot!

MultiKeys require exposing multiple contexts

Currently Flow sets up and delivers a single context for an incoming key. That's insufficient for multikeys, where we should deliver a context for each component key (plus the multikey itself).

Destroy PathContext

After new Path set to Flow, the contexts in the old path may be destroyed.
Why only the tail of the old path will be destroyed rather than all contexts which are not in the new path?

The commented in the following code is what I wants to do.

  /** Finds the tail of this path which is not in the given path, and destroys it. */
  public void destroyNotIn(PathContext path, PathContextFactory factory) {
    Iterator<Path> aElements = this.path.elements().iterator();
    Iterator<Path> bElements = path.path.elements().iterator();
    while (aElements.hasNext() && bElements.hasNext()) {
      Path aElement = aElements.next();
      Path bElement = bElements.next();
      if (!aElement.equals(bElement)) {
        factory.tearDownContext(contexts.get(aElement));
        break;
      }
    }
   /**
    while (aElements.hasNext()) {
      factory.tearDownContext(contexts.get(aElements.next()));
    }
  **/
  }

How to handle TabLayout

I am struggling with how to handle a TabLayout as part of mortar+flow. My root view is FramePathContainerView as found in flow-sample. Clearly the method that needs to be overriden somehow is:

@Override public ViewGroup getContainerView() { return this; }

Instead of returning itself as the container, it should inflate the tablayout and use another ViewGroup into which to include the children:

@Bind(R.id.tab_layout) TabLayout tabView; @Bind(R.id.tab_container) ViewGroup container;

@Override
public ViewGroup getContainerView() {
return container;
}

This can be accomplished by having FramePathContainerView examine the traversal and depending on the Path inflate a TabLayout or a normal FrameLayout. The question is, how should Path objects signal to FramePathContainerView that they need the tablayout inflated sometimes, and hidden away other times?

At first I thought this logic should go into SimplePathContainer. But the problem there is that performTraversal method already defines which containerView to use:

performTraversal(final ViewGroup containerView ...

Perhaps someone has insight on how to make this work cleanly?

Releases listed on GibHub aren't current

The "releases" page on GitHub's Flow repository shows 0.8 as the most recent version but those artifacts seem to be fairly outdated. In comparison, the most recent release mentioned in CHANGELOG.md is 0.10. Can/should new releases be added to the repository's page on GitHub?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.