Name UI states, navigate between them, remember where you've been.

Related tags

UI/UX flow
Overview

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.
Comments
  • Migrating from Flow 0.12 & Mortar 0.20

    Migrating from Flow 0.12 & Mortar 0.20

    I'm trying to port some of my apps from Flow 0.12 & Mortar 0.20 to Flow 1.0 alpha 2 but I'm wondering about a few things :

    1. do flow apps still need mortar ? (is there still some mortar benefit versus ServiceFactory ?) also I was heavily using mortar viewPresenters, is there a better way to do things now ?

    2. what about mutable keys ? I used to preserve some state by making keys mutable in flow 0.12. Is there a different way to do that now ?

    Say, you have a searchView, the user types a query and you want to persist that text for the next time the activity resumes after pause/destroy or a reboot of the device. What is the best practice ?

    1. It looks like Flow 1.0 alpha 2 is able to persist history through a KeyParceler. What if version 1 of a user app presists some history and, after an upgrade to version 2 of the same app, it tries to read this history... whith screens that have changed between versions ?
    opened by Lakedaemon 22
  • Saving state between screens

    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.

    opened by nathanielwolf 22
  • TreeKey and MultiKey shouldn't be mutually-exclusive.

    TreeKey and MultiKey shouldn't be mutually-exclusive.

    A key should be able to implement both TreeKey and MultiKey. Because the two interfaces are not logically mutually-exclusive.

    For an instance. A NormalScreen and A DialogScreen, they both have parent key CommonPath. The implementation should be like this:

    public class NormalScreen implements TreeKey {
        public NormalScreen() {
        }
    
       @Override public Object getParentKey() {
        return new CommonPath();
       }
    }
    
    public class DialogScreen implements MultiKey, TreeKey {
        final Object mainContent;
        public DialogScreen(NormalScreen mainContent) {
            this.mainContent = mainContent;
        }
    
       @Override public List<Object> getKeys() {
           return Collections.singletonList(mainContent);
       }
    
       @Override public Object getParentKey() {
        return new CommonPath();
       }
    }
    
    question API 
    opened by fengdai 14
  • Intent handling needs work

    Intent handling needs work

    There are two big problems with the current Intent handling:

    1. It's just broken. Attempting to unpack a history from an Intent will always fail because load() is looking for the wrong key.
    2. It's too inflexible with respect to the Intent. Only Flow can properly pack a History into an Intent, which means it's tricky to offer an action-based API.

    Probably the right solution is an SPI that allows pluggable Intent unpacking.

    bug 
    opened by loganj 13
  • Dispatcher gets called twice ?

    Dispatcher gets called twice ?

    I have a chat application that requires the user to be logged in. I save this as a boolean in SharedPreferences. And the key (values from an enum) to Flow depends on whether the user is logged in or not.

    @Override
    protected void attachBaseContext(Context baseContext) {
        // Initialize PrefUtils
        PrefUtils.init(baseContext);
    
        // Default key depends upon whether the user is logged in or not
        // If user is logged in, default key is Screens.CHAT
        // If the user is not logged in, default key is Screens.WELCOME
        boolean isLoggedIn = PrefUtils.getBoolean(PrefUtils.Key.IS_USER_LOGGED_IN,false);
        key = isLoggedIn ? Screens.CHAT : Screens.WELCOME;
    
        // Configure Flow
        baseContext = Flow.configure(baseContext, this)
                .dispatcher(new ViewDispatcher(this))
                .defaultKey(key)
                .install();
        super.attachBaseContext(baseContext);
    }
    

    and my Dispatcher looks like this:

    @Override
    public void dispatch(Traversal traversal, TraversalCallback callback) {
        Object dest = traversal.destination.top();
        Object source = traversal.origin == null ? null : traversal.origin.top();
    
        Logger.d(TAG,"Dispatcher called");
        Logger.d(TAG, source == null ? "SOURCE: Null" : "SOURCE: " + ((Screens) source).name());
        Logger.d(TAG, "DESTINATION: " + ((Screens) dest).name());
    
        ViewGroup root = (ViewGroup) activity.findViewById(R.id.root);
    
        if (traversal.origin != null) {
            int childCount = root.getChildCount();
            // Our container has a root view with an ImageView in it.
            // If child count > 1, we need to remove the child at position = 1
            // because ImageView is at position = 0
            if (childCount > 1) {
                // save state
                traversal.getState(traversal.origin.top()).save(root.getChildAt(1));
                // remove the added views
                removeAllViewsAfter(root,0);
            }
        }
    
        // default layout = welcome screen
        @LayoutRes int layout;
        if(dest.equals(Screens.WELCOME)){
            layout = R.layout.view_welcome;
        }else if(dest.equals(Screens.CHAT)){
            layout = R.layout.view_chat;
        }else{
            throw new IllegalArgumentException("Unrecognized Screen");
        }
    
        View incomingView = LayoutInflater
                .from(traversal.createContext(dest, activity))
                .inflate(layout, root, false);
        traversal.getState( traversal.destination.top() ).restore(incomingView);
        root.addView( incomingView );
        callback.onTraversalCompleted();
    }
    

    but if I keep the app idle (on any screen) and view the logs, then I find that dispatcher is called twice. My log looks like this:

    03-02 22:02:52.463 32264-32264/chat.example.app D/ViewDispatcher: Dispatcher called
    03-02 22:02:52.463 32264-32264/chat.example.app D/ViewDispatcher: SOURCE: Null
    03-02 22:02:52.463 32264-32264/chat.example.app D/ViewDispatcher: DESTINATION: WELCOME
    03-02 22:02:59.086 32264-32264/chat.example.app D/ViewDispatcher: Dispatcher called
    03-02 22:02:59.086 32264-32264/chat.example.app D/ViewDispatcher: SOURCE: Null
    03-02 22:02:59.086 32264-32264/chat.example.app D/ViewDispatcher: DESTINATION: WELCOME
    

    I have no idea why this is happening. I end up with my layout being drawn twice on the chat screen.

    Why is this happening? :)

    duplicate 
    opened by littlejavachild 13
  • Implementing ServiceFactory properly

    Implementing ServiceFactory properly

    I'm trying to understand how to properly use the ServiceFactory class, but so far I came with nothing. As provided in the FlowServices.java @loganj says:

    In a real app, the conditional class matching shown here doesn't scale very far. Decompose by keys. Even better, keep your ServicesFactory lean and simple by using the key to build/lookup a Dagger graph or Mortar scope!

    How do I accomplish this ? I've came up with a sample project but it's a missing piece in my puzzle.4 My doubt actually is, how can I create a Mortar Scope if I don't have any Context in the Service?

    Any help would be appreciated.

    question 
    opened by leonardo2204 12
  • Dispatcher doesn't know if resuming or starting fresh

    Dispatcher doesn't know if resuming or starting fresh

    Currently, Dispatcher.dispatch is invoked on every onResume of the Activity.

    If the onResume does not follow an onDestroy and the previous view is still in place (e.g. after opening the app switcher and returning), a naive Dispatcher will recreate the view, losing its state.

    Something like this happens e.g. in flow-sample-basic:

    1. Open app
    2. Type some characters
    3. Open app switcher and close again
    4. KeyChanger has created a new view and added it on top of the old one, both views are visible. The new view lost the text in the EditText.

    (Click to enlarge)

    This does not occur after an orientation change, as the view is destroyed before dispatch is called.

    Should dispatch always be called after onResume? Is it the responsibility of the Dispatcher (or KeyChanger when using KeyDispatcher) to detect whether an action is required or if the traversal can be ignored?

    enhancement 
    opened by hannesstruss 12
  • Move Flow configuration/installation out of getBaseContext?

    Move Flow configuration/installation out of getBaseContext?

    I just finished migrating a Flow 0.12+Mortar+Dagger codebase to Flow 1.0.0-alpha2 (+Mortar+Dagger). It took about 5 days of work and will require a lot of validation and review. After polling various sources to piece together an idea of what the delta looked like, things went relatively smoothly. The new structure feels much better. The role Flow plays in the project is more intelligible.

    That said, the biggest quirk in my opinion was the fact that Flow needs to be installed before onCreate and can only be used onPostCreate. Previously, we didn't know our first state before onCreate (it came in via an intent). My solution is to init flow with an empty state that gets updated in onPostCreate. This is because my keychanger expects keys to be in annotated with Layout and I don't want to special case the empty state so I made an empty key and layout. Not a huge deal but it feels unnecessary. I'd rather be able to configure flow when it's attached to the base context and wait until onCreate to set the dispatcher (when my activity is actually live) and services factory (maybe I have services that aren't available until later like a binding to an android service or something). I've been able to move all my DI graph and mortar scope creation to happen when the base context is attached, but writing some lazy binding thing for async-bound services feels weird too. Just some thoughts.

    opened by dcow 11
  • ViewState is not implicitly saved in onSaveInstanceState(), and there is no hook to Flow to trigger a state save

    ViewState is not implicitly saved in onSaveInstanceState(), and there is no hook to Flow to trigger a state save

    In onSaveInstanceState(), a Flow Dispatch is not triggered, and as a result, the views' state is not preserved into the State objects, but onResume() triggers a REPLACE direction that would destroy the previous view along with its view state.

    I personally added a package-internal class that manually places the view state into the InternalLifecycleIntegration's KeyManager, but it is not a canonical solution.

    public class ForceBundler {
        public static void saveToBundle(Activity activity, View... activeViews) {
            InternalLifecycleIntegration internalLifeCycleIntegration = InternalLifecycleIntegration.find(activity);
            if(internalLifeCycleIntegration != null) {
                KeyManager keyManager = internalLifeCycleIntegration.keyManager;
                for(View view : activeViews) {
                    State state = keyManager.getState(Flow.getKey(view));
                    state.save(view);
                    if(view instanceof Bundleable) {
                        state.setBundle(((Bundleable) view).toBundle());
                    }
                }
            }
        }
    }
    

    Which I call in onSaveInstanceState()

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        ForceBundler.saveToBundle(this, root.getChildAt(0));
        super.onSaveInstanceState(outState);
    }
    

    Personally I would expect a call to the changeKey() method in onSaveInstanceState() as that triggers state persistence, and this to be managed automatically through the KeyChanger.

    opened by Zhuinden 11
  • Flow::goBack returns true, when PendingTraversal::state value equals

    Flow::goBack returns true, when PendingTraversal::state value equals "DISPATCHED"

    There is inconsistency in Flow::goBack method documentation and it's actual logic. JavaDoc for this method says that it should return false if going back is not possible or a traversal is in progress.. Currently, it will return true when PendingTraversal::state value would be equal to DISPATCHED, since boolean canGoBack = history.size() > 1 && (pendingTraversal == null || pendingTraversal.state == TraversalState.FINISHED) is true in this case.

    To fix this issue canGoBack value write logic should look like this: boolean canGoBack = history.size() > 1 && (pendingTraversal == null || pendingTraversal.state == TraversalState.FINISHED).

    Please let me know if I'm missing something here or if some other solution should be applied to fix this issue. I will appreciate for your help.

    bug 
    opened by novachevskyi 10
  • NullPointerException calling Flow.get(this) in the onCreate method.

    NullPointerException calling Flow.get(this) in the onCreate method.

    I am getting the following error when I call Flow.get(this) inside the onCreate() method.

    Caused by: java.lang.NullPointerException: Attempt to read from field 'flow.Flow flow.InternalLifecycleIntegration.flow' on a null object reference
                                                         at flow.InternalContextWrapper.getSystemService(InternalContextWrapper.java:54)
                                                         at android.view.ContextThemeWrapper.getSystemService(ContextThemeWrapper.java:123)
                                                         at android.app.Activity.getSystemService(Activity.java:5263)
                                                         at flow.InternalContextWrapper.getFlow(InternalContextWrapper.java:31)
                                                         at flow.Flow.get(Flow.java:46)
                                                         at flow.sample.basic.BasicSampleActivity.onCreate(BasicSampleActivity.java:30)
                                                         at android.app.Activity.performCreate(Activity.java:6237)
                                                         at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
                                                         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
                                                         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) 
                                                         at android.app.ActivityThread.-wrap11(ActivityThread.java) 
                                                         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) 
                                                         at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                         at android.os.Looper.loop(Looper.java:148) 
                                                         at android.app.ActivityThread.main(ActivityThread.java:5417) 
                                                         at java.lang.reflect.Method.invoke(Native Method)
    

    The code:

    public class BasicSampleActivity extends Activity {
    
      @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.basic_activity_frame);
    
        Flow flow = Flow.get(this); // <============== ERROR!!! NPE
        System.out.println(flow);
      }
    
      @Override protected void attachBaseContext(Context baseContext) {
        baseContext = Flow.configure(baseContext, this) //
            .dispatcher(new BasicDispatcher(this)) //
            .defaultKey(new WelcomeScreen()) //
            .keyParceler(new BasicKeyParceler()) //
            .install();
        super.attachBaseContext(baseContext);
      }
    
      @Override public void onBackPressed() {
        if (!Flow.get(this).goBack()) {
          super.onBackPressed();
        }
      }
    }
    

    Somebody can explain me why this is happening in the onCreate() method? If I do the same in onStart() or onResume() everything works well.

    I am not sure if this is a bug or not, but I think a wiki/documentation is required for this awesome library.

    bug 
    opened by epool 9
  • InternalLifecycleIntegration memory leak on P emulator

    InternalLifecycleIntegration memory leak on P emulator

    In co.recharge.consumer.debug:1.15.2:96.
    * flow.InternalLifecycleIntegration has leaked:
    * static FontsContract.sContext
    * ↳ DebugConsumerApp.injectedAuthenticatedStuff
    * ↳ ConsumerApp$InjectedAuthenticatedStuff.onSignedInHelper
    * ↳ OnSignedInHelper.signInHooks
    * ↳ array OnSignedIn[].[4]
    * ↳ PlaceDirections.placeIDToDirectionsRelays
    * ↳ LinkedHashMap.tail
    * ↳ LinkedHashMap$LinkedHashMapEntry.value
    * ↳ BehaviorRelay.state
    * ↳ RelaySubscriptionManager.value
    * ↳ RelaySubscriptionManager$State.observers
    * ↳ array RelaySubscriptionManager$RelayObserver[].[0]
    * ↳ RelaySubscriptionManager$RelayObserver.actual
    * ↳ OperatorObserveOn$ObserveOnSubscriber.child
    * ↳ OnSubscribeOnAssembly$OnAssemblySubscriber.actual
    * ↳ SafeSubscriber.actual
    * ↳ ActionSubscriber.onNext
    * ↳ WalkingTimeView$setPlace$1.this$0 (anonymous implementation of rx.functions.Action1)
    * ↳ WalkingTimeView.!(mContext)!
    * ↳ FlowContextWrapper.!(mBase)!
    * ↳ MainActivity.mFragments
    * ↳ FragmentController.mHost
    * ↳ Activity$HostCallbacks.mFragmentManager
    * ↳ FragmentManagerImpl.mAdded
    * ↳ ArrayList.elementData
    * ↳ array Object[].[0]
    * ↳ InternalLifecycleIntegration
    

    A fun thing I learned today is that Flow uses Fragments for some reason? I'm looking at the InternalLifecycleIntegration code and I'm pretty much baffled by what's going on. Looks like it attaches a fragment to the Activity in onCreate, but then somehow FragmentManager is keeping a reference to it even after the activity has been destroyed?

    LeakCanary 1.6.2, fwiw

    opened by edenman 1
  • ETA for 1.0?

    ETA for 1.0?

    I'm interested in using Flow in my next app, but I'm concerned that its been in alpha for a long time. Is Flow still under active development? If so, would it be possible to provide a rough ETA for a 1.0 release?

    opened by fougere-mike 4
  • Do not reload rest api when go back

    Do not reload rest api when go back

    I try to use RestClient in square flow. This is my step :

    Example:
    1. Main screen call rest api in presenter. And then go to screen 2
    2. On screen 2, call goBack. 
    

    Why when go back to screen 1, rest api is called? How prevent rest client running when goBack?

    opened by bisrinursa 1
  • Save view state with id = -1

    Save view state with id = -1

    Why is a view with id 0 a View with no id but a view with id -1 is treated as View with id?

    As far as I can see in the documentation View.NO_ID is -1. Even more, reading the source code Android ignores the state of any view with no id:

        protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
            if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
                // stuff
            }
        }
    

    I'm not sure if this is a bug or I'm missing something...

    opened by BraisGabin 0
