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

Overview

featured License

simple-stack

Simple Stack

Why do I want this?

To make navigation to another screen as simple as backstack.goTo(SomeScreen()), and going back as simple as backstack.goBack().

No more FragmentTransactions in random places. Predictable and customizable navigation in a single location.

What is Simple Stack?

Simple Stack is a backstack library (or technically, a navigation framework) that allows you to represent your navigation state in a list of immutable, parcelable data classes ("keys").

This allows preserving your navigation history across configuration changes and process death - this is handled automatically.

Each screen can be associated with a scope, or a shared scope - to easily share data between screens.

This simplifies navigation and state management within an Activity using fragments, views, or whatever else.

Using Simple Stack

In order to use Simple Stack, you need to add jitpack to your project root build.gradle.kts (or build.gradle):

// build.gradle.kts
allprojects {
    repositories {
        // ...
        maven { setUrl("https://jitpack.io") }
    }
    // ...
}

or

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

In newer projects, you need to also update the settings.gradle file's dependencyResolutionManagement block:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }  // <--
        jcenter() // Warning: this repository is going to shut down soon
    }
}

and then, add the dependency to your module's build.gradle.kts (or build.gradle):

// build.gradle.kts
implementation("com.github.Zhuinden:simple-stack:2.6.2")
implementation("com.github.Zhuinden:simple-stack-extensions:2.2.2")

or

// build.gradle
implementation 'com.github.Zhuinden:simple-stack:2.6.2'
implementation 'com.github.Zhuinden:simple-stack-extensions:2.2.2'

How do I use it?

You can check out the tutorials for simple examples.

Fragments

With Fragments, the Activity code typically looks like this:

class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler {
    private lateinit var fragmentStateChanger: DefaultFragmentStateChanger

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        fragmentStateChanger = DefaultFragmentStateChanger(supportFragmentManager, R.id.container)

        Navigator.configure()
            .setStateChanger(SimpleStateChanger(this))
            .install(this, findViewById(R.id.container), History.of(FirstScreen()))
    }

    override fun onBackPressed() {
        if (!Navigator.onBackPressed(this)) {
            super.onBackPressed()
        }
    }

    override fun onNavigationEvent(stateChange: StateChange) {
        fragmentStateChanger.handleStateChange(stateChange)
    }
}

Where FirstScreen looks like this:

@Parcelize // typically data class
class FirstScreen: DefaultFragmentKey() {
    override fun instantiateFragment(): Fragment = FirstFragment()
}

And FirstFragment looks like this:

class FirstFragment: KeyedFragment(R.layout.first_fragment) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val key: FirstScreen = getKey() // params
    }
}

After which going to the second screen is as simple as backstack.goTo(SecondScreen()).

Scopes

To simplify sharing data/state between screens, a screen key can implement ScopeKey.

The scope is described with a String tag, and services bound to that scope can be configured via ScopedServices.

Services bound to a ServiceBinder get lifecycle callbacks: ScopedServices.Registered, ScopedServices.Activated, or Bundleable.

This lets you easily share a class between screens, while still letting you handle Android's lifecycles seamlessly.

Using the simple-stack-extensions, this can be simplified using the DefaultServiceProvider.

It looks like this:

Navigator.configure()
    .setScopedServices(DefaultServiceProvider())
    /* ... */

And then:

@Parcelize // typically data class
class FirstScreen: DefaultFragmentKey(), DefaultServiceProvider.HasServices {
    override fun instantiateFragment(): Fragment = FirstFragment()

    override fun getScopeTag() = javaClass.name

    override fun bindServices(serviceBinder: ServiceBinder) {
        with(serviceBinder) {
            add(FirstScopedModel())
        }
    }
}

class FirstScopedModel : Bundleable, ScopedServices.Registered { // interfaces are optional
    ...
}

class FirstFragment : KeyedFragment(R.layout.first_fragment) {
    private val firstModel by lazy { lookup<FirstScopedModel>() }

    ...
}

class SecondFragment : KeyedFragment(R.layout.second_fragment) {
    private val firstModel by lazy { lookup<FirstScopedModel>() } // <- available if FirstScreen is in the backstack

    ...
}

And FirstScopedModel is shared between two screens.

Any additional shared scopes on top of screen scopes can be defined using ScopeKey.Child.

What are additional benefits?

Making your navigation state explicit means you're in control of your application.

