From 26b35f3781b53ff32a04f76868ad3e9be928cb41 Mon Sep 17 00:00:00 2001 From: Gabor Varadi Date: Fri, 31 Mar 2023 19:25:03 +0200 Subject: [PATCH] #259 Add support for Backstack.setBackHandlingModel(AHEAD_OF_TIME) (#271) * #259 Add support for Backstack.setBackHandlingModel(AHEAD_OF_TIME) * #259 Update (most) samples to use android:enableOnBackInvokedCallback * #259 Update some dependencies in samples * #259 Update readme and changelog * #259 Remove accidental "res/res" folder * #259 formatting --- CHANGELOG.md | 206 ++++++ README.md | 140 +++- build.gradle.kts | 6 +- .../extensions-compose-example/build.gradle | 10 +- .../src/main/AndroidManifest.xml | 1 + .../app/MainActivity.kt | 30 +- .../extensions-example/build.gradle | 4 +- .../src/main/AndroidManifest.xml | 1 + .../app/MainActivity.kt | 32 +- .../mvvm-sample/build.gradle.kts | 2 +- .../mvvm-sample/src/main/AndroidManifest.xml | 9 +- .../application/MainActivity.kt | 30 +- .../src/main/AndroidManifest.xml | 5 +- .../application/MainActivity.java | 37 +- .../src/main/AndroidManifest.xml | 5 +- .../application/MainActivity.java | 36 +- .../build.gradle.kts | 4 +- .../src/main/AndroidManifest.xml | 5 +- .../application/MainActivity.kt | 30 +- .../build.gradle.kts | 2 +- .../src/main/AndroidManifest.xml | 5 +- .../application/MainActivity.kt | 30 +- .../build.gradle.kts | 4 +- .../src/main/AndroidManifest.xml | 5 +- .../application/MainActivity.kt | 31 +- .../build.gradle.kts | 4 +- .../application/MainActivity.kt | 19 +- .../core/navigation/Multistack.kt | 1 + .../build.gradle.kts | 4 +- .../application/MainActivity.kt | 2 +- .../core/navigation/Multistack.kt | 1 + .../.gitignore | 14 - .../build.gradle | 66 -- .../build.gradle.kts | 94 +++ .../proguard-rules.pro | 17 + .../ExampleInstrumentedTest.kt | 22 - .../ExampleInstrumentedTest.java | 28 + .../src/main/AndroidManifest.xml | 5 +- .../application/MainActivity.kt | 32 +- .../core/navigation/FragmentStackHost.kt | 46 +- .../features/root/RootScreen.kt | 6 +- .../src/main/res/anim/slide_in_from_left.xml | 8 + .../src/main/res/anim/slide_in_from_right.xml | 8 + .../src/main/res/anim/slide_out_to_left.xml | 8 + .../src/main/res/anim/slide_out_to_right.xml | 8 + .../src/main/res/drawable/ic_plus.xml | 9 + .../src/main/res/layout/new_word_fragment.xml | 22 + .../src/main/res/layout/word_list_item.xml | 12 + .../src/main/res/layout/word_list_view.xml | 24 + .../src/main/res/values-w820dp/dimens.xml | 6 + .../src/main/res/values/dimens.xml | 5 + .../src/main/res/values/styles.xml | 8 + .../ExampleUnitTest.kt | 16 - .../ExampleUnitTest.java | 19 + .../src/main/AndroidManifest.xml | 7 +- .../application/MainActivity.java | 38 +- .../src/main/AndroidManifest.xml | 1 + .../application/MainActivity.kt | 29 +- .../src/main/AndroidManifest.xml | 7 +- .../application/MainActivity.java | 35 +- simple-stack/build.gradle.kts | 2 +- .../simplestack/AheadOfTimeBackCallback.java | 101 +++ .../AheadOfTimeBackCallbackRegistry.java | 111 +++ ...kProcessingContractViolationException.java | 17 + ...adOfTimeWillHandleBackChangedListener.java | 17 + .../simplestack/BackHandlingModel.java | 26 + .../com/zhuinden/simplestack/Backstack.java | 399 ++++++++--- .../simplestack/BackstackDelegate.java | 26 +- .../zhuinden/simplestack/NavigationCore.java | 153 ++++- .../zhuinden/simplestack/ScopeManager.java | 278 +++++--- .../zhuinden/simplestack/ScopedServices.java | 6 +- .../zhuinden/simplestack/ServiceBinder.java | 26 +- .../simplestack/navigator/BackstackHost.java | 3 + .../simplestack/navigator/Navigator.java | 28 + .../BackstackAheadOfTimeBackModelTest.java | 649 ++++++++++++++++++ .../com/zhuinden/simplestack/TestSuite.java | 33 +- tutorials/tutorial-sample/build.gradle | 63 -- tutorials/tutorial-sample/build.gradle.kts | 99 +++ tutorials/tutorial-sample/proguard-rules.pro | 16 +- .../ExampleInstrumentedTest.kt | 22 - .../src/main/AndroidManifest.xml | 37 +- .../simplestacktutorials/app/MainActivity.kt | 8 + .../steps/step_1/Step1Activity.kt | 25 +- .../steps/step_2/Step2Activity.kt | 31 +- .../steps/step_3/Step3Activity.kt | 31 +- .../steps/step_4/Step4Activity.kt | 30 +- .../steps/step_5/Step5Activity.kt | 30 +- .../steps/step_6/Step6Activity.kt | 30 +- .../steps/step_7/Step7Activity.kt | 28 +- .../steps/step_8/Step8Activity.kt | 30 +- .../steps/step_9/Step9Activity.kt | 28 +- .../src/main/res/drawable/ic_plus.xml | 9 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2963 -> 3593 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4905 -> 5339 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2060 -> 2636 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2783 -> 3388 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4490 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 6895 -> 7472 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6387 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10413 -> 11873 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9128 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15132 -> 16570 bytes .../src/main/res/values-w820dp/dimens.xml | 6 + .../src/main/res/values/colors.xml | 15 +- .../src/main/res/values/dimens.xml | 5 + .../src/main/res/values/strings.xml | 4 +- .../src/main/res/values/styles.xml | 10 - .../src/main/res/values}/themes.xml | 14 +- .../simplestacktutorials/ExampleUnitTest.kt | 16 - 109 files changed, 2930 insertions(+), 833 deletions(-) delete mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/build.gradle create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/build.gradle.kts create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/proguard-rules.pro delete mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/androidTest/java/com/zhuinden/simplestackbottomnavfragmentexample/ExampleInstrumentedTest.kt create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/androidTest/java/com/zhuinden/simplestackexamplescoping/ExampleInstrumentedTest.java create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_in_from_left.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_in_from_right.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_out_to_left.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_out_to_right.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/drawable/ic_plus.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/layout/new_word_fragment.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/layout/word_list_item.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/layout/word_list_view.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/values-w820dp/dimens.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/values/dimens.xml create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/values/styles.xml delete mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/test/java/com/zhuinden/simplestackbottomnavfragmentexample/ExampleUnitTest.kt create mode 100644 samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/test/java/com/zhuinden/simplestackexamplescoping/ExampleUnitTest.java create mode 100644 simple-stack/src/main/java/com/zhuinden/simplestack/AheadOfTimeBackCallback.java create mode 100644 simple-stack/src/main/java/com/zhuinden/simplestack/AheadOfTimeBackCallbackRegistry.java create mode 100644 simple-stack/src/main/java/com/zhuinden/simplestack/AheadOfTimeBackProcessingContractViolationException.java create mode 100644 simple-stack/src/main/java/com/zhuinden/simplestack/AheadOfTimeWillHandleBackChangedListener.java create mode 100644 simple-stack/src/main/java/com/zhuinden/simplestack/BackHandlingModel.java create mode 100644 simple-stack/src/test/java/com/zhuinden/simplestack/BackstackAheadOfTimeBackModelTest.java delete mode 100644 tutorials/tutorial-sample/build.gradle create mode 100644 tutorials/tutorial-sample/build.gradle.kts delete mode 100644 tutorials/tutorial-sample/src/androidTest/java/com/zhuinden/simplestacktutorials/ExampleInstrumentedTest.kt create mode 100644 tutorials/tutorial-sample/src/main/res/drawable/ic_plus.xml create mode 100644 tutorials/tutorial-sample/src/main/res/values-w820dp/dimens.xml create mode 100644 tutorials/tutorial-sample/src/main/res/values/dimens.xml rename {samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/values-night => tutorials/tutorial-sample/src/main/res/values}/themes.xml (54%) delete mode 100644 tutorials/tutorial-sample/src/test/java/com/zhuinden/simplestacktutorials/ExampleUnitTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index f05484ff..ac9b7ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,211 @@ # Change log +-Simple Stack 2.7.0 (2023-03-31) +-------------------------------- + +- ***MAJOR FEATURE ADDITION***: Added `Backstack.setBackHandlingModel(BackHandlingModel.AHEAD_OF_TIME)` to + support `android:enableBackInvokedCallback="true"` on Android 14 for predictive back gesture support. + +With this, `Navigator.Installer.setBackHandlingModel()`, `BackstackDelegate.setBackHandlingModel()`, +and `Backstack.setBackHandlingModel()` are added. + +Also, `ServiceBinder.getAheadOfTimeBackCallbackRegistry()` is added as a replacement for `ScopedServices.HandlesBack`. +Please note that using it requires `AHEAD_OF_TIME` mode, and without it, trying to +use `ServiceBinder.getAheadOfTimeBackCallbackRegistry()` throws an exception. + +Also, `Backstack.willHandleAheadOfTimeBack()`, `Backstack.addAheadOfTimeWillHandleBackChangedListener()` +and `Backstack.removeAheadOfTimeWillHandleBackChangedListener()` are added. + +**IMPORTANT**: + +The `AHEAD_OF_TIME` back handling model must be enabled similarly to how `setScopedServices()` or other similar configs +must be called before `backstack.setup()`, `Navigator.install()`, or `BackstackDelegate.onCreate()`. + +When `AHEAD_OF_TIME` is set, the behavior of `goBack()` changes. Calling `goBack()` when `willHandleAheadOfTimeBack()` +returns false throws an exception. + +When `AHEAD_OF_TIME` is set, `ScopedServices.HandlesBack` will **no longer be called** (as it cannot return whether a +service WILL handle back or not), and should be replaced with registrations to the `AheadOfTimeBackCallbackRegistry`. + +When `AHEAD_OF_TIME` is NOT set (and therefore the default, `EVENT_BUBBLING` is set), +calling `willHandleAheadOfTimeBack` or `addAheadOfTimeWillHandleBackChangedListener` +or `removeAheadOfTimeWillHandleBackChangedListener` throws an exception. + +To migrate to use the ahead-of-time back handling model, then you might have the previous +somewhat `onBackPressedDispatcher`-compatible (but not predictive-back-gesture compatible) code: + +``` kotlin +class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { + private lateinit var fragmentStateChanger: DefaultFragmentStateChanger + + @Suppress("DEPRECATION") + private val backPressedCallback = object: OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (!Navigator.onBackPressed(this@MainActivity)) { + this.remove() + onBackPressed() // this is the reliable way to handle back for now + this@MainActivity.onBackPressedDispatcher.addCallback(this) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + onBackPressedDispatcher.addCallback(backPressedCallback) // this is the reliable way to handle back for now + + val binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + fragmentStateChanger = DefaultFragmentStateChanger(supportFragmentManager, R.id.container) + + Navigator.configure() + .setStateChanger(SimpleStateChanger(this)) + .install(this, binding.container, History.single(HomeKey)) + } + + override fun onNavigationEvent(stateChange: StateChange) { + fragmentStateChanger.handleStateChange(stateChange) + } +} +``` + +This code changes to the following in order to support predictive back gesture using ahead-of-time model: + +```kotlin +class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { + private lateinit var fragmentStateChanger: FragmentStateChanger + + private lateinit var authenticationManager: AuthenticationManager + + private lateinit var backstack: Backstack + + private val backPressedCallback = object : OnBackPressedCallback(false) { // <-- ! + override fun handleOnBackPressed() { + backstack.goBack() + } + } + + private val updateBackPressedCallback = AheadOfTimeWillHandleBackChangedListener { // <-- ! + backPressedCallback.isEnabled = it // <-- ! + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.main_activity) + + onBackPressedDispatcher.addCallback(backPressedCallback) // <-- ! + + fragmentStateChanger = FragmentStateChanger(supportFragmentManager, R.id.container) + + backstack = Navigator.configure() + .setBackHandlingModel(BackHandlingModel.AHEAD_OF_TIME) // <-- ! + .setStateChanger(SimpleStateChanger(this)) + .install(this, binding.container, History.single(HomeKey)) + + backPressedCallback.isEnabled = backstack.willHandleAheadOfTimeBack() // <-- ! + backstack.addAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback) // <-- ! + } + + override fun onDestroy() { + backstack.removeAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback); // <-- ! + super.onDestroy() + } + + override fun onNavigationEvent(stateChange: StateChange) { + fragmentStateChanger.handleStateChange(stateChange) + } +} +``` + +Please make sure to remove the `AheadOfTimeWillHandleBackChangedListener` in `onDestroy` (Activity) or `onDestroyView` ( +Fragment), because the listener staying registered would be a memory leak. + +A "lifecycle-aware" callback *might* be added to `simple-stack-extensions` later. + +If you can't update to the `AHEAD_OF_TIME` back handling model, then don't worry, as backwards compatibility has been +preserved with the previous behavior. + +When using `AHEAD_OF_TIME` back handling model, `ScopedServices.HandlesBack` is no longer called. To replace this, you +might have had something like this: + +```kotlin +class FragmentStackHost( + initialKey: Any +) : Bundleable, ScopedServices.HandlesBack { + var isActiveForBack: Boolean = false + + // ... + + override fun onBackEvent(): Boolean { + if (isActiveForBack) { + return backstack.goBack() + } else { + return false + } + } +} +``` + +This is replaced like so: + +```kotlin +class FragmentStackHost( + initialKey: Any, + private val aheadOfTimeBackCallbackRegistry: AheadOfTimeBackCallbackRegistry, +) : Bundleable, ScopedServices.Registered { + var isActiveForBack: Boolean = false + set(value) { + field = value + backCallback.isEnabled = value && backstackWillHandleBack + } + + private var backstackWillHandleBack = false + set(value) { + field = value + backCallback.isEnabled = isActiveForBack && value + } + + private val backCallback = object : AheadOfTimeBackCallback(false) { + override fun onBackReceived() { + backstack.goBack() + } + } + + private val willHandleBackChangedListener = AheadOfTimeWillHandleBackChangedListener { + backstackWillHandleBack = it + } + + init { + // ... + backstackWillHandleBack = backstack.willHandleAheadOfTimeBack() + backstack.addAheadOfTimeWillHandleBackChangedListener(willHandleBackChangedListener) + } + + override fun onServiceRegistered() { + aheadOfTimeBackCallbackRegistry.registerAheadOfTimeBackCallback(backCallback) + } + + override fun onServiceUnregistered() { + aheadOfTimeBackCallbackRegistry.unregisterAheadOfTimeCallback(backCallback) + } +} +``` + +Where `FragmentStackHost` gets the `AheadOfTimeBackCallbackRegistry` +from `serviceBinder.getAheadOfTimeBackCallbackRegistry()`. + +So in this snippet, whether back will be handled needs to be propagated up, and manage the enabled state of +the `AheadOfTimeBackCallback` to intercept back if needed. + +While this might seem a bit tricky, this is how Google does it in their own micromanagement of communicating with +the `onBackPressedDispatcher` as well, so evaluating ahead of time who will want to handle back later is unavoidable. + +- DEPRECATED: `BackstackDelegate.onBackPressed()` and `Navigator.onBackPressed()`. Not only are they the same + as `backstack.goBack()` and merely managed to confuse people historically, but this deprecation mirros the deprecation + of `onBackPressed` in compileSdk 33, to push towards using predictive back. + -Simple Stack 2.6.5 (2022-11-11) -------------------------------- diff --git a/README.md b/README.md index de6da313..1f47b565 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ and then, add the dependency to your module's `build.gradle.kts` (or `build.grad ``` kotlin // build.gradle.kts -implementation("com.github.Zhuinden:simple-stack:2.6.5") +implementation("com.github.Zhuinden:simple-stack:2.7.0") implementation("com.github.Zhuinden.simple-stack-extensions:core-ktx:2.2.5") implementation("com.github.Zhuinden.simple-stack-extensions:fragments:2.2.5") @@ -83,7 +83,7 @@ or ``` groovy // build.gradle -implementation 'com.github.Zhuinden:simple-stack:2.6.5' +implementation 'com.github.Zhuinden:simple-stack:2.7.0' implementation 'com.github.Zhuinden.simple-stack-extensions:core-ktx:2.2.5' implementation 'com.github.Zhuinden.simple-stack-extensions:fragments:2.2.5' @@ -95,40 +95,54 @@ implementation 'com.github.Zhuinden.simple-stack-extensions:services-ktx:2.2.5' ## How do I use it? -You can check out [**the tutorials**](https://github.com/Zhuinden/simple-stack/tree/611e8c7db738a776156b8f709db22b8e37413221/tutorials) for simple examples. +You can check out [**the +tutorials**](https://github.com/Zhuinden/simple-stack/tree/611e8c7db738a776156b8f709db22b8e37413221/tutorials) for +simple examples. ## Fragments -With Fragments, the Activity code looks like this: +With Fragments, in `AHEAD_OF_TIME` back handling mode to support predictive back gesture (along +with `android:enableBackInvokedCallback`), the Activity code looks like this: -``` kotlin +```kotlin class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { - private lateinit var fragmentStateChanger: DefaultFragmentStateChanger + private lateinit var fragmentStateChanger: FragmentStateChanger - @Suppress("DEPRECATION") - private val backPressedCallback = object: OnBackPressedCallback(true) { + private lateinit var authenticationManager: AuthenticationManager + + private lateinit var backstack: Backstack + + private val backPressedCallback = object : OnBackPressedCallback(false) { // <-- ! override fun handleOnBackPressed() { - if (!Navigator.onBackPressed(this@MainActivity)) { - this.remove() - onBackPressed() // this is the reliable way to handle back for now - this@MainActivity.onBackPressedDispatcher.addCallback(this) - } + backstack.goBack() } } - + + private val updateBackPressedCallback = AheadOfTimeWillHandleBackChangedListener { // <-- ! + backPressedCallback.isEnabled = it // <-- ! + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - onBackPressedDispatcher.addCallback(backPressedCallback) // this is the reliable way to handle back for now + setContentView(R.layout.main_activity) - val binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - fragmentStateChanger = DefaultFragmentStateChanger(supportFragmentManager, R.id.container) - - Navigator.configure() + onBackPressedDispatcher.addCallback(backPressedCallback) // <-- ! + + fragmentStateChanger = FragmentStateChanger(supportFragmentManager, R.id.container) + + backstack = Navigator.configure() + .setBackHandlingModel(BackHandlingModel.AHEAD_OF_TIME) // <-- ! .setStateChanger(SimpleStateChanger(this)) .install(this, binding.container, History.single(HomeKey)) + + backPressedCallback.isEnabled = backstack.willHandleAheadOfTimeBack() // <-- ! + backstack.addAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback) // <-- ! + } + + override fun onDestroy() { + backstack.removeAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback); // <-- ! + super.onDestroy() } override fun onNavigationEvent(stateChange: StateChange) { @@ -137,13 +151,8 @@ class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { } ``` -Originally, handling back was simpler, as all you had to do is override `onBackPressed()` (then -call `backstack.goBack()`, if it returned `true` then you would not call `super.onBackPressed()`) , but in order to -support `BackHandler` in Compose, or Fragments that use `OnBackPressedDispatcher` internally, you cannot -override `onBackPressed` anymore in a reliable manner. - -With targetSdkVersion 34, `onBackPressed` will no longer be called. Simple-Stack does not yet -support `targetSdkVersion 34`, it is tracked in issue #259. +With targetSdkVersion 34 and with `android:enableOnBackInvokedCallback="true"` enabled, `onBackPressed` ( +and `KEYCODE_BACK`) will no longer be called. In that case, the `AHEAD_OF_TIME` back handling model should be preferred. ## Screens @@ -298,24 +307,85 @@ override fun onNavigationEvent(stateChange: StateChange) { // using SimpleStateC } ``` -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. +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. +## Dev Talk about Simple-Stack -## More information +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`](https://www.youtube.com/watch?v=5ACcin1Z2HQ) +. -For more information, check the [wiki page](https://github.com/Zhuinden/simple-stack/wiki). +## Tutorial by Ryan Kay + +For a quick tutorial on how to set up dependency injection, model lifecycles, and reactive state management using +Simple-Stack, you can look at the tutorial by Ryan Michael Kay [**here, by clicking this +link**](https://youtu.be/yRVt6sALB-g?t=2600).) -## Talk +## More information -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`](https://www.youtube.com/watch?v=5ACcin1Z2HQ). +For more information, check the [wiki page](https://github.com/Zhuinden/simple-stack/wiki). ## 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](https://developer.android.com/jetpack/compose/interop/interop-apis#compose-in-fragments) guide. +This however is only required if ONLY composables are used, and NO fragments. When using Fragments, refer to the +official [Fragment Compose interop](https://developer.android.com/jetpack/compose/interop/interop-apis#compose-in-fragments) +guide. + +For Fragment + Simple-Stack + Compose integration, you can also +check [the corresponding sample](https://github.com/Zhuinden/simple-stack/tree/ced6d11e711fa2dda85e3bd7813cb2a192f10396/samples/advanced-samples/extensions-compose-example) +. + +## About the event-bubbling back handling model + +Note: Before supporting predictive back gestures and using `EVENT_BUBBLING` back handling model, the code that interops +with OnBackPressedDispatcher looks like this: + +``` kotlin +class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { + private lateinit var fragmentStateChanger: DefaultFragmentStateChanger + + @Suppress("DEPRECATION") + private val backPressedCallback = object: OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (!Navigator.onBackPressed(this@MainActivity)) { + this.remove() + onBackPressed() // this is the reliable way to handle back for now + this@MainActivity.onBackPressedDispatcher.addCallback(this) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + onBackPressedDispatcher.addCallback(backPressedCallback) // this is the reliable way to handle back for now + + val binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + fragmentStateChanger = DefaultFragmentStateChanger(supportFragmentManager, R.id.container) + + Navigator.configure() + .setStateChanger(SimpleStateChanger(this)) + .install(this, binding.container, History.single(HomeKey)) + } + + override fun onNavigationEvent(stateChange: StateChange) { + fragmentStateChanger.handleStateChange(stateChange) + } +} +``` + +Originally, handling back was simpler, as all you had to do is override `onBackPressed()` (then +call `backstack.goBack()`, if it returned `true` then you would not call `super.onBackPressed()`) , but in order to +support `BackHandler` in Compose, or Fragments that use `OnBackPressedDispatcher` internally, you cannot +override `onBackPressed` anymore in a reliable manner. -For Fragment + Simple-Stack + Compose integration, you can also check [the corresponding sample](https://github.com/Zhuinden/simple-stack/tree/ced6d11e711fa2dda85e3bd7813cb2a192f10396/samples/advanced-samples/extensions-compose-example). +Now, either this should be used (if cannot migrate to `AHEAD_OF_TIME` back handling model), or migrate +to `AHEAD_OF_TIME` back handling model and `AheadOfTimeBackCallback`. ## License diff --git a/build.gradle.kts b/build.gradle.kts index fe21ed7a..08e321e1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,9 +10,9 @@ buildscript { jcenter() } dependencies { - classpath("com.android.tools.build:gradle:7.4.1") - classpath("io.realm:realm-gradle-plugin:10.13.0") - classpath("io.realm:realm-transformer:10.13.0") + classpath("com.android.tools.build:gradle:7.4.2") + classpath("io.realm:realm-gradle-plugin:10.13.1") + classpath("io.realm:realm-transformer:10.13.1") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/samples/advanced-samples/extensions-compose-example/build.gradle b/samples/advanced-samples/extensions-compose-example/build.gradle index a58818bb..e858e3a1 100644 --- a/samples/advanced-samples/extensions-compose-example/build.gradle +++ b/samples/advanced-samples/extensions-compose-example/build.gradle @@ -57,13 +57,13 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "androidx.recyclerview:recyclerview:1.2.1" - implementation "com.github.lisawray.groupie:groupie:2.10.0" - implementation("com.github.lisawray.groupie:groupie-viewbinding:2.10.0") + implementation "com.github.lisawray.groupie:groupie:2.10.1" + implementation("com.github.lisawray.groupie:groupie-viewbinding:2.10.1") implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.core:core-ktx:1.9.0") - implementation("androidx.activity:activity-ktx:1.6.1") - implementation("androidx.fragment:fragment-ktx:1.5.5") + implementation("androidx.activity:activity-ktx:1.7.0") + implementation("androidx.fragment:fragment-ktx:1.5.6") implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") @@ -102,6 +102,8 @@ dependencies { implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1' testImplementation 'junit:junit:4.13.2' + androidTestImplementation("androidx.test:runner:1.5.2") + androidTestImplementation("androidx.test:rules:1.5.0") androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/samples/advanced-samples/extensions-compose-example/src/main/AndroidManifest.xml b/samples/advanced-samples/extensions-compose-example/src/main/AndroidManifest.xml index 1db7b2e3..107e2464 100644 --- a/samples/advanced-samples/extensions-compose-example/src/main/AndroidManifest.xml +++ b/samples/advanced-samples/extensions-compose-example/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ ProfileKey(authenticationManager.getAuthenticatedUser()) else -> LoginKey - } - )) + } + ) + ) + + backPressedCallback.isEnabled = backstack.willHandleAheadOfTimeBack() + backstack.addAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback) } override fun onNavigationEvent(stateChange: StateChange) { @@ -64,6 +68,8 @@ class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { } override fun onDestroy() { + backstack.removeAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback) + if (isFinishing) { authenticationManager.clearRegistration() // just for sample repeat sake } diff --git a/samples/advanced-samples/mvvm-sample/build.gradle.kts b/samples/advanced-samples/mvvm-sample/build.gradle.kts index e3440f0d..9d41b490 100644 --- a/samples/advanced-samples/mvvm-sample/build.gradle.kts +++ b/samples/advanced-samples/mvvm-sample/build.gradle.kts @@ -95,7 +95,7 @@ dependencies { implementation("io.reactivex.rxjava2:rxkotlin:2.4.0") implementation("com.jakewharton.rxrelay2:rxrelay:2.1.1") - implementation("androidx.activity:activity:1.6.1") + implementation("androidx.activity:activity:1.7.0") implementation("androidx.cardview:cardview:1.0.0") implementation("com.google.android.material:material:1.8.0") implementation("androidx.recyclerview:recyclerview:1.2.1") diff --git a/samples/advanced-samples/mvvm-sample/src/main/AndroidManifest.xml b/samples/advanced-samples/mvvm-sample/src/main/AndroidManifest.xml index 94112804..aeab43be 100644 --- a/samples/advanced-samples/mvvm-sample/src/main/AndroidManifest.xml +++ b/samples/advanced-samples/mvvm-sample/src/main/AndroidManifest.xml @@ -14,21 +14,22 @@ ~ limitations under the License. --> - + + android:launchMode="singleTask"> diff --git a/samples/advanced-samples/mvvm-sample/src/main/java/com/zhuinden/simplestackexamplemvvm/application/MainActivity.kt b/samples/advanced-samples/mvvm-sample/src/main/java/com/zhuinden/simplestackexamplemvvm/application/MainActivity.kt index d2880894..a229dcf0 100644 --- a/samples/advanced-samples/mvvm-sample/src/main/java/com/zhuinden/simplestackexamplemvvm/application/MainActivity.kt +++ b/samples/advanced-samples/mvvm-sample/src/main/java/com/zhuinden/simplestackexamplemvvm/application/MainActivity.kt @@ -8,9 +8,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout import com.zhuinden.liveevent.observe -import com.zhuinden.simplestack.History -import com.zhuinden.simplestack.SimpleStateChanger -import com.zhuinden.simplestack.StateChange +import com.zhuinden.simplestack.* import com.zhuinden.simplestack.navigator.Navigator import com.zhuinden.simplestackexamplemvvm.R import com.zhuinden.simplestackexamplemvvm.databinding.MainActivityBinding @@ -27,17 +25,18 @@ class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { private lateinit var snackbarTextEmitter: SnackbarTextEmitter private lateinit var binding: MainActivityBinding - @Suppress("DEPRECATION") - private val backPressedCallback = object : OnBackPressedCallback(true) { + private lateinit var backstack: Backstack + + private val backPressedCallback = object : OnBackPressedCallback(false) { override fun handleOnBackPressed() { - if (!Navigator.onBackPressed(this@MainActivity)) { - this.remove() - onBackPressed() // this is the reliable way to handle back for now - this@MainActivity.onBackPressedDispatcher.addCallback(this) - } + backstack.goBack() } } + private val updateBackPressedCallback = AheadOfTimeWillHandleBackChangedListener { + backPressedCallback.isEnabled = it + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainActivityBinding.inflate(layoutInflater) @@ -72,11 +71,20 @@ class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { showSnackbar(binding.root, getString(textRes)) } - Navigator.configure() + backstack = Navigator.configure() + .setBackHandlingModel(BackHandlingModel.AHEAD_OF_TIME) .setStateChanger(SimpleStateChanger(this)) .setGlobalServices(globalServices) .setScopedServices(DefaultServiceProvider()) .install(this, binding.contentFrame, History.of(TasksKey)) + + backPressedCallback.isEnabled = backstack.willHandleAheadOfTimeBack() + backstack.addAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback) + } + + override fun onDestroy() { + backstack.removeAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback) + super.onDestroy() } fun toggleLeftDrawer() { diff --git a/samples/basic-samples/simple-stack-example-basic-java-fragment/src/main/AndroidManifest.xml b/samples/basic-samples/simple-stack-example-basic-java-fragment/src/main/AndroidManifest.xml index 0827e0d8..3bc9fbf9 100644 --- a/samples/basic-samples/simple-stack-example-basic-java-fragment/src/main/AndroidManifest.xml +++ b/samples/basic-samples/simple-stack-example-basic-java-fragment/src/main/AndroidManifest.xml @@ -1,9 +1,10 @@ - + - + - + - + - + - + diff --git a/samples/community-samples/simple-stack-example-kotlin-community-sample/src/main/java/com/community/simplestackkotlindaggerexample/application/MainActivity.kt b/samples/community-samples/simple-stack-example-kotlin-community-sample/src/main/java/com/community/simplestackkotlindaggerexample/application/MainActivity.kt index 6233e0ae..5c56fb60 100644 --- a/samples/community-samples/simple-stack-example-kotlin-community-sample/src/main/java/com/community/simplestackkotlindaggerexample/application/MainActivity.kt +++ b/samples/community-samples/simple-stack-example-kotlin-community-sample/src/main/java/com/community/simplestackkotlindaggerexample/application/MainActivity.kt @@ -6,24 +6,23 @@ import androidx.appcompat.app.AppCompatActivity import com.community.simplestackkotlindaggerexample.R import com.community.simplestackkotlindaggerexample.databinding.ActivityMainBinding import com.community.simplestackkotlindaggerexample.screens.home.HomeKey -import com.zhuinden.simplestack.History -import com.zhuinden.simplestack.SimpleStateChanger -import com.zhuinden.simplestack.StateChange +import com.zhuinden.simplestack.* import com.zhuinden.simplestack.navigator.Navigator import com.zhuinden.simplestackextensions.fragments.DefaultFragmentStateChanger class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { - @Suppress("DEPRECATION") - private val backPressedCallback = object : OnBackPressedCallback(true) { + private lateinit var backstack: Backstack + + private val backPressedCallback = object : OnBackPressedCallback(false) { override fun handleOnBackPressed() { - if (!Navigator.onBackPressed(this@MainActivity)) { - this.remove() - onBackPressed() // this is the reliable way to handle back for now - this@MainActivity.onBackPressedDispatcher.addCallback(this) - } + backstack.goBack() } } + private val updateBackPressedCallback = AheadOfTimeWillHandleBackChangedListener { + backPressedCallback.isEnabled = it + } + private lateinit var fragmentStateChanger: DefaultFragmentStateChanger override fun onCreate(savedInstanceState: Bundle?) { @@ -36,9 +35,19 @@ class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { fragmentStateChanger = DefaultFragmentStateChanger(supportFragmentManager, R.id.container) - Navigator.configure() + backstack = Navigator.configure() + .setBackHandlingModel(BackHandlingModel.AHEAD_OF_TIME) .setStateChanger(SimpleStateChanger(this)) .install(this, binding.container, History.single(HomeKey)) + + + backPressedCallback.isEnabled = backstack.willHandleAheadOfTimeBack() + backstack.addAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback) + } + + override fun onDestroy() { + backstack.removeAheadOfTimeWillHandleBackChangedListener(updateBackPressedCallback) + super.onDestroy() } override fun onNavigationEvent(stateChange: StateChange) { diff --git a/samples/legacy-samples/simple-stack-example-multistack-fragment/build.gradle.kts b/samples/legacy-samples/simple-stack-example-multistack-fragment/build.gradle.kts index b50d7c08..af21691b 100644 --- a/samples/legacy-samples/simple-stack-example-multistack-fragment/build.gradle.kts +++ b/samples/legacy-samples/simple-stack-example-multistack-fragment/build.gradle.kts @@ -53,8 +53,8 @@ dependencies { compileOnly("org.glassfish:javax.annotation:10.0-b28") implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.core:core:1.9.0") - implementation("androidx.activity:activity:1.6.1") - implementation("androidx.fragment:fragment-ktx:1.5.5") + implementation("androidx.activity:activity:1.7.0") + implementation("androidx.fragment:fragment-ktx:1.5.6") implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") diff --git a/samples/legacy-samples/simple-stack-example-multistack-fragment/src/main/java/com/zhuinden/simplestackdemomultistack/application/MainActivity.kt b/samples/legacy-samples/simple-stack-example-multistack-fragment/src/main/java/com/zhuinden/simplestackdemomultistack/application/MainActivity.kt index 836ea4d5..fe07b590 100644 --- a/samples/legacy-samples/simple-stack-example-multistack-fragment/src/main/java/com/zhuinden/simplestackdemomultistack/application/MainActivity.kt +++ b/samples/legacy-samples/simple-stack-example-multistack-fragment/src/main/java/com/zhuinden/simplestackdemomultistack/application/MainActivity.kt @@ -18,17 +18,18 @@ import com.zhuinden.simplestackdemomultistack.features.main.mail.MailKey class MainActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler { @Suppress("DEPRECATION") - private val backPressedCallback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - val backstack = multistack.getSelectedStack() - - if (!backstack.goBack()) { - this.remove() - onBackPressed() // this is the reliable way to handle back for now - this@MainActivity.onBackPressedDispatcher.addCallback(this) + private val backPressedCallback = + object : OnBackPressedCallback(true) { // TODO: update this code to use AHEAD_OF_TIME model + override fun handleOnBackPressed() { + val backstack = multistack.getSelectedStack() + + if (!backstack.goBack()) { + this.remove() + onBackPressed() // this is the reliable way to handle back for now when using EVENT_BUBBLING back handling model + this@MainActivity.onBackPressedDispatcher.addCallback(this) + } } } - } lateinit var multistack: Multistack diff --git a/samples/legacy-samples/simple-stack-example-multistack-fragment/src/main/java/com/zhuinden/simplestackdemomultistack/core/navigation/Multistack.kt b/samples/legacy-samples/simple-stack-example-multistack-fragment/src/main/java/com/zhuinden/simplestackdemomultistack/core/navigation/Multistack.kt index 3aac8d92..8f000f61 100644 --- a/samples/legacy-samples/simple-stack-example-multistack-fragment/src/main/java/com/zhuinden/simplestackdemomultistack/core/navigation/Multistack.kt +++ b/samples/legacy-samples/simple-stack-example-multistack-fragment/src/main/java/com/zhuinden/simplestackdemomultistack/core/navigation/Multistack.kt @@ -25,6 +25,7 @@ class Multistack : Bundleable { selectedStack = identifier } val backstack = Backstack().apply { + // TODO: update this to handle AHEAD_OF_TIME back handling model setup(History.of(initialKey)) } backstacks[identifier] = backstack diff --git a/samples/legacy-samples/simple-stack-example-multistack-view/build.gradle.kts b/samples/legacy-samples/simple-stack-example-multistack-view/build.gradle.kts index bd88b6f1..2bc092b9 100644 --- a/samples/legacy-samples/simple-stack-example-multistack-view/build.gradle.kts +++ b/samples/legacy-samples/simple-stack-example-multistack-view/build.gradle.kts @@ -53,8 +53,8 @@ dependencies { compileOnly("org.glassfish:javax.annotation:10.0-b28") implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.core:core:1.9.0") - implementation("androidx.activity:activity:1.6.1") - implementation("androidx.fragment:fragment-ktx:1.5.5") + implementation("androidx.activity:activity:1.7.0") + implementation("androidx.fragment:fragment-ktx:1.5.6") implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") diff --git a/samples/legacy-samples/simple-stack-example-multistack-view/src/main/java/com/zhuinden/simplestackdemomultistack/application/MainActivity.kt b/samples/legacy-samples/simple-stack-example-multistack-view/src/main/java/com/zhuinden/simplestackdemomultistack/application/MainActivity.kt index 0b9ff1ff..c2877df5 100644 --- a/samples/legacy-samples/simple-stack-example-multistack-view/src/main/java/com/zhuinden/simplestackdemomultistack/application/MainActivity.kt +++ b/samples/legacy-samples/simple-stack-example-multistack-view/src/main/java/com/zhuinden/simplestackdemomultistack/application/MainActivity.kt @@ -27,7 +27,7 @@ class MainActivity : AppCompatActivity(), MultistackViewStateChanger.AnimationSt if (!backstack.goBack()) { this.remove() - onBackPressed() // this is the reliable way to handle back for now + onBackPressed() // this is the reliable way to handle back for now when using EVENT_BUBBLING back handling model this@MainActivity.onBackPressedDispatcher.addCallback(this) } } diff --git a/samples/legacy-samples/simple-stack-example-multistack-view/src/main/java/com/zhuinden/simplestackdemomultistack/core/navigation/Multistack.kt b/samples/legacy-samples/simple-stack-example-multistack-view/src/main/java/com/zhuinden/simplestackdemomultistack/core/navigation/Multistack.kt index 61466f4c..da750285 100644 --- a/samples/legacy-samples/simple-stack-example-multistack-view/src/main/java/com/zhuinden/simplestackdemomultistack/core/navigation/Multistack.kt +++ b/samples/legacy-samples/simple-stack-example-multistack-view/src/main/java/com/zhuinden/simplestackdemomultistack/core/navigation/Multistack.kt @@ -25,6 +25,7 @@ class Multistack : Bundleable { selectedStack = identifier } val backstack = Backstack().apply { + // TODO: update this to handle AHEAD_OF_TIME back handling model setup(History.of(initialKey)) } backstacks[identifier] = backstack diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/.gitignore b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/.gitignore index aa724b77..796b96d1 100644 --- a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/.gitignore +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/.gitignore @@ -1,15 +1 @@ -*.iml -.gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store /build -/captures -.externalNativeBuild -.cxx -local.properties diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/build.gradle b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/build.gradle deleted file mode 100644 index 324f835d..00000000 --- a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -plugins { - id 'com.android.application' - id 'kotlin-android' - id 'kotlin-parcelize' -} - -android { - compileSdk 33 - - defaultConfig { - applicationId "com.zhuinden.simplestackbottomnavfragmentexample" - minSdk 21 - targetSdk 31 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - - buildFeatures { - viewBinding true - } -} - -dependencies { - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.8.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation "androidx.recyclerview:recyclerview:1.2.1" - - implementation "androidx.fragment:fragment-ktx:1.5.5" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" - implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1" - implementation "androidx.lifecycle:lifecycle-common-java8:2.5.1" - - implementation(project(":simple-stack")) - implementation 'com.github.Zhuinden.simple-stack-extensions:core-ktx:2.2.5' - implementation 'com.github.Zhuinden.simple-stack-extensions:fragments:2.2.5' - implementation 'com.github.Zhuinden.simple-stack-extensions:fragments-ktx:2.2.5' - implementation 'com.github.Zhuinden.simple-stack-extensions:navigator-ktx:2.2.5' - implementation 'com.github.Zhuinden.simple-stack-extensions:services:2.2.5' - implementation 'com.github.Zhuinden.simple-stack-extensions:services-ktx:2.2.5' - - implementation 'com.github.Zhuinden:fragmentviewbindingdelegate-kt:1.0.2' - - testImplementation 'junit:junit:4.+' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} \ No newline at end of file diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/build.gradle.kts b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/build.gradle.kts new file mode 100644 index 00000000..b057445d --- /dev/null +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/build.gradle.kts @@ -0,0 +1,94 @@ +plugins { + id("com.android.application") + kotlin("android") + id("kotlin-parcelize") + kotlin("kapt") +} + +android { + compileSdk = 33 + + defaultConfig { + applicationId = "com.zhuinden.simplestackbottomnavfragmentexample" + minSdk = 16 + targetSdk = 31 + versionCode = 1 + versionName = "1.0" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + multiDexEnabled = true + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + languageVersion = "1.9" // data objects + } + + buildFeatures { + viewBinding = true + } +} + +kotlin.sourceSets.all { + languageSettings.enableLanguageFeature("DataObjects") +} + +dependencies { + //implementation(mapOf("dir" to "libs", "include" to listOf("*.jar"))) + implementation(project(":simple-stack")) + + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.8.0") + testImplementation("junit:junit:4.13.2") + + implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1") + + implementation("com.github.Zhuinden.simple-stack-extensions:core-ktx:2.2.5") { + exclude(module = "simple-stack") + } + implementation("com.github.Zhuinden.simple-stack-extensions:fragments:2.2.5") { + exclude(module = "simple-stack") + } + implementation("com.github.Zhuinden.simple-stack-extensions:fragments-ktx:2.2.5") { + exclude(module = "simple-stack") + } + implementation("com.github.Zhuinden.simple-stack-extensions:navigator-ktx:2.2.5") { + exclude(module = "simple-stack") + } + implementation("com.github.Zhuinden.simple-stack-extensions:services:2.2.5") { + exclude(module = "simple-stack") + } + implementation("com.github.Zhuinden.simple-stack-extensions:services-ktx:2.2.5") { + exclude(module = "simple-stack") + } + + implementation("com.github.Zhuinden:fragmentviewbindingdelegate-kt:1.0.2") + + implementation("io.reactivex.rxjava2:rxjava:2.2.21") + implementation("io.reactivex.rxjava2:rxandroid:2.1.1") + implementation("com.jakewharton.rxrelay2:rxrelay:2.1.1") + + implementation("androidx.lifecycle:lifecycle-viewmodel:2.5.1") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1") + + implementation("androidx.multidex:multidex:2.0.1") + + implementation("com.github.Zhuinden:live-event:1.3.0") + + +} diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/proguard-rules.pro b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/proguard-rules.pro new file mode 100644 index 00000000..993c56a6 --- /dev/null +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Development\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/androidTest/java/com/zhuinden/simplestackbottomnavfragmentexample/ExampleInstrumentedTest.kt b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/androidTest/java/com/zhuinden/simplestackbottomnavfragmentexample/ExampleInstrumentedTest.kt deleted file mode 100644 index fd3dd802..00000000 --- a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/androidTest/java/com/zhuinden/simplestackbottomnavfragmentexample/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.zhuinden.simplestackbottomnavfragmentexample - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.zhuinden.bottomnavfragmentexample", appContext.packageName) - } -} \ No newline at end of file diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/androidTest/java/com/zhuinden/simplestackexamplescoping/ExampleInstrumentedTest.java b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/androidTest/java/com/zhuinden/simplestackexamplescoping/ExampleInstrumentedTest.java new file mode 100644 index 00000000..6196e9c1 --- /dev/null +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/androidTest/java/com/zhuinden/simplestackexamplescoping/ExampleInstrumentedTest.java @@ -0,0 +1,28 @@ +package com.zhuinden.simplestackexamplescoping; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + + @Test + public void useAppContext() + throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.zhuinden.simplestackexamplescoping", appContext.getPackageName()); + } +} diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/AndroidManifest.xml b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/AndroidManifest.xml index 55f26dc5..a6ec0d0d 100644 --- a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/AndroidManifest.xml +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/AndroidManifest.xml @@ -1,9 +1,10 @@ - + + + + \ No newline at end of file diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_in_from_right.xml b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_in_from_right.xml new file mode 100644 index 00000000..d0a1a5b5 --- /dev/null +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_in_from_right.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_out_to_left.xml b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_out_to_left.xml new file mode 100644 index 00000000..49804a06 --- /dev/null +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_out_to_left.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_out_to_right.xml b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_out_to_right.xml new file mode 100644 index 00000000..55819b2e --- /dev/null +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/anim/slide_out_to_right.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/drawable/ic_plus.xml b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/drawable/ic_plus.xml new file mode 100644 index 00000000..4c12b507 --- /dev/null +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/drawable/ic_plus.xml @@ -0,0 +1,9 @@ + + + diff --git a/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/layout/new_word_fragment.xml b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/layout/new_word_fragment.xml new file mode 100644 index 00000000..18f8fae8 --- /dev/null +++ b/samples/multistack-samples/simple-stack-example-multistack-nested-fragment/src/main/res/layout/new_word_fragment.xml @@ -0,0 +1,22 @@ + + + + + +