Owner
Square
Square
This project has been superseded by SuperSLiM, a layout manager for RecyclerView. I strongly recommend using SuperSLiM and not StickyGridHeaders.

StickyGridHeaders Replacement project at SuperSLiM This repository is abandoned and will no longer see any development or support. The replacement Sup

Tonic Artos 1.5k Nov 15, 2022
A simple and customizable two or three states Switch View

RMSwitch A simple View that works like a switch, but with more customizations. With the option to choose between two or three states. (from v1.1.0) **

Riccardo Moro 656 Dec 2, 2022
Custom UI control for android which is showing data as a segments and a value inside them.

Segmented Bar View for Android Custom UI control for android which is showing data as a segments and a value inside them. Screenshots Install From rep

GSPD 354 Nov 10, 2022
A small, easy to use android library for implementing flipping between views as seen in the popular Flipboard application

FlipView About This library is made to be very easy to use and at the same time be feature complete. With only a few lines of code you can have a flip

Emil Sjölander 924 Nov 10, 2022
You can store all your password, bank details, card details in one place and remember only one master PIN. The application works totally offline.

Keep Password An application where you can store all your password, bank details, card details in one place and remember only one master PIN. The appl

rıdvan 4 Apr 18, 2022
An interactive indicator to navigate between the different pages of a ViewPager