Instead of hacking around with the right fragment transaction tags, or calling NEW_TASK | CLEAR_TASK and making the screen flicker - you can just say backstack.setHistory(History.of(SomeScreen(), OtherScreen()) and that is now your active navigation history.

Using Backstack to navigate allows you to move navigation responsibilities out of your view layer. No need to run FragmentTransactions directly in a click listener each time you want to move to a different screen. No need to mess around with LiveData<Event<T>> or SingleLiveData to get your "view" to decide what state your app should be in either.

class FirstScopedModel(private val backstack: Backstack) {
    fun doSomething() {
        // ...
        backstack.goTo(SecondScreen())
    }
}

Another additional benefit is that your navigation history can be unit tested.

assertThat(backstack.getHistory()).containsExactly(SomeScreen(), OtherScreen())

And most importantly, navigation (swapping screens) happens in one place, and you are in direct control of what happens in such a scenario. By writing a StateChanger, you can set up "how to display my current navigation state" in any way you want. No more ((MainActivity)getActivity()).setTitleText("blah"); inside Fragment's onStart().

Write once, works in all cases.

override fun onNavigationEvent(stateChange: StateChange) { // using SimpleStateChanger
    val newScreen = stateChange.topNewKey<MyScreen>() // use your new navigation state

    setTitle(newScreen.title);

    ... // set up fragments, set up views, whatever you want
}

Whether you navigate forward or backward, or you rotate the screen, or you come back after low memory condition - it's irrelevant. The StateChanger will always handle the scenario in a predictable way.

More information

For more information, check the wiki page.

Talk

For an overview of the "why" and the "what" of what Simple-Stack offers, you can check out this talk called Simplified Single-Activity Apps using Simple-Stack.

What about Jetpack Compose?

See https://github.com/Zhuinden/simple-stack-compose-integration/ for a default way to use composables as screens.

This however is only required if ONLY composables are used, and NO fragments. When using Fragments, refer to the official Fragment Compose interop guide.

For Fragment + Simple-Stack + Compose integration, you can also check the corresponding sample.

License

Copyright 2017-2021 Gabor Varadi

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
  • FragmentStateChanger with show/hide does not trigger proper lifecycle

    FragmentStateChanger with show/hide does not trigger proper lifecycle

    This is not an issue with the library, it's more of a heads up.

    In the MVP samples, the state changer uses attach/detach. This called lifecycle methods properly (up to onStart/onStop) but will destroy the fragment view.

    In the MVVM sample, the state changer uses show/hide. This won't destroy the fragment view but it also won't do any lifecycle at all.

    This means that all the LiveData's of all the opened fragments will keep observing as usual.

    I'm not sure how to avoid this because afaik there isn't a way to keep the view and do regular lifecycle. I'm not a fan of destroying the view because state will get lost. Navigation library does this and they refuse to allow show/hide.

    user support question design required blocked 
    opened by kakai248 45
  • Simple one-screen scope?

    Simple one-screen scope?

    Often times I have screens which are not part of a flow, where I don't need the same controller instance to be available in other screens. 1) But I still have to create a scope entry in my Scopes object. 2) Make my screen key implement ScopeKey and return that scope. 3) Expose the controller via dagger component even though the controller has @Inject annotated constructor. 4) Then add a when entry in ScopeConfiguration to return the controller (component.getMyController()) for my new scope.

    I could just do component.inject(this) in my fragments. But then the controller wouldn't survive config change/process death.

    Is it possible to make single-screen scopes easier?

    user support design required blocked 
    opened by jamolkhon 18
  • setHistory with force

    setHistory with force "recreation" even if Key already exists in backstack

    I've read the tutorial and have browsed other issues, but didn't find information how to implement such behavior. Or I just missed it.

    I need to setHistory() with a certain Key, but it is already in backstack, so now it just behaves like I would call goBack().

    I could just override getFragmentTag() by the Key and set an irrelavant value with default value like 0, and when I need to recreated this Key I would set Random.nextInt() just to distinguish the 2 Key's, but it's a hack.

    Am I missing something and there is some reasonable way to get this behavior?

    user support question 
    opened by uvaysss 17
  • onDestroy not called, leaving in broken state for following fragments

    onDestroy not called, leaving in broken state for following fragments

    simple-stack v1.14.0 Android Studio 3.3.2 android support 28.0.0, with supportFragments androidx.legacy:legacy-support-v13:1.0.0 - now also tested on androidx instead of old support, same problem

    I have a particular scenario where I can get simple-stack and/or androids fragmentManager into a broken state, and I'm at a loss of how to repair the issue. Sometimes when I pop a fragment (backstack.goBack), its onDestroy does not get called. When that happens (that onDestroy is missed on popping), then when I open a new version of same fragment, I get an invisible fragment, probably because my theme is transparant. The new fragment, does not get created because my FragmentStateChanger (directly from sample code-base), does not create a new fragment because fragmentManager already has a fragment by same tag. Indeed debugger inspection of fragmentManager.getActiveFragments() shows me 2 fragments, my main and the popped fragment. So it finds that fragment. But it never calls onCreateView, or onStart, or onResume on that (broken) fragment. Inspection shows the fragment is mCalled=true (whatever that means), and mDetached=false, and mRemoving=true.

    If I back out of my app, and open it again without killing VM, it creates a new fragment, but the backstack is now broken, I get the following execption when calling backstack.getHistory().fromTop(1), after opening the 2nd fragment

    java.lang.IllegalArgumentException: The provided offset value [1] was out of range: [-1; 1) at com.zhuinden.simplestack.History.fromTop(History.java:75)

    I found a hack that I can use in fragmentStateChanger, so I only reuse af fragment if fragment != null && !fragment.isRemoving(), thus I now still recreate a new fragments. But it leaks all the old broken fragments, the fragment manager holds on to those. I then 'fix' a the bad state, by calling system.exit on activity.onDestroy, if broken state detected (I might be able to do less, but for now it surfices.

    I just noticed that when I exit the activity then alle the broken fragments get their onDestroy() called during the activity's super.onDestroy().

    I can only reproduce the problem when navigating to my 'problem' fragment from my MainFragment which contains a Map fragment. Maybe that screws with android somehow. I don't see the problem when starting the 'problem' fragment from a deeper fragment. Also I don't see the problem when debugger is attached, but I suspect thats a timing issue.

    Any guess at whether its an Android bug or simple-stack or both? Or if its an Android bug, whether you can circumvent it somehow.

    user support done 
    opened by arberg 16
  • Recipes

    Recipes

    I'm currently migrating a complex app from Mortar+Flow to Mortar+SimpleStack

    I have a key that leads to different screens (a search screen, an introscreen, a progress screen that waits when a database is ready to go to the search screen, etc...) doing some kind of redirection.

    I was hoping to do it with simplestack but

    1. building my data and when it is ready, calling

    Navigator.getBackstack(this).setHistory(HistoryBuilder.from(Navigator.getBackstack(this)).build(), StateChange.REPLACE)

    to redirect to the search screen doesn't work as (I guess the same short circuit that in flow) prevents going from a screen with a key to the same screen with the same key (even if the asociated view changed)

    so, I tried 2) resetting the view with val top = Navigator.getBackstack(this).top<Any>() val newView = with (it) {newView(top)} L.error("state change top=$top newView=$newView") val container = containerViewGroup() ?: return container.removeViewAt(0) container.addView(newView, 0)

    it works, but now, when In this screen, I do a Naviagator.getBackstack(this).goTo(OtherScreenKey())

    I get an exception, this one : java.lang.IllegalArgumentException: The view [org.lakedaemon.dictionary.flow.JapaneseSearchView{421e1af0 V.E..... ........ 0,0-2560,1550 #7f1000fe app:id/flowSearchView}] contained no key! at com.zhuinden.simplestack.BackstackManager.persistViewToState(BackstackManager.java:244) at com.zhuinden.simplestack.navigator.Navigator.persistViewToState(Navigator.java:288) at com.zhuinden.simplestack.navigator.DefaultStateChanger$NavigatorStatePersistenceStrategy.persistViewToState(DefaultStateChanger.java:75) at com.zhuinden.simplestack.navigator.DefaultStateChanger.performViewChange(DefaultStateChanger.java:556) at com.zhuinden.simplestack.navigator.DefaultStateChanger.performViewChange(DefaultStateChanger.java:541) at com.zhuinden.simplestack.navigator.DefaultStateChanger$2.stateChangeComplete(DefaultStateChanger.java:524) at com.zhuinden.simplestack.navigator.DefaultStateChanger$NoOpStateChanger.handleStateChange(DefaultStateChanger.java:42) at com.zhuinden.simplestack.navigator.DefaultStateChanger.handleStateChange(DefaultStateChanger.java:517) at com.zhuinden.simplestack.BackstackManager$1.handleStateChange(BackstackManager.java:76) at com.zhuinden.simplestack.Backstack.changeState(Backstack.java:306) at com.zhuinden.simplestack.Backstack.beginStateChangeIfPossible(Backstack.java:272) at com.zhuinden.simplestack.Backstack.enqueueStateChange(Backstack.java:254) at com.zhuinden.simplestack.Backstack.goTo(Backstack.java:164) at org.lakedaemon.android.AndroidKt.goToScreenFor(Android.kt:594) at org.lakedaemon.dictionary.UtilsKt.goToScreenFor(utils.kt:25) at org.lakedaemon.dictionary.list.WordViewHolder.onSingleTapConfirmed(WordViewHolder.kt:48) at org.lakedaemon.android.listener.DoubleTapListener.onSingleTapConfirmed(utils.kt:40) at android.view.GestureDetector$GestureHandler.handleMessage(GestureDetector.java:315) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:157) at android.app.ActivityThread.main(ActivityThread.java:5350) 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:1265) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081) at dalvik.system.NativeStart.main(Native Method)

    What should I do to fix this ?

    user support question 
    opened by Lakedaemon 16
  • How do I know if there is a fragment from savedInstanceState?

    How do I know if there is a fragment from savedInstanceState?

    Right now I have this in my activity's onCreate:

    backstackDelegate.onCreate(savedInstanceState,
                    getLastCustomNonConfigurationInstance(),
    HistoryBuilder.single(HomeKey.create()));
    

    But when the application is restored from instanceState, it has the Home Fragment above whatever fragment the activity was killed with. What can I do to prevent this?

    user support 
    opened by johnernest02 15
  • onRetainCustomNonConfigurationInstance is deprecated by androidx because

    onRetainCustomNonConfigurationInstance is deprecated by androidx because "why not"

    https://developer.android.com/jetpack/androidx/androidx-rn#2018-nov

    This means I need to revise the BackstackDelegate API and decide how to track in onCreate that now I can no longer get a nullable non-config to decide which one to use.

    Although I'm guessing the idea is the same as what I do in BackstackHost.

    wontfix 
    opened by Zhuinden 14
  • Investigate setting ScopedServices with deferred initialization

    Investigate setting ScopedServices with deferred initialization

    This is still an issue when using Navigator to create a backstack (backstack is not accessible until Navigator.install() is called). Is there a better workaround rather than creating BackstackHolder?

    @matejdro I see a new issue wasn't opened. Does Navigator.configure().setDeferredInitialization(true) + Navigator.executeDeferredInitialization suit your needs? You effectively get a restored Backstack but without setting the StateChanger.

    user support question feature request done 
    opened by Zhuinden 13
  • Possibly introduce a simpler built-in way of result passing between keys

    Possibly introduce a simpler built-in way of result passing between keys

    Originally, samples showed both how to create a MessageQueue class that could solve this, and also how to use a shared ScopedService + EventEmitter to solve this.

    Google has its own way now:

    https://issuetracker.google.com/issues/79672220

    This has been fixed internally and will be available in the Navigation 2.3.0-alpha02 release.

    With this change, you can now get a SavedStateHandle from a NavBackStackEntry and use the given SavedStateHandle to pass a result between two entries.

    If Fragment A needs a result from Fragment B..

    A should get the savedStateHandle from the currentBackStackEntry, call getLiveData providing a key and observe the result.

    findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<Type>("key")?.observe(
        viewLifecycleOwner) {result ->
        // Do something with the result.
    }
    

    B should get the savedStateHandle from the previousBackStackEntry, and set the result with the same key as the LiveData in A

    findNavController().previousBackStackEntry?.savedStateHandle?.set("key", result)
    

    Because this uses SavedStateHandle, all results need to be types that can be added to a Bundle (i.e Parcelable, Serializable, etc.).

    Technically a SavedStateHandle is analogous to a StateBundle. So if every key has an associated StateBundle, then you can use that for result passing.

    .....now just hold on a second, isn't this already possible with Backstack.getSavedState() + setBundle()? 🤔

    https://github.com/Zhuinden/simple-stack/blob/7d51efbf2fe393651b2bb4a70f5afa2671c64e1c/simple-stack/src/main/java/com/zhuinden/simplestack/Backstack.java#L580-L596

    https://github.com/Zhuinden/simple-stack/blob/7d51efbf2fe393651b2bb4a70f5afa2671c64e1c/simple-stack/src/main/java/com/zhuinden/simplestack/SavedState.java#L34

    enhancement design required done 
    opened by Zhuinden 13
  • Can I use this library easily with Data-binding mvvm-pattern?

    Can I use this library easily with Data-binding mvvm-pattern?

    Hey at first thanks for the good samples. I want to use your library, but till now I didn't found out if it is possible to switch the fragments easily in the viewmodels. Does your library support that? If not how would you solve this issue?

    user support 
    opened by fvermeulen 13
  • when press the back button, it execute twice and then exit the app

    when press the back button, it execute twice and then exit the app

    I set three tab, when go to tab2.1 from tab2, and then press the back button, it goes back first then exit the app (seems it execute twice), I'm new to android and especially navigation, anyone knows what the problem is ?

    user support done 
    opened by Jackyaung 12
  • TargetSdkVersion 34 replaces onBackPressed() with OnBackInvokedCallback

    TargetSdkVersion 34 replaces onBackPressed() with OnBackInvokedCallback

    With the "advent" of OnBackPressedDispatcher, we already had to do this:

    class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler {
        private val backPressedCallback = object: OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (!Navigator.onBackPressed([email protected])) {
                    this.remove() // this is the only safe way to invoke onBackPressed while cancelling the execution of this callback
                    onBackPressed() // this is the only safe way to invoke onBackPressed while cancelling the execution of this callback
                    [email protected](this) // this is the only safe way to invoke onBackPressed while cancelling the execution of this callback
                }
            }
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            // ...
            onBackPressedDispatcher.addCallback(backPressedCallback) // this is required for `onBackPressedDispatcher` to work correctly
            // ...
        }
    
        @Suppress("RedundantModalityModifier")
        override final fun onBackPressed() { // you cannot use `onBackPressed()` if you use `OnBackPressedDispatcher`
            super.onBackPressed() // `OnBackPressedDispatcher` by Google effectively breaks all usages of `onBackPressed()` because they do not respect the original contract of `onBackPressed()`
        }
    }
    

    However, now, onBackPressed() is deprecated in Android Tiramisu, replaced with onBackPressedCallback and onBackInvokedCallback, which seem to have the same design problems as OnBackPressedDispatcher: namely, that once you registered to receive a callback and you have received said callback, you can't cancel the request.

    Clearly, the people at Google writing this code have never heard of event bubbling, which has worked in most UI frameworks in the past 15+ years.

    However, this means we might need to bite the bullet and replace the current event-bubbling approach of ScopedServices.HandlesBack with tinkering with exposing if a service on top is currently handling back or not.

    This is quite a heavy change and will require Android T to come out.

    design required breaking? 
    opened by Zhuinden 0
  • Navigation action that removes part of the stack without altering the top key can cause detached fragments to not be removed when used with SimpleStateChanger

    Navigation action that removes part of the stack without altering the top key can cause detached fragments to not be removed when used with SimpleStateChanger

    Reported by JCR:

    • Remove a key inside the stack
        val desiredKeyToRemove = backstack.getHistory<SimpleStackScreenKey>()
            .firstOrNull { it::class == keyToReplace }
    
        if (desiredKeyToRemove == null) {
            return
        } else {
            backstack.setHistory(
                History.builderFrom(backstack)
                    .remove(desiredKeyToRemove)
                    .build<Key>(),
                REPLACE
            )
        }
    
    • Don't trigger navigation action with this (top key does not change)

    https://github.com/Zhuinden/simple-stack/blob/d003d93512be06c1a3899a6ef434d848f1aae4d3/simple-stack/src/main/java/com/zhuinden/simplestack/SimpleStateChanger.java#L40-L44

    • Key is no longer in the stack the next time top key changes, so fragment does not get removed by DefaultFragmentStateChanger (stays detached)

    https://github.com/Zhuinden/simple-stack-extensions/blob/1296f6a07964be31220f4ab6b6af8724d6511f57/fragments/src/main/java/com/zhuinden/simplestackextensions/fragments/DefaultFragmentStateChanger.java#L189


    Conclusion: SimpleStateChanger contains the built-in check from the original square/flow

    https://github.com/square/flow/blame/438663b5755d7db124ed3d62889844bf565b15f7/flow/src/main/java/flow/KeyDispatcher.java#L65-L74

    However in this case it doesn't work (as fragments have memory, while flow used to be used only with views (that have no memory)).

    Proposal: what if

    1.) SimpleStateChanger also calls "onNavigationEvent" when state change is not content equals (but top is the same)

    2.) DefaultFragmentStateChanger does not apply animations for fragment transactions that don't change the top, but still applies the state changes themselves

    However, the short-circuit for "is top the same" is very old. By making it "content equals", then if anyone ever relied on it actually always being "is top the same" would need to do the check themselves, which is behavior breaking change. :sweat:

    bug design required 
    opened by Zhuinden 3
  • DialogFragment and BottomSheetDialogFragment as part of navigation stack

    DialogFragment and BottomSheetDialogFragment as part of navigation stack

    I really like this libary and was wondering how to implement navigation for dialog and bottomsheet fragments.

    Their are 2 reasons for this:

    • consistent navigation also for dialog and bottomsheet;
    • possibility to show another fullscreen fragment on top of a dialog or bottomsheet, because the usual one is shown with it's own window.

    I was thinking about a own backstack for each fragment, but with a custom StateChanger where instead of attach/detach or show/hide would be used add/remove so that the fragments that are underneath could be visible.

    Maybe you could give a hint about how you would do this kind of navigation or maybe why I should not do this, maybe there are some downsides to this.

    user support design required 
    opened by uvaysss 5
  • Consider a way to connect a

    Consider a way to connect a "child nested stack" to a "parent stack" similarly to `setParentComposition`

    While multi-stack is not the focus of the library by default, if it were possible to integrate against Compose using actual composable functions instead of externally stored state (see the behavior of OnBackPressedDispatcher for why this is actually potentially impossible without manually hijacking onBackPressed in Activity), then it would be possible to introduce a backstack at any "nesting level". After all, what needs to be propagated is back events and service lookups.

    Although can you truly keep alive a child composition's "belongings" (read: scoped services / global services) in a retained scope? Not even Compose has figured this out without the use of Fragments to host ViewModelStores.

    design required one day maybe 
    opened by Zhuinden 2