Android PagerSlidingTabStrip Interactive paging indicator widget, compatible with the ViewPager from the Android Support Library. Try out the sample a

Andreas Stütz 121 Sep 10, 2022
An interactive indicator to navigate between the different pages of a ViewPager

Android PagerSlidingTabStrip (default Material Design) This library is not maintained anymore and there will be no further releases. For most of the c

JPARDOGO 2.2k Jan 4, 2023
** A slide-out menu implementation, which allows users to navigate between views in your app.

MenuDrawer A slide-out menu implementation, which allows users to navigate between views in your app. Most commonly the menu is revealed by either dra

Simon Vig Therkildsen 2.6k Dec 8, 2022
eventbus-intellij-plugin 3.8 0.0 L1 Java Plugin to navigate between events posted by EventBus.

eventbus-intellij-plugin Plugin to navigate between events posted by EventBus. Post to onEvent and onEvent to Post Install There are two ways. Prefere

Shinnosuke Kugimiya 315 Aug 8, 2022
The CustomCalendarView provides an easy and customizable calendar to create a Calendar. It dispaly the days of a month in a grid layout and allows to navigate between months

Custom-Calendar-View To use the CustomCalendarView in your application, you first need to add the library to your application. You can do this by eith

Nilanchala Panigrahy 113 Nov 29, 2022
Loading layout is a container view that manages easy switching between loading, completed and other states of your screen with a single line.

Loading layout is a container view that manages easy switching between loading, completed and other states of your screen with a single line.

ValarTech 16 Jul 5, 2022
A simple library for automatically animating between Compose states.

compose-autotransition Status: Experimental A simple library for automatically animating between Compose states. var scale by remember { mutableStateO

Zach Klippenstein 64 Oct 13, 2022
Notify users when a new version of your Android app is available, and prompt them with the Play Store link. A port of the iOS library of the same name.

Siren for Android Notify users when a new version of your Android app is available, and prompt them with the Play Store link. This is a port of the iO

Quality Mobile Puzzle Apps 133 Nov 22, 2022
Photo picker library for android. Let's you pick photos directly from files, or navigate to camera or gallery.

ChiliPhotoPicker Made with ❤️ by Chili Labs. Library made without DataBinding, RxJava and image loading libraries, to give you opportunity to use it w

Chili Labs 394 Jan 2, 2023
Photo picker library for android. Let's you pick photos directly from files, or navigate to camera or gallery.

ChiliPhotoPicker Made with ❤️ by Chili Labs. Library made without DataBinding, RxJava and image loading libraries, to give you opportunity to use it w

Chili Labs 394 Nov 29, 2022
A lightweight library to help you navigate in compose with well typed functions.

TypedNavigation A lightweight library to help you navigate in compose with well typed functions. Installation: You can add this library to your projec

xmartlabs 23 Apr 7, 2022
Boat - A scoped and composable way to navigate

Boat Boat is an implementation of a scoped, simple and composable way to navigat

Bloder 5 Feb 9, 2022
Button for android with animations for transition and error states.

Transition Button Android Preview Expand animation: Shake animation: Installation Gradle dependencies { implementation 'com.royrodriguez:transitionbu

Roy Rodriguez 137 Jan 3, 2023
This project has been superseded by SuperSLiM, a layout manager for RecyclerView. I strongly recommend using SuperSLiM and not StickyGridHeaders.

StickyGridHeaders Replacement project at SuperSLiM This repository is abandoned and will no longer see any development or support. The replacement Sup

Tonic Artos 1.5k Nov 15, 2022