Releases(2.6.4)
  • 2.6.4(Apr 21, 2022)

    Simple-Stack 2.6.4 (2022-04-21)

    • FIX: Attempt at fixing a crash related to LinkedHashMap.retainAll() specifically on Android 6 and Android 6.1 devices (#256).

    • 2.6.3 had an issue with maven-publish and transitive dependencies not getting resolved for consumers, therefore it is skipped.

    Source code(tar.gz)
    Source code(zip)
  • 2.6.2(Jun 7, 2021)

    Simple Stack 2.6.2 (2021-06-07)

    • ADDED: Backstack.canSetScopeProviders().

    This is in conjunction with the 2.6.1 change, while making it safe to use them without extra checks such as if(lastNonConfigurationInstance == null) {. (see https://github.com/Zhuinden/simple-stack/issues/243)

    Source code(tar.gz)
    Source code(zip)
  • 2.6.1(May 3, 2021)

    Simple-Stack 2.6.1 (2021-05-03)

    • CHANGE: Backstack.setScopedServices(ScopedServices), Backstack.setGlobalServices(GlobalServices), and Backstack.setGlobalServices(GlobalServices.Factory) can now be called after setup(), but before setStateChanger().

    This allows setting the scoped services on the backstack instance, when using deferred initialization, before the initial state change is run.

    Source code(tar.gz)
    Source code(zip)
  • 2.6.0(Mar 8, 2021)

    Simple-Stack 2.6.0 (2021-03-08)

    • ADD: Backstack.addRetainedObject(objectTag, retainedObject), Backstack.hasRetainedObject(objectTag), Backstack.removeRetainedObject(objectTag), Backstack.getRetainedObject(objectTag).

    This allows simpler way of persisting an object instance across configuration changes.

    Also, retained objects that implement Bundleable are given state restoration callbacks.

    • UPDATE: Add simple-stack-example-multistack-nested-fragment that shows how to create a fragment that has Backstacks for its child fragments, thus creating true multi-stack apps using nested backstacks.

    • DEPRECATED: Backstack.addCompletionListener, Backstack.removeCompletionListener, Backstack.removeCompletionListeners.

    These were the same as addStateChangeCompletionListener and removeStateChangeCompletionListener, and should not have been duplicate APIs.

    Source code(tar.gz)
    Source code(zip)
  • 2.5.0(Dec 16, 2020)

    Simple Stack 2.5.0 (2020-12-16)

    • ADD: Backstack.exitScope(scopeTag), Backstack.exitScope(scopeTag, direction) and Backstack.exitScopeTo(scopeTag, targetKey, direction).

    If a scope is found, the backstack now allows exiting from it. Providing a target allows exiting into a new target key.

    • ADD: AsyncStateChanger for convenience.

    Mirroring the addition of SimpleStateChanger for synchronous state changes, AsyncStateChanger is for async state changes (while still no longer having to remember checking for the same key being provided using isTopNewKeyEqualToPrevious).

    • UPDATE: state-bundle is updated to 1.4.0 (add a few missing @Nullables that became platform types instead of nullables).
    Source code(tar.gz)
    Source code(zip)
  • 2.4.0(Jul 8, 2020)

    Simple-Stack 2.4.0 (2020-07-08)

    • SIGNATURE CHANGE: GlobalServices.Factory now receives Backstack parameter in create(). (#231)

    I'm aware this is technically "breaking", but the effect should be minor, and hopefully shouldn't cause problems.`

    The Backstack cannot be added as a service directly, but it can be added as an alias.

    • FIX: GlobalServices.Factory's create() method was non-null, but @Nonnull was missing.

    • MINOR FIX: Adding the Backstack from serviceBinder.getBackstack() with addService() would cause a loop in toBundle(). Now it explicitly throws IllegalArgumentException instead sooner (not reported before).

    • DEPRECATED: backstack.removeAllStateChangeCompletionListeners(). This was added "for convenience", but in reality it is not a good/safe API, and it should not exist.

    • UPDATE: Create and releasesimple-stack-extensions:2.0.0 for default scoping and default fragment behaviors.

    • ADD: GlobalServices.SCOPE_TAG to make it possible to see the scope tag of global services without relying on internals.

    Source code(tar.gz)
    Source code(zip)
  • 2.3.2(Apr 11, 2020)

    Simple-Stack 2.3.2 (2020-04-11)

    • FIX: Bug introduced in 2.3.1, using backstack.removeAllStateChangeCompletionListeners() would remove an internal completion listener and therefore scope activation callbacks would stop being dispatched.
    Source code(tar.gz)
    Source code(zip)
  • 2.3.1(Mar 30, 2020)

    Simple-Stack 2.3.1 (2020-03-31)

    • FIX: Ensure that if multiple navigation actions are enqueued, then scope activation dispatch only occurs for the final state change, instead of potentially running into an AssertionError. (#220, thanks @valeriyo)

    If you use either ScopeKey or ScopeKey.Child, it is advised to update, and get this bugfix.

    Source code(tar.gz)
    Source code(zip)
  • 2.3.0(Feb 27, 2020)

    Simple-Stack 2.3.0 (2020-02-27)

    • CHANGE: Remove dependency on android.support.annotation.*. With that, there should be no dependency from the library on either android.support.* or androidx.*.

    Replaced it using javax.annotation.Nullable and javax.annotation.Nonnull provided by api("com.google.code.findbugs:jsr305:3.0.2").

    • UPDATE: state-bundle is updated to 1.3.0 (Remove dependency on android.support.annotation.*, replace with javax.annotation.*).

    With these changes, Jetifier should no longer be needed when using Simple-Stack.

    Source code(tar.gz)
    Source code(zip)
  • 2.2.5(Feb 18, 2020)

    Simple-Stack 2.2.5 (2020-02-18)

    • CHANGE: Backstack.getSavedState(Object key).getBundle() is now initialized to an empty StateBundle instead of null (but is still nullable because of setBundle()).

    • FIX: Backstack.persistViewToState(Object key) no longer creates a new SavedState instance, and uses getSavedState to re-use (or create) the existing one.

    • FIX: Ensure that backDispatchedServices is also cleared after execution of dispatchBack.

    Source code(tar.gz)
    Source code(zip)
  • 2.2.4(Jan 30, 2020)

  • 2.2.3(Jan 22, 2020)

    Simple-Stack 2.2.3 (2020-01-23)

    • FIX: ScopedService.Activated callback could immediately execute a navigation action, which if destroyed the current scope, then it could result in an AssertionError (#215).

    • ADD: SimpleStateChanger for convenience when a StateChanger is not intended to take asynchronous execution into account.

    Source code(tar.gz)
    Source code(zip)
  • 2.2.2(Jan 17, 2020)

    Simple-Stack 2.2.2 (2020-01-17)

    • FIX: Ensure that unused keys can be GCed inside ScopeManager (previously they could be kept alive in a map, #213).
    Source code(tar.gz)
    Source code(zip)
  • 2.2.1(Jan 12, 2020)

    Simple Stack 2.2.1 (2020-01-12)

    • FIX: The explicit parent scope chain was not always resolved correctly if a key is only a ScopeKey.Child, but not a ScopeKey, and the key did not register any new scopes (as all scopes defined by the ScopeKey.Child had already been registered by previous keys).

    This could provide incorrect results in findScopesForKey(key, EXPLICIT), and could skip a HandlesBack service in the current top key's explicit parent chain.

    Source code(tar.gz)
    Source code(zip)
  • 2.2.0(Dec 30, 2019)

    Simple-Stack 2.2.0 (2019-12-30)

    • ADDED: ScopedService.HandlesBack.

    When a service implements HandlesBack, then when Backstack.goBack() is called, it is first dispatched across the current active explicit scope chain.

    This allows handling "back", without having to dispatch it through the active view hierarchy (in order to get access to it in scoped services).

    • FIX: Backstack.moveToTop() did not re-order the scope hierarchy according to the new active keys (as the scope order depended on construction order, but existing scopes were not recreated).

    This could have been a problem if services used the same name across multiple scopes, and the keys were re-ordered in place (not add/remove).

    • ADDED: Backstack.setGlobalServices(GlobalServices.Factory) and its BackstackDelegate/Navigator equivalent.

    This allows delaying the instantiation of services when the global scope is actually being created, rather than providing them immediately.

    Please note that providing a GlobalServices.Factory will override whatever GlobalServices was previously set.

    Also note that the GlobalServices.Factory should not be an anonymous inner class / lambda / inner class, as it is kept for configuration changes.

    • DEPRECATED: BackstackDelegate.

    With the renaming of BackstackManager to Backstack in 2.0.x, it's become easier to use Backstack directly than juggling the BackstackDelegate.

    Also, using Navigator with Fragments seems to have no side-effects, therefore this is now the preferred approach (since setting a non-default state changer calls setShouldPersistContainerChild(false), also since 2.0.x).

    Therefore, using Navigator is now preferred over BackstackDelegate.

    • FIX: Backstack.forceClear() now calls finalizeScopes() first to ensure that scoped services are also properly reset.
    Source code(tar.gz)
    Source code(zip)
  • 2.1.2(Oct 10, 2019)

    Simple Stack 2.1.2 (2019-10-10)

    • BEHAVIOR CHANGE: The navigation operations goBack(), replaceTop(), jumpToRoot(), goUp(), goUpChain(), and goTo() (when going to existing element) are now considered "terminal" operations.

    Terminal operation means that actions (that are not setHistory) called on the Backstack are ignored while the state change of the terminal action has not been completed yet.

    This is done to eliminate the possibility of enqueueing incorrect "forward" navigation immediately when a "back" navigation is happening, that could potentially create "illegal" navigation history.

    Illegal navigation history is a problem when using implicit scopes, as with the right button mashing, you could potentially "skip" an expected screen, and not have registered its services.

    Therefore, the possibility of this scenario is now blocked.

    Source code(tar.gz)
    Source code(zip)
  • 2.1.1(Sep 26, 2019)

    Simple Stack 2.1.1 (2019-09-26)

    • FIX: Make findScopesForKey work consistently even if a key is not a ScopeKey.

    • FIX: Add missing @NonNull on Context argument on some methods of Navigator.

    Source code(tar.gz)
    Source code(zip)
  • 2.1.0(Sep 25, 2019)

    Simple Stack 2.1.0 (2019-09-25)


    • FIX: Ensure that while navigation is in progress, lookupService() (and other service lookup methods) can access all currently built scopes, rather than only the latest navigation history currently being navigated to.

    This fix is crucial when lookupService is used inside onFinishInflate method of views inflated by ViewPager adapters; and returning from process death, navigation to a different screen is immediate (f.ex. deep-linking via notifications).

    Source code(tar.gz)
    Source code(zip)
  • 2.0.3(May 20, 2019)

    Simple Stack 2.0.3 (2019-05-20)

    • ADDED: ServiceBinder.addAlias() to allow adding an alias to an added service: available for look-up, but without getting callbacks by this registration (thus avoiding unnecessary toBundle() calls for a multi-registered service).
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Apr 30, 2019)

    Simple Stack 2.0.0 (2019-04-30)

    MAJOR API BREAKING CHANGES! To create better consistency and naming, certain core APIs were renamed, moved around, restructured, etc.

    This means that 1.x and 2.x shouldn't be used in a project at the same time, because they're not compatible.

    • BREAKING CHANGE: BackstackManager is now Backstack.

    What was called Backstack is now called NavigationCore and is an internal class (not part of public API).

    (!) This also means that the new Backstack(List<?>) constructor is replaced with new Backstack(); backstack.setup(List<?>).

    All sane public APIs of NavigationCore were moved over to the new Backstack (think navigation-related ones).

    This means that wherever you used to receive a Backstack, now you still receive a "Backstack", but it has additional functionality (such as lookupService).

    As a result, the following methods no longer exist:

    • BackstackDelegate.getManager() is removed

    • Navigator.getManager() is removed

    • ServiceBinder.getManager() is removed

    With that, StateChange.getBackstack() now returns what was previously the BackstackManager, allowing access to scope-related functions.

    • BREAKING CHANGE: ScopedServices.Scoped is removed, and completely replaced with ScopedServices.Registered.

    • BREAKING CHANGE: ScopedServices.Activated behavior change, now only called once rather than multiple times if the service is in an active explicit parent chain.

      • onScopeActive()/onScopeInactive() -> onServiceActive()/onServiceInactive().

    Previously, you could get multiple callbacks to onScopeActive per each activated scope the service was in, now it is tracked per service instead and only called for 0->1 and 1->0.

    • BREAKING CHANGE: setShouldPersistContainerChild(true) was the default, now it is default to false. It's only true by default if the DefaultStateChanger is used.

    If you use a custom-view-based setup and a custom state changer, please make sure to set Navigator.configure().setShouldPersistContainerChild(true).install(...).

    • BREAKING CHANGE: ScopedServices.ServiceBinder is now moved to top-level, now accessible as ServiceBinder.

    • BREAKING CHANGE: ViewChangeHandler.CompletionCallback renamed to ViewChangeHandler.ViewChangeCallback.

    • BREAKING CHANGE: StateKey is renamed to DefaultViewKey.

    • BREAKING CHANGE: HistoryBuilder is now History.Builder. The deprecated HistoryBuilder factory methods were removed.

    • BREAKING CHANGE:

      • StateChange.getPreviousState() -> StateChange.getPreviousKeys()
      • StateChange.getNewState() -> StateChange.getNewKeys()
      • StateChange.topPreviousState() -> StateChange.topPreviousKey()
      • StateChange.topNewState() -> StateChange.topNewKey()
      • StateChange.isTopNewStateEqualToPrevious() -> StateChange.isTopNewKeyEqualToPrevious()
    • BREAKING CHANGE: StateChange.backstack() -> StateChange.getBackstack().

    • BREAKING CHANGE: ServiceBinder method renaming for sake of consistency.

      • ServiceBinder.add() -> ServiceBinder.addService()
      • ServiceBinder.has() -> ServiceBinder.hasService()
      • ServiceBinder.get() -> ServiceBinder.getService()
      • ServiceBinder.canFind() -> ServiceBinder.canFindService()
      • ServiceBinder.lookup() -> ServiceBinder.lookupService()
      • ServiceBinder.canFindFrom() -> ServiceBinder.canFindFromScope()
      • ServiceBinder.lookUpFrom() -> ServiceBinder.lookupFromScope()
    • CHANGE/FIX: Added missing @NonNull annotation on KeyContextWrapper.getKey(). Now throws exception if the key is not found (previously returned null).

    Source code(tar.gz)
    Source code(zip)
  • 1.14.1(Apr 26, 2019)

    Simple-Stack 1.14.1 (2019-04-26)

    • FIX: onServiceUnregistered() was called multiple times if the service was being unregistered from a scope where it was registered multiple times.

    • SAMPLE UPDATE: Safer version of the FragmentStateChanger that handles re-entrancy of back and going to the same target as where we were (handle fragment.isRemoving).

    • SAMPLE UPDATE: MVP/MVVM samples have a better packaging structure.

    Source code(tar.gz)
    Source code(zip)
  • 1.14.0(Mar 16, 2019)

    Simple-Stack 1.14.0 (2019-03-16)

    • ADD: ScopedServices.Registered to receive a service lifecycle callback when a service is added to a scope for the first time (it was not in any other scopes).

    • ADD: findScopesForKey(Object key, ScopeLookupMode mode) to retrieve the list of scopes accessible from a given key.

    • FIX: A service registered in multiple scopes would receive fromBundle(stateBundle) callback in each scope where it was registered and when that given scope was entered, restoring potentially outdated state on back navigation.

    Now, a service will only receive fromBundle callback before its onServiceRegistered() callback, and not before each onEnterScope(scopeTag) callbacks.

    • FIX: during backstackManager.finalizeScopes(), onExitScope and onScopeInactive were dispatched in an incorrect order across nested explicit parents.

    • CHANGE: persistViewToState()/restoreViewFromState() now use a separate bundle from the one that's publicly visible on SavedState.

    Source code(tar.gz)
    Source code(zip)
  • 1.13.4(Mar 10, 2019)

    Simple-Stack 1.13.4 (2019-03-10)

    • FIX: calling backstackManager.finalizeScopes() multiple times now results in consistent and defined behavior (namely, it gets ignored).

    • CHANGE: navigation that occurs after backstackManager.finalizeScopes() will now trigger reconstruction and proper callbacks of services in a consistent manner.

    • FIX: ScopedServices.Activated's onScopeInactive() during scope finalization was called in order from explicit parent to child, instead of child to explicit parent.

    Source code(tar.gz)
    Source code(zip)
  • 1.13.3(Feb 27, 2019)

    Simple-Stack 1.13.3 (2019-02-27)

    • FIX: NPE when canFindFromScope() was used on an uninitialized stack, instead of returning false.

    • ADD: ScopeLookupMode for canFindFromScope() and lookupFromScope() methods, which allows restricting the lookup only to the explicit parent chain).

    • ADD: setGlobalServices() to allow setting global services (that functions as the last parent of any parent chains).

    Source code(tar.gz)
    Source code(zip)
  • 1.13.2(Feb 5, 2019)

    Simple-Stack 1.13.2 (2019-02-05)

    • FIX: Fix that a service registered multiple times in the same scope with different tags would receive service lifecycle callbacks as many times as it was registered.

    • ADD: Convenience method stateChange.isTopNewStateEqualToPrevious() to replace stateChange.topNewState<Any>() == stateChange.topPreviousState() condition check. It does the same thing, but maybe it's a bit easier to read.

    • FIX: Fix a typo which resulted in not throwing if the provided service tag was null (whoops.)

    • UPDATE: Backstack now checks if altering methods are called from the thread where the backstack was created.

    Source code(tar.gz)
    Source code(zip)
  • 1.13.1(Nov 25, 2018)

    Simple Stack 1.13.1 (2018-11-25)

    • UPDATE: Added simple-stack-example-scoping to showcase the usage of ScopeKey.Child, with Fragments and Navigator.

    • ADDED: lookupFromScope() and canFindFromScope() methods that begin the lookup from the specified scope, instead of the active-most one. This is to allow safer look-ups when the same service tag is used in different scopes.

    • UPDATE: Better error message if the scope does not exist for lookup (also provides the currently accessed scopes).

    Source code(tar.gz)
    Source code(zip)
  • 1.13.0(Sep 10, 2018)

    Simple-Stack 1.13.0 (2018-09-10)

    • ADDED: Adds ScopeKey.Child interface, which allows the definition of explicit parent hierarchy of a given scope.

    Even by default, there is an implicit hierarchy between screens: it is possible to look up services defined by previous keys.

    However, there are times when we must define scopes that are supersets of multiple screens. In this case, we know we are on a given screen, within a given state, and we require a superscope to exist that is shared across multiple screens.

    In this case, the key can define an explicit parent hierarchy of scopes. These scopes are created before the key's own scope (assuming the key is also a ScopeKey).

    The parent scopes are only destroyed after all their children are destroyed.

    lookupService() prefers explicit parents, however will also continue to seek the service across implicit parents, and their explicit parent chain as well.

    Source code(tar.gz)
    Source code(zip)
  • 1.12.3(Sep 2, 2018)

    Simple Stack 1.12.3 (2018-09-02)

    • CHANGE: When lookupService cannot find the service, the exception message is improved (and tells you what could be wrong in your configuration).

    • UPDATE: mvp-view, mvp-fragments and mvvm-fragments samples now use ScopedServices (and Bundleable) to retain presenters/viewmodels across config changes and have their states persisted/restored across process death.

    Source code(tar.gz)
    Source code(zip)
  • 1.12.2(Aug 29, 2018)

    Simple Stack 1.12.2 (2018-08-29)

    • CHANGE: AnimatorViewChangeHandler has an overridable method called resetPreviousViewValues(), which receives the previous view after animation is complete.

    • CHANGE: FadeViewChangeHandler and SegueViewChangeHandler now reset alpha = 1f and translationX = 0f respectively, after animation is complete and the view is removed.

    Source code(tar.gz)
    Source code(zip)
  • 1.12.1(Aug 27, 2018)

    Simple-Stack 1.12.1 (2018-08-28)

    • ADDED: ScopedServices.Activated. Implementing Activated for a scoped service makes the service receive a callback when the scope it is bound to becomes the top-most scope, and when it stops being the top-most scope.

    There are strict ordering guarantees that onEnterScope, onScopeActive, onScopeInactive, onExitScope are called in this order.

    onScopeInactive is called in reverse order (just like onExitScope).

    When navigating from one scope to another scope, the new scope becomes active before the previous scope becomes inactive.

    Source code(tar.gz)
    Source code(zip)
Owner
Gabor Varadi
I'm an Android developer. EpicPandaForce @ SO. I maintain Simple-Stack. Say no to the Fragment backstack!
Gabor Varadi
A small navigation library for Android to ease the use of fragment transactions & handling backstack (also available for Jetpack Compose).

A small navigation library for Android to ease the use of fragment transactions & handling backstack (also available for Jetpack Compose).

Kaustubh Patange 84 Sep 11, 2022
Memory efficient android library for managing individual fragment backstack.

fragstack : Android library for managing individual fragment backstack. An Easy to use library for managing individual fragment back stack as Instagra

Abhishesh 21 Feb 6, 2021
Okuki is a simple, hierarchical navigation bus and back stack for Android, with optional Rx bindings, and Toothpick DI integration.

Okuki A simple, hierarchical navigation bus and back stack for Android, with optional Rx bindings, and Toothpick integration for automatic dependency-

Cain Wong 144 Feb 11, 2022
NavigationAndFragments - A use case for fragments and navigation

NavigationAndFragments A use case for fragments and navigation. To implement this use case, follow these steps : Create a new fragment navigation xml

Google Developers 3 Sep 15, 2022
NavigationComponent-SendingData - Android Navigation Component : Pass value (arguments) in Fragments

NavigationComponent-SendingData Android Navigation Component : Pass value (argum

Reyhaneh Ezatpanah 1 Dec 28, 2021
A multi back stack android navigation

Labyrinth A multi back stack android navigation MIT License - Copyright (c) 2020 Abanoub Milad Nassief Hanna [email protected] @Linkedin @Github Scr

Abanoub Milad Nassief Hanna 7 Dec 28, 2021
An Android library for managing multiple stacks of fragments

FragNav Android library for managing multiple stacks of fragments (e.g., Bottom Navigation , Navigation Drawer). This library does NOT include the UI

Nic Capdevila 1.4k Sep 22, 2022
Activities, Intents, Fragments e o componente de navegação - Kotlin - Android Studio

Words App This folder contains the source code for the Words app codelab. Introduction Words app allows you to select a letter and use Intents to navi

Ademir Oliveira 0 Nov 23, 2021
New style for app design simple bottom navigation with side navigation drawer UI made in Jetpack Compose.😉😎

BottomNavWithSideDrawer New style for app design simple bottom navigtaion with side navigation drawer UI made in Jetpack Compose. ?? ?? (Navigation Co

Arvind Meshram 4 Jul 6, 2022
Alligator is a modern Android navigation library that will help to organize your navigation code in clean and testable way.

Alligator Alligator is a modern Android navigation library that will help to organize your navigation code in clean and testable way. Features Any app

Artur Artikov 292 Jul 7, 2022
A small and simple, yet fully fledged and customizable navigation library for Jetpack Compose

A small and simple, yet fully fledged and customizable navigation library for Jetpack Compose

Vitali Olshevski 218 Sep 20, 2022
Animated Tab Bar is an awesome navigation extension that you can use to add cool, animated and fully customizable tab navigation in your apps

Animated Tab Bar is an awesome navigation extension that you can use to add cool, animated and fully customizable tab navigation in your apps. The extension provides handy methods and properties to change the behaviour as well as the appearance of the navigation bar.

Zain Ul Hassan 3 Jul 27, 2022
Android multi-module navigation built on top of Jetpack Navigation Compose

MultiNavCompose Android library for multi-module navigation built on top of Jetpack Navigation Compose. The goal of this library is to simplify the se

Jeziel Lago 21 Aug 31, 2022
DSC Moi University session on using Navigation components to simplify creating navigation flow in our apps to use best practices recommended by the Google Android Team

Navigation Components Navigate between destination using safe args How to use the navigation graph and editor How send data between destinations Demo

Breens Mbaka 6 Feb 3, 2022
Navigation Component: THE BEST WAY to create navigation flows for your app

LIVE #017 - Navigation Component: A MELHOR FORMA de criar fluxos de navegação para o seu app! Código fonte do projeto criado na live #017, ensinando c

Kaique Ocanha 4 Jun 15, 2022
Navigation Drawer Bottom Navigation View

LIVE #019 - Toolbar, Navigation Drawer e BottomNavigationView com Navigation Com

Kaique Ocanha 6 Jun 15, 2022
Bottom-App-Bar-with-Bottom-Navigation-in-Jetpack-compose-Android - Bottom App Bar with Bottom Navigation in Jetpack compose

Bottom-App-Bar-with-Bottom-Navigation-in-Jetpack-compose-Android This is simple

Shruti Patel 1 Jul 11, 2022
A simple navigation library for Android 🗺️

Enro ??️ A simple navigation library for Android "The novices’ eyes followed the wriggling path up from the well as it swept a great meandering arc ar

Isaac Udy 206 Sep 5, 2022
🎉 [Android Library] A light-weight library to easily make beautiful Navigation Bar with ton of 🎨 customization option.

Bubble Navigation ?? A light-weight library to easily make beautiful Navigation Bars with a ton of ?? customization options. Demos FloatingTopBarActiv

Gaurav Kumar 1.6k Sep 23, 2022