Repository: uber/RIBs
Branch: main
Commit: f08e54ecde89
Files: 665
Total size: 18.8 MB
Directory structure:
gitextract_os7hyq2r/
├── .github/
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── android.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── RELEASING.md
├── build.gradle.kts
├── config/
│ ├── lint/
│ │ └── lint.xml
│ └── spotless/
│ ├── copyright.java
│ └── copyright.kt
├── conventions/
│ ├── build.gradle.kts
│ ├── settings.gradle.kts
│ └── src/
│ └── main/
│ └── kotlin/
│ ├── Extensions.kt
│ ├── ribs.android.application.errorprone.gradle.kts
│ ├── ribs.android.application.gradle.kts
│ ├── ribs.android.library.gradle.kts
│ ├── ribs.kotlin.library.gradle.kts
│ └── ribs.spotless.gradle.kts
├── demos/
│ ├── compose/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── compose/
│ │ │ ├── ComposeApplication.kt
│ │ │ ├── root/
│ │ │ │ ├── RootActivity.kt
│ │ │ │ ├── RootInteractor.kt
│ │ │ │ ├── RootRouter.kt
│ │ │ │ ├── RootScope.kt
│ │ │ │ ├── RootView.kt
│ │ │ │ └── main/
│ │ │ │ ├── AuthStream.kt
│ │ │ │ ├── MainInteractor.kt
│ │ │ │ ├── MainRouter.kt
│ │ │ │ ├── MainScope.kt
│ │ │ │ ├── MainView.kt
│ │ │ │ ├── loggedin/
│ │ │ │ │ ├── LoggedInEvent.kt
│ │ │ │ │ ├── LoggedInInteractor.kt
│ │ │ │ │ ├── LoggedInRouter.kt
│ │ │ │ │ ├── LoggedInScope.kt
│ │ │ │ │ ├── LoggedInView.kt
│ │ │ │ │ ├── ScoreStream.kt
│ │ │ │ │ ├── offgame/
│ │ │ │ │ │ ├── OffGameEvent.kt
│ │ │ │ │ │ ├── OffGameInteractor.kt
│ │ │ │ │ │ ├── OffGameRouter.kt
│ │ │ │ │ │ ├── OffGameScope.kt
│ │ │ │ │ │ ├── OffGameView.kt
│ │ │ │ │ │ └── OffGameViewModel.kt
│ │ │ │ │ └── tictactoe/
│ │ │ │ │ ├── Board.kt
│ │ │ │ │ ├── BoardCoordinate.kt
│ │ │ │ │ ├── TicTacToeEvent.kt
│ │ │ │ │ ├── TicTacToeInteractor.kt
│ │ │ │ │ ├── TicTacToeRouter.kt
│ │ │ │ │ ├── TicTacToeScope.kt
│ │ │ │ │ ├── TicTacToeView.kt
│ │ │ │ │ └── TicTacToeViewModel.kt
│ │ │ │ └── loggedout/
│ │ │ │ ├── LoggedOutEvent.kt
│ │ │ │ ├── LoggedOutInteractor.kt
│ │ │ │ ├── LoggedOutRouter.kt
│ │ │ │ ├── LoggedOutScope.kt
│ │ │ │ ├── LoggedOutView.kt
│ │ │ │ └── LoggedOutViewModel.kt
│ │ │ └── util/
│ │ │ ├── AnalyticsClient.kt
│ │ │ ├── CustomButton.kt
│ │ │ ├── CustomClientProvider.kt
│ │ │ ├── EventStream.kt
│ │ │ ├── ExperimentClient.kt
│ │ │ ├── LoggerClient.kt
│ │ │ └── StateStream.kt
│ │ └── res/
│ │ ├── drawable/
│ │ │ └── ic_launcher_background.xml
│ │ ├── drawable-v24/
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── mipmap-anydpi-v26/
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ └── values/
│ │ ├── ids.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ ├── flipper/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ ├── RootActivity.java
│ │ │ ├── SampleApplication.java
│ │ │ └── root/
│ │ │ ├── RootBuilder.java
│ │ │ ├── RootInteractor.java
│ │ │ ├── RootRouter.java
│ │ │ ├── RootView.java
│ │ │ ├── loggedin/
│ │ │ │ ├── LoggedInBuilder.java
│ │ │ │ ├── LoggedInInteractor.java
│ │ │ │ ├── LoggedInRouter.java
│ │ │ │ ├── offgame/
│ │ │ │ │ ├── OffGameBuilder.java
│ │ │ │ │ ├── OffGameInteractor.java
│ │ │ │ │ ├── OffGameRouter.java
│ │ │ │ │ └── OffGameView.java
│ │ │ │ └── tictactoe/
│ │ │ │ ├── Board.java
│ │ │ │ ├── BoardCoordinate.java
│ │ │ │ ├── TicTacToeBuilder.java
│ │ │ │ ├── TicTacToeInteractor.java
│ │ │ │ ├── TicTacToeRouter.java
│ │ │ │ └── TicTacToeView.java
│ │ │ └── loggedout/
│ │ │ ├── LoggedOutBuilder.java
│ │ │ ├── LoggedOutInteractor.java
│ │ │ ├── LoggedOutRouter.java
│ │ │ └── LoggedOutView.java
│ │ └── res/
│ │ ├── layout/
│ │ │ ├── logged_out_rib.xml
│ │ │ ├── off_game_rib.xml
│ │ │ ├── root_rib.xml
│ │ │ └── tic_tac_toe_rib.xml
│ │ └── values/
│ │ └── ub__strings.xml
│ ├── intellij/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ ├── RootActivity.java
│ │ │ ├── SampleApplication.java
│ │ │ └── root/
│ │ │ ├── RootBuilder.java
│ │ │ ├── RootInteractor.java
│ │ │ ├── RootRouter.java
│ │ │ ├── RootView.java
│ │ │ ├── loggedin/
│ │ │ │ ├── LoggedInBuilder.java
│ │ │ │ ├── LoggedInInteractor.java
│ │ │ │ ├── LoggedInRouter.java
│ │ │ │ ├── offgame/
│ │ │ │ │ ├── OffGameBuilder.java
│ │ │ │ │ ├── OffGameInteractor.java
│ │ │ │ │ ├── OffGameRouter.java
│ │ │ │ │ └── OffGameView.java
│ │ │ │ └── tictactoe/
│ │ │ │ ├── Board.java
│ │ │ │ ├── BoardCoordinate.java
│ │ │ │ ├── TicTacToeBuilder.java
│ │ │ │ ├── TicTacToeInteractor.java
│ │ │ │ ├── TicTacToeRouter.java
│ │ │ │ └── TicTacToeView.java
│ │ │ └── loggedout/
│ │ │ ├── LoggedOutBuilder.java
│ │ │ ├── LoggedOutInteractor.java
│ │ │ ├── LoggedOutRouter.java
│ │ │ └── LoggedOutView.java
│ │ └── res/
│ │ ├── layout/
│ │ │ ├── logged_out_rib.xml
│ │ │ ├── off_game_rib.xml
│ │ │ ├── root_rib.xml
│ │ │ └── tic_tac_toe_rib.xml
│ │ └── values/
│ │ └── ub__strings.xml
│ ├── memory-leaks/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ ├── RootActivity.java
│ │ │ ├── SampleApplication.java
│ │ │ └── root/
│ │ │ ├── RootBuilder.java
│ │ │ ├── RootInteractor.java
│ │ │ ├── RootRouter.java
│ │ │ ├── RootView.java
│ │ │ ├── loggedin/
│ │ │ │ ├── LoggedInBuilder.java
│ │ │ │ ├── LoggedInInteractor.java
│ │ │ │ └── LoggedInRouter.java
│ │ │ └── loggedout/
│ │ │ ├── LoggedOutBuilder.java
│ │ │ ├── LoggedOutInteractor.java
│ │ │ ├── LoggedOutRouter.java
│ │ │ └── LoggedOutView.java
│ │ └── res/
│ │ ├── layout/
│ │ │ ├── logged_out_rib.xml
│ │ │ └── root_rib.xml
│ │ └── values/
│ │ └── ub__strings.xml
│ └── rib-workers/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── kotlin/
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── workers/
│ │ ├── ComposeApplication.kt
│ │ ├── root/
│ │ │ ├── RootActivity.kt
│ │ │ ├── RootInteractor.kt
│ │ │ ├── RootRouter.kt
│ │ │ ├── RootScope.kt
│ │ │ ├── RootView.kt
│ │ │ ├── logger/
│ │ │ │ └── ApplicationLevelWorkerLogger.kt
│ │ │ └── main/
│ │ │ ├── MainInteractor.kt
│ │ │ ├── MainRouter.kt
│ │ │ ├── MainScope.kt
│ │ │ ├── MainView.kt
│ │ │ ├── ribworkerselection/
│ │ │ │ ├── RibWorkerBindTypeClickType.kt
│ │ │ │ ├── RibWorkerSelectionInteractor.kt
│ │ │ │ ├── RibWorkerSelectionRouter.kt
│ │ │ │ ├── RibWorkerSelectionScope.kt
│ │ │ │ ├── RibWorkerSelectionView.kt
│ │ │ │ └── RibWorkerSelectionViewModel.kt
│ │ │ └── workers/
│ │ │ ├── BackgroundWorker.kt
│ │ │ ├── DefaultRibCoroutineWorker.kt
│ │ │ ├── DefaultWorker.kt
│ │ │ ├── IoWorker.kt
│ │ │ └── UiWorker.kt
│ │ └── util/
│ │ ├── EventStream.kt
│ │ └── StateStream.kt
│ └── res/
│ ├── drawable/
│ │ └── ic_launcher_background.xml
│ ├── drawable-v24/
│ │ └── ic_launcher_foreground.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ └── values/
│ ├── strings.xml
│ └── themes.xml
├── gradle/
│ ├── app-libs.versions.toml
│ ├── japicmp.gradle
│ ├── libs.versions.toml
│ ├── test-libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── libraries/
│ ├── rib-android/
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ ├── main/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── core/
│ │ │ ├── ActivityContext.kt
│ │ │ ├── ActivityStarter.kt
│ │ │ ├── BasicViewRouter.kt
│ │ │ ├── IntentCreator.kt
│ │ │ ├── IntentCreatorImpl.kt
│ │ │ ├── IntentFactory.kt
│ │ │ ├── RibActivity.kt
│ │ │ ├── RibDebugOverlay.kt
│ │ │ ├── RxActivityEvents.kt
│ │ │ ├── ViewBuilder.kt
│ │ │ ├── ViewPresenter.kt
│ │ │ ├── ViewRouter.kt
│ │ │ ├── XRay.kt
│ │ │ └── lifecycle/
│ │ │ ├── ActivityCallbackEvent.kt
│ │ │ ├── ActivityEvent.kt
│ │ │ └── ActivityLifecycleEvent.kt
│ │ └── test/
│ │ ├── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── core/
│ │ │ ├── BundleTest.kt
│ │ │ ├── RibActivityTest.kt
│ │ │ ├── ViewBuilderTest.kt
│ │ │ └── XRayTest.kt
│ │ └── resources/
│ │ └── robolectric.properties
│ ├── rib-android-compose/
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ └── main/
│ │ └── kotlin/
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── core/
│ │ ├── BasicComposeRouter.kt
│ │ └── ComposePresenter.kt
│ ├── rib-android-core/
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ ├── main/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── core/
│ │ │ ├── ActivityDelegate.kt
│ │ │ ├── CoreAppCompatActivity.kt
│ │ │ └── HasActivityDelegate.kt
│ │ └── test/
│ │ └── resources/
│ │ └── robolectric.properties
│ ├── rib-base/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ ├── main/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── core/
│ │ │ ├── BasicInteractor.kt
│ │ │ ├── BasicRouter.kt
│ │ │ ├── Builder.kt
│ │ │ ├── Bundle.kt
│ │ │ ├── EmptyPresenter.kt
│ │ │ ├── FlowAsScope.kt
│ │ │ ├── Initializer.kt
│ │ │ ├── Interactor.kt
│ │ │ ├── InteractorAndViewModule.kt
│ │ │ ├── InteractorBaseComponent.kt
│ │ │ ├── InteractorComponent.kt
│ │ │ ├── InteractorModule.kt
│ │ │ ├── InteractorType.kt
│ │ │ ├── LazyBackingProperty.kt
│ │ │ ├── Presenter.kt
│ │ │ ├── Rib.kt
│ │ │ ├── RibBuilder.kt
│ │ │ ├── RibCoroutineWorker.kt
│ │ │ ├── RibEventType.kt
│ │ │ ├── RibEvents.kt
│ │ │ ├── RibInteractor.kt
│ │ │ ├── RibRefWatcher.kt
│ │ │ ├── RibRouterEvent.kt
│ │ │ ├── Router.kt
│ │ │ ├── Worker.kt
│ │ │ ├── WorkerBinder.kt
│ │ │ ├── WorkerScopeProvider.kt
│ │ │ ├── WorkerUnbinder.kt
│ │ │ ├── internal/
│ │ │ │ └── CoreFriendModuleApi.kt
│ │ │ └── lifecycle/
│ │ │ ├── InteractorEvent.kt
│ │ │ ├── PresenterEvent.kt
│ │ │ └── WorkerEvent.kt
│ │ └── test/
│ │ └── kotlin/
│ │ ├── android/
│ │ │ └── os/
│ │ │ ├── Bundle.kt
│ │ │ └── Parcelable.kt
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── core/
│ │ ├── InteractorAndRouterTest.kt
│ │ ├── LazyBackingPropertyTest.kt
│ │ ├── RecordingObserver.kt
│ │ ├── RibCoroutineWorkerTest.kt
│ │ ├── RibEventsTest.kt
│ │ ├── RibEventsUtils.kt
│ │ ├── RibRefWatcherTest.kt
│ │ ├── RouterTest.kt
│ │ ├── WorkerBinderTest.kt
│ │ ├── WorkerScopeProviderTest.kt
│ │ └── WorkerTest.kt
│ ├── rib-compiler-app/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ ├── main/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── compiler/
│ │ │ ├── AnnotatedClass.kt
│ │ │ ├── AnnotationVerifier.kt
│ │ │ ├── CompilerUtils.kt
│ │ │ ├── Constants.kt
│ │ │ ├── ErrorReporter.kt
│ │ │ ├── Generator.kt
│ │ │ ├── InteractorAnnotatedClass.kt
│ │ │ ├── InteractorAnnotationVerifier.kt
│ │ │ ├── ProcessContext.kt
│ │ │ ├── ProcessorPipeline.kt
│ │ │ ├── RibInteractorProcessorPipeline.kt
│ │ │ ├── RibProcessor.kt
│ │ │ ├── TypeProcessorPipeline.kt
│ │ │ └── VerificationFailedException.kt
│ │ └── test/
│ │ ├── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── compiler/
│ │ │ ├── InteractorAnnotationVerifierTest.kt
│ │ │ └── InteractorProcessorTestBase.kt
│ │ └── resources/
│ │ └── fixtures/
│ │ ├── AnnotatedInteractor.java
│ │ ├── AnnotatedInteractorNoSuffix.java
│ │ ├── AnnotatedNonInteractor.java
│ │ └── CustomConstructorInteractor.java
│ ├── rib-compiler-test/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ ├── libs/
│ │ │ └── tools.jar
│ │ └── src/
│ │ ├── main/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── compiler/
│ │ │ ├── Constants.kt
│ │ │ ├── InteractorTestGenerator.kt
│ │ │ └── RibTestProcessor.kt
│ │ └── test/
│ │ ├── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── compiler/
│ │ │ ├── InteractorTestGeneratorProcessorTestBase.kt
│ │ │ └── InteractorTestGeneratorTest.kt
│ │ └── resources/
│ │ └── fixtures/
│ │ ├── AnnotatedBasicInteractor.java
│ │ ├── AnnotatedInteractor.java
│ │ ├── TestAnnotatedBasicInteractor.java
│ │ ├── TestAnnotatedInteractor.java
│ │ └── TestForcedBasicInteractor.java
│ ├── rib-coroutines/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ ├── main/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── core/
│ │ │ ├── RibCoroutineScopes.kt
│ │ │ ├── RibCoroutinesConfig.kt
│ │ │ ├── RibDispatchers.kt
│ │ │ └── internal/
│ │ │ └── CoroutinesFriendModuleApi.kt
│ │ └── test/
│ │ └── kotlin/
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── core/
│ │ └── RibCoroutineScopesTest.kt
│ ├── rib-coroutines-test/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ ├── main/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── core/
│ │ │ ├── RibCoroutinesRule.kt
│ │ │ ├── TestRibCoroutineScopes.kt
│ │ │ └── TestRibDispatchers.kt
│ │ └── test/
│ │ └── kotlin/
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── core/
│ │ ├── RibCoroutinesRuleTest.kt
│ │ ├── RibDispatchersTest.kt
│ │ └── RibScopesTest.kt
│ ├── rib-debug-utils/
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ └── main/
│ │ └── kotlin/
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── core/
│ │ └── RouterDebugUtils.kt
│ ├── rib-router-navigator/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ ├── main/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── core/
│ │ │ ├── RouterAndState.kt
│ │ │ ├── RouterNavigator.kt
│ │ │ ├── RouterNavigatorEvent.kt
│ │ │ ├── RouterNavigatorEventType.kt
│ │ │ ├── RouterNavigatorEvents.kt
│ │ │ ├── RouterNavigatorFactory.kt
│ │ │ ├── RouterNavigatorState.kt
│ │ │ └── StackRouterNavigator.kt
│ │ └── test/
│ │ └── kotlin/
│ │ ├── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── core/
│ │ │ ├── RouterAndStateTest.kt
│ │ │ └── StackRouterNavigatorTest.kt
│ │ └── os/
│ │ ├── Bundle.kt
│ │ └── Parcelable.kt
│ ├── rib-screen-stack-base/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ └── main/
│ │ └── kotlin/
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── core/
│ │ └── screenstack/
│ │ ├── ScreenStackBase.kt
│ │ ├── ViewProvider.kt
│ │ └── lifecycle/
│ │ └── ScreenStackEvent.kt
│ ├── rib-test/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ └── main/
│ │ └── kotlin/
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── core/
│ │ ├── AndroidRecordingRx2Observer.kt
│ │ ├── FakeComponent.kt
│ │ ├── FakeInteractor.kt
│ │ ├── FakePresenter.kt
│ │ ├── FakeRouter.kt
│ │ ├── FakeWorker.kt
│ │ ├── InteractorHelper.kt
│ │ ├── PresenterHelper.kt
│ │ ├── RibTestBasePlaceholder.kt
│ │ ├── RouterHelper.kt
│ │ ├── TestRibCoroutineWorker.kt
│ │ └── WorkerHelper.kt
│ ├── rib-workflow/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ └── src/
│ │ ├── main/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ └── workflow/
│ │ │ └── core/
│ │ │ ├── ActionableItem.kt
│ │ │ ├── Step.kt
│ │ │ ├── Workflow.kt
│ │ │ └── internal/
│ │ │ └── WorkflowFriendModuleApi.kt
│ │ └── test/
│ │ └── kotlin/
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── workflow/
│ │ └── core/
│ │ ├── AndroidSchedulersRule.kt
│ │ ├── DelegatingScheduler.kt
│ │ ├── StepTest.kt
│ │ └── WorkflowTest.kt
│ └── rib-workflow-test/
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── kotlin/
│ └── com/
│ └── uber/
│ └── rib/
│ └── workflow/
│ └── core/
│ └── StepTester.kt
├── releasing.sh
├── settings.gradle
├── tooling/
│ ├── rib-flipper-plugin/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── desktop/
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ └── src/
│ │ │ ├── TreeChart.js
│ │ │ ├── index.js
│ │ │ └── utils.js
│ │ ├── gradle.properties
│ │ └── src/
│ │ └── main/
│ │ └── kotlin/
│ │ └── com/
│ │ └── uber/
│ │ └── rib/
│ │ └── flipper/
│ │ ├── RibEventPayload.kt
│ │ ├── RibTreeMessageType.kt
│ │ └── RibTreePlugin.kt
│ ├── rib-intellij-plugin/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ ├── deploy/
│ │ │ └── rib-intellij-plugin.jar
│ │ ├── gradle.properties
│ │ ├── native/
│ │ │ ├── gradle.properties
│ │ │ └── intellij-broadcast-rib/
│ │ │ ├── README.md
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── debug/
│ │ │ └── broadcast/
│ │ │ └── rib/
│ │ │ ├── RibHierarchyDebugBroadcastHandler.java
│ │ │ ├── RibHierarchyPayload.java
│ │ │ ├── RibHierarchyUtils.java
│ │ │ └── RibHierarchyWithSelectionPayload.java
│ │ └── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com/
│ │ │ │ └── uber/
│ │ │ │ └── presidio/
│ │ │ │ └── intellij_plugin/
│ │ │ │ ├── action/
│ │ │ │ │ └── rib/
│ │ │ │ │ ├── GenerateAction.java
│ │ │ │ │ ├── GenerateRibAction.java
│ │ │ │ │ ├── GenerateRibDialog.form
│ │ │ │ │ ├── GenerateRibDialog.java
│ │ │ │ │ └── Generators.java
│ │ │ │ └── generator/
│ │ │ │ ├── Generator.java
│ │ │ │ ├── GeneratorPair.java
│ │ │ │ └── rib/
│ │ │ │ ├── BuilderGenerator.java
│ │ │ │ ├── InteractorWithEmptyPresenterGenerator.java
│ │ │ │ ├── InteractorWithEmptyPresenterTestGenerator.java
│ │ │ │ ├── InteractorWithPresenterGenerator.java
│ │ │ │ ├── InteractorWithPresenterTestGenerator.java
│ │ │ │ ├── RouterGenerator.java
│ │ │ │ ├── RouterTestGenerator.java
│ │ │ │ ├── ViewBuilderGenerator.java
│ │ │ │ ├── ViewGenerator.java
│ │ │ │ ├── ViewRouterGenerator.java
│ │ │ │ └── ViewRouterTestGenerator.java
│ │ │ ├── kotlin/
│ │ │ │ └── com/
│ │ │ │ └── uber/
│ │ │ │ └── intellij/
│ │ │ │ └── plugin/
│ │ │ │ └── android/
│ │ │ │ └── rib/
│ │ │ │ ├── AndroidDeviceRepository.kt
│ │ │ │ ├── AttachRibProjectServiceActivity.kt
│ │ │ │ ├── CommandLineUtils.kt
│ │ │ │ ├── RibHierarchyBrowser.kt
│ │ │ │ ├── RibHierarchyPanel.kt
│ │ │ │ ├── RibHierarchyUtils.kt
│ │ │ │ ├── RibIcons.kt
│ │ │ │ ├── RibProjectService.kt
│ │ │ │ ├── RibViewBrowser.kt
│ │ │ │ ├── io/
│ │ │ │ │ ├── AckRequest.kt
│ │ │ │ │ ├── LogcatMessageDecoder.kt
│ │ │ │ │ ├── LogcatRequestProcessor.kt
│ │ │ │ │ ├── Request.kt
│ │ │ │ │ ├── RequestProcessor.kt
│ │ │ │ │ ├── Response.kt
│ │ │ │ │ ├── RibHierarchyRequest.kt
│ │ │ │ │ ├── RibHighlightRequest.kt
│ │ │ │ │ └── RibLocateRequest.kt
│ │ │ │ └── ui/
│ │ │ │ ├── HierarchyBrowserBase.kt
│ │ │ │ ├── RibHierarchyActivityDescriptor.kt
│ │ │ │ ├── RibHierarchyApplicationDescriptor.kt
│ │ │ │ ├── RibHierarchyDescriptor.kt
│ │ │ │ ├── RibHierarchyNodeDescriptor.kt
│ │ │ │ ├── RibHierarchyRootNodeDescriptor.kt
│ │ │ │ ├── RibHierarchyTreeStructure.kt
│ │ │ │ ├── RibViewNodeDescriptor.kt
│ │ │ │ └── RibViewRootNodeDescriptor.kt
│ │ │ └── resources/
│ │ │ ├── META-INF/
│ │ │ │ └── plugin.xml
│ │ │ ├── partials/
│ │ │ │ ├── ExampleInteractorTest.java.partial
│ │ │ │ ├── ExampleRouterTest.java.partial
│ │ │ │ ├── RibInteractorDidBecomeActive.java.partial
│ │ │ │ └── RibInteractorWillResignActive.java.partial
│ │ │ └── templates/
│ │ │ ├── java/
│ │ │ │ ├── RibBuilder.java.template
│ │ │ │ ├── RibInteractorWithEmptyPresenter.java.template
│ │ │ │ ├── RibInteractorWithEmptyPresenterTest.java.template
│ │ │ │ ├── RibInteractorWithPresenter.java.template
│ │ │ │ ├── RibInteractorWithPresenterTest.java.template
│ │ │ │ ├── RibRouter.java.template
│ │ │ │ ├── RibRouterTest.java.template
│ │ │ │ ├── RibView.java.template
│ │ │ │ ├── RibViewBuilder.java.template
│ │ │ │ ├── RibViewRouter.java.template
│ │ │ │ └── RibViewRouterTest.java.template
│ │ │ └── kotlin/
│ │ │ ├── RibBuilder.kt.template
│ │ │ ├── RibInteractorWithEmptyPresenter.kt.template
│ │ │ ├── RibInteractorWithEmptyPresenterTest.kt.template
│ │ │ ├── RibInteractorWithPresenter.kt.template
│ │ │ ├── RibInteractorWithPresenterTest.kt.template
│ │ │ ├── RibRouter.kt.template
│ │ │ ├── RibRouterTest.kt.template
│ │ │ ├── RibView.kt.template
│ │ │ ├── RibViewBuilder.kt.template
│ │ │ ├── RibViewRouter.kt.template
│ │ │ └── RibViewRouterTest.kt.template
│ │ └── test/
│ │ └── java/
│ │ └── com/
│ │ └── uber/
│ │ └── presidio/
│ │ └── intellij_plugin/
│ │ └── action/
│ │ └── rib/
│ │ └── RibGeneratorsTest.java
│ └── utils/
│ └── intellij-broadcast-core/
│ ├── README.md
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── uber/
│ └── debug/
│ └── broadcast/
│ └── core/
│ ├── AckDebugBroadcastHandler.java
│ ├── DebugBroadcastReceiver.java
│ ├── DebugBroadcastRequest.java
│ └── DebugBroadcastResponse.java
└── tutorials/
├── tutorial1/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ ├── RootActivity.java
│ │ │ ├── SampleApplication.java
│ │ │ └── root/
│ │ │ ├── RootBuilder.java
│ │ │ ├── RootInteractor.java
│ │ │ ├── RootRouter.java
│ │ │ └── RootView.java
│ │ └── res/
│ │ ├── layout/
│ │ │ └── root_rib.xml
│ │ └── values/
│ │ └── ub__strings.xml
│ └── test/
│ └── java/
│ └── com/
│ └── uber/
│ └── rib/
│ └── root/
│ ├── RootInteractorTest.java
│ └── RootRouterTest.java
├── tutorial2/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ ├── RootActivity.java
│ │ │ ├── SampleApplication.java
│ │ │ └── root/
│ │ │ ├── RootBuilder.java
│ │ │ ├── RootInteractor.java
│ │ │ ├── RootRouter.java
│ │ │ ├── RootView.java
│ │ │ ├── loggedin/
│ │ │ │ ├── offgame/
│ │ │ │ │ ├── OffGameBuilder.java
│ │ │ │ │ ├── OffGameInteractor.java
│ │ │ │ │ ├── OffGameRouter.java
│ │ │ │ │ └── OffGameView.java
│ │ │ │ └── tictactoe/
│ │ │ │ ├── Board.java
│ │ │ │ ├── BoardCoordinate.java
│ │ │ │ ├── TicTacToeBuilder.java
│ │ │ │ ├── TicTacToeInteractor.java
│ │ │ │ ├── TicTacToeRouter.java
│ │ │ │ └── TicTacToeView.java
│ │ │ └── loggedout/
│ │ │ ├── LoggedOutBuilder.java
│ │ │ ├── LoggedOutInteractor.java
│ │ │ ├── LoggedOutRouter.java
│ │ │ └── LoggedOutView.java
│ │ └── res/
│ │ ├── layout/
│ │ │ ├── logged_out_rib.xml
│ │ │ ├── off_game_rib.xml
│ │ │ ├── root_rib.xml
│ │ │ └── tic_tac_toe_rib.xml
│ │ └── values/
│ │ └── ub__strings.xml
│ └── test/
│ └── java/
│ └── com/
│ └── uber/
│ └── rib/
│ └── root/
│ ├── RootInteractorTest.java
│ ├── RootRouterTest.java
│ └── loggedout/
│ ├── LoggedOutInteractorTest.java
│ └── LoggedOutRouterTest.java
├── tutorial3/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ ├── RootActivity.java
│ │ │ ├── SampleApplication.java
│ │ │ └── root/
│ │ │ ├── RootBuilder.java
│ │ │ ├── RootInteractor.java
│ │ │ ├── RootRouter.java
│ │ │ ├── RootView.java
│ │ │ ├── loggedin/
│ │ │ │ ├── LoggedInBuilder.java
│ │ │ │ ├── LoggedInInteractor.java
│ │ │ │ ├── LoggedInRouter.java
│ │ │ │ ├── offgame/
│ │ │ │ │ ├── OffGameBuilder.java
│ │ │ │ │ ├── OffGameInteractor.java
│ │ │ │ │ ├── OffGameRouter.java
│ │ │ │ │ └── OffGameView.java
│ │ │ │ └── tictactoe/
│ │ │ │ ├── Board.java
│ │ │ │ ├── BoardCoordinate.java
│ │ │ │ ├── TicTacToeBuilder.java
│ │ │ │ ├── TicTacToeInteractor.java
│ │ │ │ ├── TicTacToeRouter.java
│ │ │ │ └── TicTacToeView.java
│ │ │ └── loggedout/
│ │ │ ├── LoggedOutBuilder.java
│ │ │ ├── LoggedOutInteractor.java
│ │ │ ├── LoggedOutRouter.java
│ │ │ └── LoggedOutView.java
│ │ └── res/
│ │ ├── layout/
│ │ │ ├── logged_out_rib.xml
│ │ │ ├── off_game_rib.xml
│ │ │ ├── root_rib.xml
│ │ │ └── tic_tac_toe_rib.xml
│ │ └── values/
│ │ └── ub__strings.xml
│ └── test/
│ └── java/
│ └── com/
│ └── uber/
│ └── rib/
│ └── root/
│ ├── RootInteractorTest.java
│ ├── RootRouterTest.java
│ ├── loggedin/
│ │ ├── LoggedInInteractorTest.java
│ │ ├── LoggedInRouterTest.java
│ │ ├── offgame/
│ │ │ ├── OffGameInteractorTest.java
│ │ │ └── OffGameRouterTest.java
│ │ └── tictactoe/
│ │ ├── TicTacToeInteractorTest.java
│ │ └── TicTacToeRouterTest.java
│ └── loggedout/
│ ├── LoggedOutInteractorTest.java
│ └── LoggedOutRouterTest.java
├── tutorial3-completed/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── uber/
│ │ │ └── rib/
│ │ │ ├── RootActivity.java
│ │ │ ├── SampleApplication.java
│ │ │ └── root/
│ │ │ ├── RootBuilder.java
│ │ │ ├── RootInteractor.java
│ │ │ ├── RootRouter.java
│ │ │ ├── RootView.java
│ │ │ ├── loggedin/
│ │ │ │ ├── LoggedInBuilder.java
│ │ │ │ ├── LoggedInInteractor.java
│ │ │ │ ├── LoggedInRouter.java
│ │ │ │ ├── MutableScoreStream.java
│ │ │ │ ├── ScoreStream.java
│ │ │ │ ├── offgame/
│ │ │ │ │ ├── OffGameBuilder.java
│ │ │ │ │ ├── OffGameInteractor.java
│ │ │ │ │ ├── OffGameRouter.java
│ │ │ │ │ └── OffGameView.java
│ │ │ │ └── tictactoe/
│ │ │ │ ├── Board.java
│ │ │ │ ├── BoardCoordinate.java
│ │ │ │ ├── TicTacToeBuilder.java
│ │ │ │ ├── TicTacToeInteractor.java
│ │ │ │ ├── TicTacToeRouter.java
│ │ │ │ └── TicTacToeView.java
│ │ │ └── loggedout/
│ │ │ ├── LoggedOutBuilder.java
│ │ │ ├── LoggedOutInteractor.java
│ │ │ ├── LoggedOutRouter.java
│ │ │ └── LoggedOutView.java
│ │ └── res/
│ │ ├── layout/
│ │ │ ├── logged_out_rib.xml
│ │ │ ├── off_game_rib.xml
│ │ │ ├── root_rib.xml
│ │ │ └── tic_tac_toe_rib.xml
│ │ └── values/
│ │ └── ub__strings.xml
│ └── test/
│ └── java/
│ └── com/
│ └── uber/
│ └── rib/
│ └── root/
│ ├── RootInteractorTest.java
│ ├── RootRouterTest.java
│ ├── loggedin/
│ │ ├── LoggedInInteractorTest.java
│ │ ├── LoggedInRouterTest.java
│ │ ├── offgame/
│ │ │ ├── OffGameInteractorTest.java
│ │ │ └── OffGameRouterTest.java
│ │ └── tictactoe/
│ │ ├── TicTacToeInteractorTest.java
│ │ └── TicTacToeRouterTest.java
│ └── loggedout/
│ ├── LoggedOutInteractorTest.java
│ └── LoggedOutRouterTest.java
└── tutorial4/
├── README.md
├── build.gradle.kts
└── src/
└── main/
├── AndroidManifest.xml
├── java/
│ └── com/
│ └── uber/
│ └── rib/
│ ├── RootActivity.java
│ ├── SampleApplication.java
│ └── root/
│ ├── RootActionableItem.java
│ ├── RootBuilder.java
│ ├── RootInteractor.java
│ ├── RootRouter.java
│ ├── RootView.java
│ ├── RootWorkflow.java
│ ├── RootWorkflowModel.java
│ ├── UserName.kt
│ ├── WorkflowFactory.java
│ ├── loggedin/
│ │ ├── GameKey.java
│ │ ├── GameProvider.java
│ │ ├── LoggedInActionableItem.java
│ │ ├── LoggedInBuilder.java
│ │ ├── LoggedInInteractor.java
│ │ ├── LoggedInRouter.java
│ │ ├── MutableScoreStream.java
│ │ ├── ScoreStream.java
│ │ ├── offgame/
│ │ │ ├── OffGameBuilder.java
│ │ │ ├── OffGameInteractor.java
│ │ │ ├── OffGameRouter.java
│ │ │ └── OffGameView.java
│ │ ├── randomWinner/
│ │ │ ├── RandomWinnerBuilder.java
│ │ │ ├── RandomWinnerInteractor.java
│ │ │ ├── RandomWinnerRouter.java
│ │ │ └── RandomWinnerView.java
│ │ └── tictactoe/
│ │ ├── Board.java
│ │ ├── BoardCoordinate.java
│ │ ├── TicTacToeBuilder.java
│ │ ├── TicTacToeInteractor.java
│ │ ├── TicTacToeRouter.java
│ │ └── TicTacToeView.java
│ └── loggedout/
│ ├── LoggedOutBuilder.java
│ ├── LoggedOutInteractor.java
│ ├── LoggedOutRouter.java
│ └── LoggedOutView.java
└── res/
├── layout/
│ ├── game_button.xml
│ ├── logged_out_rib.xml
│ ├── off_game_rib.xml
│ ├── root_rib.xml
│ └── tic_tac_toe_rib.xml
└── values/
└── ub__strings.xml
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
**Description**:
**Related issue(s)**:
================================================
FILE: .github/workflows/android.yml
================================================
name: CI Android
on: [push, pull_request]
jobs:
build:
name: JDK ${{ matrix.java_version }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./
strategy:
matrix:
java_version: [17]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install JDK
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: ${{ matrix.java_version }}
- name: Install Android SDK
uses: malinskiy/action-android/install-sdk@release/0.1.1
- name: Gradle Wrapper Validation
uses: gradle/wrapper-validation-action@v1
- name: Configure Gradle
# Gradle configuration install deps
run: ./gradlew help
- name: Spot check
run: ./gradlew spotlessCheck --stacktrace
- name: Build Project
run: ./gradlew assemble --stacktrace
- name : Testing
run: ./gradlew test --stacktrace
- name: Final Checks
run: ./gradlew check --stacktrace
- name: Upload snapshot
run: ./gradlew publish -PmavenCentralUsername="${{ secrets.SonatypeUsername }}" -PmavenCentralPassword="${{ secrets.SonatypePassword }}"
if: success() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && matrix.java_version == '11'
================================================
FILE: .gitignore
================================================
###OSX###
.DS_Store
.AppleDouble
.LSOverride
# Icon must ends with two \r.
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
###Linux###
*~
# KDE directory preferences
.directory
###Android###
# Built application files
*.apk
*.ap_
# Files for ART and Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gradle files
.gradle/
.gradletasknamecache
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Lint
lint-report.html
lint-report_files/
lint_result.txt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
###IntelliJ###
*.iml
*.ipr
*.iws
.idea/
.intellijPlatform/
###Eclipse###
*.pydevproject
.metadata
tmp/
*.tmp
*.bak
*.swp
*~.nib
.settings/
.loadpath
### Xcode ###
# Build generated
build/
DerivedData/
# Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# Swift Package Manager
.build/
# CocoaPods
Pods/
Podfile.lock
*.xcworkspace/
# Carthage
Cartfile.resolved
Carthage/
### Other ###
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
# sbteclipse plugin
.target
# TeXlipse plugin
.texlipse
# kotlin
annotations/
================================================
FILE: CHANGELOG.md
================================================
# Changelog
### Version 0.1.0
* Initial release
### Version 0.9.2
* Fix forked Workflow invoking didComplete multiple times
### Version 0.9.3
* Upgraded iOS library to Swift 5
### Version 0.10.0
* Updates from the internal fork of RIBs (see Releases section)
### Version 0.10.1
* Added REPLACE_TOP `RouterNavigator` flag
### Version 0.11.0
* Migrate library modules to Kotlin but keep the same APIs
### Version 0.11.1
* Bugfixes for Kotlin migration
### Version 0.11.2
* One more bugfix and log message for Kotlin migration edge case
### Version 0.11.3
* Added NEW_TASK_REPLACE `RouterNavigator` flag
### Version 0.12.0
* Added Jetpack Compose RIB classes
### Version 0.12.1
* `BasicComposeRouter` now auto-attaches child composable content
### Version 0.12.2
* Work around Bazel desugar issues
### Version 0.13.0
* [Android] Adds rib-coroutines and rib-coroutines-test to enable corotouines interop
### Version 0.13.1
* [Android] Upgrade to Kotlin 1.7
* [Android] Add Window Focus Event API
* [Android] Add open modifier to doOnRemoved()8
* [Android] Deprecate mockitokotlin2
### Version 0.13.2
* [Android] Reverting binary breaking change from 0.13.1 on Basic Interactor
### Version 0.13.3
* [Intellij] Plugin 0.1.5
* [Android] Clear cached CoroutineScope instance once its job completes
* [Android] Make all TestDispatchers in TestRibDispatchers use the same TestCoroutineScheduler
### Version 0.14.0
* [Android] Bump Kotlin, Gradle, and other dependencies versions.
* [Android] Provide option to bind multiple Workers at once on specific RibDispatchers AndroidAndroid related tickets
* [Android] Use Kotlin contracts to remove var and !! usage in RibCoroutineWorker
* [Android] [Draft] Add capability for binding multiple Workers in specified CoroutineDispatcher AndroidAndroid related tickets
* [Android] Enable explicit api mode for Kotlin libraries AndroidAndroid related tickets
* [Android] Provide a more idiomatic Java API for RibDispatchers
* [Android] Upgrade code formatters versions AndroidAndroid related tickets
* [Android] Create README for Compose Demo AndroidAndroid related tickets
* [Android] [Rib Worker] Specify CoroutineDispatcher for onStart/onStop and provide WorkerBinder monitoring option AndroidAndroid related tickets
* [Android] Reduce Rx <-> Coroutines interop and allow unconfined coroutines to run eagerly inside Workers onStart
* [Android] Redesign RouterAndState to avoid router caching
* [Android] Fix router navigator events source compatibility
* [Android] Enable strict explicit API mode on rib-base
* [Android] Introduce RibCoroutineWorker AndroidAndroid related tickets
* [Android] Replacing some Behavior/Publish Relay usage in core artifacts with coroutines
### Version 0.14.1
* [Android] Open lifecycleFlow, thus enabling it for mocking
* [Android] [WorkerBinder] Guard against potential Worker.coroutineContext being null while using Mockito
### Version 0.14.2
* [Android] Fix potential for deadlocks in `Worker` binding. by @psteiger in https://github.com/uber/RIBs/pull/582
* [Android] Add Rib Worker demo app by @FranAguilera in https://github.com/uber/RIBs/pull/575
### Version 0.15.0
* Only complete the worker's scope after calling `Worker.onStop` by @psteiger in https://github.com/uber/RIBs/pull/585
* Improve KDoc on `ActivityLifecycleEvent` by explaining ordering semantics. by @psteiger in https://github.com/uber/RIBs/pull/586
* Make use of `jvmToolchain` for building the project. by @psteiger in https://github.com/uber/RIBs/pull/583
* Revamp Gradle scripts by @psteiger in https://github.com/uber/RIBs/pull/588
* Deprecate old worker by @FranAguilera in https://github.com/uber/RIBs/pull/597
* Allow overriding default CoroutineDispatcher for WorkerBinder calls by @FranAguilera in https://github.com/uber/RIBs/pull/596
* Update README.md by @FranAguilera in https://github.com/uber/RIBs/pull/600
* Deprecate WorkerUnbinder by @FranAguilera in https://github.com/uber/RIBs/pull/601
* Expose ribActionEvents stream by @FranAguilera in https://github.com/uber/RIBs/pull/599
### Version 0.15.1
* [Android] Remove use of @JvmDefault in favor of using -Xjvm-default=all by @psteiger in https://github.com/uber/RIBs/pull/576
### Version 0.15.2
* [Android] Set view tree owners for RibActivity
### Version 0.15.3
* Add RibCoroutineWorker.bind that receives multiple workers by @FranAguilera in https://github.com/uber/RIBs/pull/607
* Change default CoroutineContext from empty to default for the RibCoroutineWorker<>Worker conversion by @FranAguilera in https://github.com/uber/RIBs/pull/608
* Add `RibCoroutineWorker` factory method with `CoroutineScope` as receiver by @psteiger in https://github.com/uber/RIBs/pull/610
* Update coroutines 1.7.3 by @tyvsmith in https://github.com/uber/RIBs/pull/609
* Bump kotlinx.coroutines.test to 1.7.3 by @psteiger in https://github.com/uber/RIBs/pull/611
### Version 0.15.4
* Set JvmVersion to 1.8
### Version 0.16.0
* Get rid of suppressions for "invisible_reference" and "invisible_member" by @psteiger in https://github.com/uber/RIBs/pull/618
* Introduce `TestScope.test(RibCoroutineWorker)` test helper utility. by @psteiger in https://github.com/uber/RIBs/pull/620
### Version 0.16.1
* [Android] Remove duplicate method by @jbarr in https://github.com/uber/RIBs/pull/621
### Version 0.16.2
* Make suspend functions callable inside `test(worker) { }` by @psteiger in https://github.com/uber/RIBs/pull/624
* [RibCoroutineWorker] In `asWorker()`, keep scope alive until lifecycl… by @psteiger in https://github.com/uber/RIBs/pull/625
### Version 0.16.3
* Fix Flipper Ribtree Plugin memory leak by @mamykin-andrey in https://github.com/uber/RIBs/pull/630
* Support classes that are both `Worker` and `RibCoroutineWorker` in co… by @psteiger in https://github.com/uber/RIBs/pull/629
* Increase buffer capacity for mutableRouterEvents flow within RibEvents by @RahulDMello in https://github.com/uber/RIBs/pull/635
* Add test asserting Rx subscription is disposed after `RibCoroutineWor… by @psteiger in https://github.com/uber/RIBs/pull/628
### Version 0.16.4
* Remove intrinsics usage in `RibCoroutineWorker` by @psteiger in https://github.com/uber/RIBs/pull/627
* Remove ios to prep for separate repos by @tyvsmith in https://github.com/uber/RIBs/pull/646
* fix url typo for ribs-ios by @tyvsmith in https://github.com/uber/RIBs/pull/647
* Replace the SharedFlow with a StateFlow in the Interactor class by @rysh88 in https://github.com/uber/RIBs/pull/651
* Remove Java 8 usage from repository. by @psteiger in https://github.com/uber/RIBs/pull/652
* Bump Kotlin, Compose, Coroutines, AGP. by @psteiger in https://github.com/uber/RIBs/pull/653
* Add tests for RibEvents buffer size configuration by @psteiger in https://github.com/uber/RIBs/pull/642
* `ScopeProvider.coroutineScope`: Fail silently when accessing outside of RIB scope. by @psteiger in https://github.com/uber/RIBs/pull/632
### Version 0.16.5
* Remove unneeded deps
* Restore source compatibility for Interactor
* Add mavenCentral publishing by @psteiger in https://github.com/uber/RIBs/pull/655
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mobile-open-source@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to RIBs
Uber welcomes contributions of all kinds and sizes. This includes everything from from simple bug reports to large features.
Before we can accept your contributions, we kindly ask you to sign our [Contributor License Agreement](https://cla-assistant.io/uber/RIBs).
Workflow
--------
We love GitHub issues!
For small feature requests, an issue first proposing it for discussion or demo implementation in a PR suffice.
For big features, please open an issue so that we can agree on the direction, and hopefully avoid investing a lot of time on a feature that might need reworking.
Small pull requests for things like typos, bug fixes, etc are always welcome.
### Code style
This project uses [ktfmt](https://github.com/facebookincubator/ktfmt), [ktlint](https://github.com/pinterest/ktlint), and [GJF](https://github.com/google/google-java-format),
provided via the [spotless](https://github.com/diffplug/spotless) gradle plugin.
If you find that one of your pull reviews does not pass the CI server check due to a code style
conflict, you can easily fix it by running: `./gradlew spotlessApply`.
Generally speaking - we use ktfmt, vanilla ktlint + 2space indents, and vanilla GJF. You can integrate both of
these in IntelliJ code style via either [GJF's official plugin](https://plugins.jetbrains.com/plugin/8527-google-java-format) or applying code style from Jetbrains' official style.
No star imports please!
DOs and DON'Ts
--------------
* DO follow our [coding style](https://github.com/uber/java-code-styles)
* DO include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken.
* DO keep the discussions focused. When a new or related topic comes up it's often better to create new issue than to side track the discussion.
* DON'T submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it.
Guiding Principles
------------------
* We allow anyone to participate in our projects. Tasks can be carried out by anyone that demonstrates the capability to complete them
* Always be respectful of one another. Assume the best in others and act with empathy at all times
* Collaborate closely with individuals maintaining the project or experienced users. Getting ideas out in the open and seeing a proposal before it's a pull request helps reduce redundancy and ensures we're all connected to the decision making process
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
RIBs depends on the following libraries:
Copyright (C) 2017 The Guava Authors
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.
================================================
FILE: README.md
================================================
[](https://github.com/uber/RIBs/actions/workflows/android.yml)
[](https://opensource.org/licenses/Apache-2.0)
[](https://search.maven.org/artifact/com.uber.rib/rib-android)
> ⚠️ **Alert:** RIBs for IOS has been has been moved to a [separate repo](https://github.com/uber/ribs-ios)
RIBs is the cross-platform architecture framework behind many mobile apps at Uber. The name RIBs is short for Router, Interactor and Builder, which are core components of this architecture. This framework is designed for mobile apps with a large number of engineers and nested states.
The RIBs architecture provides:
* **Shared architecture across iOS and Android.** Build cross-platform apps that have similar architecture, enabling iOS and Android teams to cross-review business logic code.
* **Testability and Isolation.** Classes must be easy to unit test and reason about in isolation. Individual RIB classes have distinct responsibilities like: routing, business, view logic, creation. Plus, most RIB logic is decoupled from child RIB logic. This makes RIB classes easy to test and reason about independently.
* **Tooling for developer productivity.** RIBs come with IDE tooling around code generation, memory leak detection, static analysis and runtime integrations - all which improve developer productivity for large teams or small.
* **An architecture that scales.** This architecture has proven to scale to hundreds of engineers working on the same codebase and apps with hundreds of RIBs.
## Documentation
To get started with RIBs, please refer to the [RIBs documentation](https://github.com/uber/RIBs/wiki). This describes key concepts on RIBs, from what they are for, their structure and common use cases.
To get more hands on with RIBs, we have written a [series of tutorials](https://github.com/uber/RIBs/wiki) that run you through the main aspects of the architecture with hands-on examples.
To read about the backstory on why we created RIBs, see [this blog post](https://www.uber.com/blog/new-rider-app-architecture/) we wrote when releasing RIBs in production the first time and see [this short video](https://www.youtube.com/watch?v=Q5cTT0M0YXg) where we discussed how the RIBs architecture works.
#### What is the difference between RIBs and MV*/VIPER?
MVC, MVP, MVI, MVVM and VIPER are architecture patterns. RIBs is a framework. What differentiates RIBs from frameworks based on MV*/VIPER is:
- **Business logic drives the app, not the view tree**. Unlike with MV*/VIPER, a RIB does not have to have a view. This means that the app hierarchy is driven by the business logic, not the view tree.
- **Independent business logic and view trees**. RIBs decouple how the business logic scopes are structured from view hierarchies. This allows the application to have a deep business logic tree, isolating business logic nodes, while maintaining a shallow view hierarchy making layouts, animations and transitions easy.
There are some other novel things about RIBs. However, these could also be implemented with other MV*/VIPER frameworks. These are:
- **Cross-platform approach**, allowing iOS and Android architecture to stay in sync.
- **Tooling for easier adoption** on larger apps or teams. Tooling we are open sourcing includes IDE plugins for code generation and static code analysis.
- **Strong opinions about how state should be communicated**, using DI and Rx. Each RIB defines its dependencies and what dependencies it needs from its parent. Parent components that fulfill a child’s parent dependencies are provided to child Builders as a constructor dependency to allow for hierarchical DI scoping. This means that information is communicated via these dependencies up and down the tree.
## Usage
1. Clone this repository
2. Integrate using your preferred installation mechanism
For usage of the tooling built around RIBs, please see the [Tooling section](https://github.com/uber/RIBs/wiki#rib-tooling) in our documentation.
## Installation for Android
To integrate the recommended minimum setup for RIBs add the following to your `build.gradle`:
```gradle
dependencies {
annotationProcessor 'com.uber.rib:rib-compiler-test:0.16.5'
implementation 'com.uber.rib:rib-android:0.16.5'
testImplementation 'com.uber.rib:rib-test:0.16.5'
}
```
There are a number of extension packages available as well including Kotlin extensions, Jetpack Compose support, Coroutines support
## Related projects
If you like RIBs, check out other related open source projects from our team:
- [RIBs-iOS](https://github.com/uber/ribs-ios): The iOS version of RIBs
- [Needle](https://github.com/uber/needle): a compile-time safe Swift dependency injection framework.
- [Motif](https://github.com/uber/motif): An abstract on top of Dagger offering simpler APIs for nested scopes.
- [Swift Concurrency](https://github.com/uber/swift-concurrency): a set of concurrency utility classes used by Uber, inspired by the equivalent [java.util.concurrent](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html) package classes.
- [Swift Abstract Class](https://github.com/uber/swift-abstract-class): a light-weight library along with an executable that enables compile-time safe abstract class development for Swift projects.
- [Swift Common](https://github.com/uber/swift-common): common libraries used by this set of Swift open source projects.
## License
Copyright (C) 2017 Uber Technologies
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.
================================================
FILE: RELEASING.md
================================================
Releasing
=========
Android
-------
1. Change the version in `gradle.properties` to a non-SNAPSHOT version.
2. Update the `README.md` with the new version.
3. Update the `CHANGELOG.md` for the impending release.
4. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version)
5. `git tag -a X.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version)
6. `./gradlew clean publish --no-daemon --no-parallel && ./gradlew closeAndReleaseRepository`
7. Update the `gradle.properties` to the next SNAPSHOT version.
8. `git commit -am "Prepare next development version."`
9. `git push && git push --tags`
================================================
FILE: build.gradle.kts
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.kapt) apply false
alias(libs.plugins.kotlin.ksp) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.maven.publish) apply false
alias(libs.plugins.errorprone) apply false
alias(libs.plugins.nullaway) apply false
alias(libs.plugins.intellij.platform) apply false
alias(libs.plugins.spotless) apply false
}
================================================
FILE: config/lint/lint.xml
================================================
================================================
FILE: config/spotless/copyright.java
================================================
/*
* Copyright (C) $YEAR. Uber Technologies
*
* 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.
*/
================================================
FILE: config/spotless/copyright.kt
================================================
/*
* Copyright (C) $YEAR. Uber Technologies
*
* 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.
*/
================================================
FILE: conventions/build.gradle.kts
================================================
/*
* Copyright (C) 2023. Uber Technologies
*
* 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.
*/
plugins {
`kotlin-dsl`
`kotlin-dsl-precompiled-script-plugins`
}
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
// Workaround for using version catalog on precompiled scripts.
// https://github.com/gradle/gradle/issues/15383#issuecomment-779893192
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
implementation(files(appLibs.javaClass.superclass.protectionDomain.codeSource.location))
implementation(gradleApi())
implementation(libs.gradle.android.plugin)
implementation(libs.gradle.kotlin.plugin)
implementation(plugin(libs.plugins.errorprone))
implementation(plugin(libs.plugins.nullaway))
implementation(plugin(libs.plugins.spotless))
implementation(plugin(libs.plugins.kotlin.ksp))
}
// Helper function that transforms a Gradle Plugin alias from a
// Version Catalog into a valid dependency notation for buildSrc
// See https://docs.gradle.org/current/userguide/version_catalogs.html
@Suppress("UnusedReceiverParameter")
fun DependencyHandlerScope.plugin(plugin: Provider) =
plugin.map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" }
================================================
FILE: conventions/settings.gradle.kts
================================================
/*
* Copyright (C) 2023. Uber Technologies
*
* 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.
*/
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
create("appLibs") {
from(files("../gradle/app-libs.versions.toml"))
}
}
}
rootProject.name = "conventions"
================================================
FILE: conventions/src/main/kotlin/Extensions.kt
================================================
/*
* Copyright (C) 2023. Uber Technologies
*
* 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.
*/
import com.android.build.gradle.AbstractAppExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.TestedExtension
import com.android.build.gradle.api.BaseVariant
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
import net.ltgt.gradle.nullaway.nullaway
fun AbstractAppExtension.errorprone() {
applicationVariants.configureEach { errorprone() }
testErrorprone()
}
fun LibraryExtension.errorprone() {
libraryVariants.configureEach { errorprone() }
testErrorprone()
}
fun TestedExtension.testErrorprone() {
testVariants.configureEach { errorprone() }
unitTestVariants.configureEach { errorprone() }
}
fun BaseVariant.errorprone() {
javaCompileProvider.configure {
options.errorprone.nullaway {
severity.set(CheckSeverity.ERROR)
annotatedPackages.add("com.uber")
}
options.errorprone.excludedPaths.set(".*/build/generated/.*")
}
}
================================================
FILE: conventions/src/main/kotlin/ribs.android.application.errorprone.gradle.kts
================================================
/*
* Copyright (C) 2023. Uber Technologies
*
* 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.
*/
val libs = the()
val appLibs = the().named("appLibs")
plugins {
id("ribs.android.application")
id("com.google.devtools.ksp")
id("net.ltgt.errorprone")
id("net.ltgt.nullaway")
id("ribs.spotless")
}
android {
errorprone()
}
dependencies {
ksp(appLibs.findLibrary("autodispose-errorprone").get())
ksp(appLibs.findLibrary("uber-nullaway").get())
errorprone(appLibs.findLibrary("errorprone-core").get())
errorprone(libs.guava.jre)
errorproneJavac(appLibs.findLibrary("errorprone-javac").get())
errorprone(appLibs.findLibrary("uber-nullaway").get())
}
================================================
FILE: conventions/src/main/kotlin/ribs.android.application.gradle.kts
================================================
/*
* Copyright (C) 2023. Uber Technologies
*
* 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.
*/
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
val libs = the()
plugins {
kotlin("android")
id("com.android.application")
id("ribs.spotless")
}
kotlin {
jvmToolchain(17)
compilerOptions {
jvmTarget = JvmTarget.JVM_1_8
optIn.add("kotlin.RequiresOptIn")
freeCompilerArgs.add("-Xjvm-default=all")
// TODO: For Kotlin 2.2, delete the line above and uncomment the line below.
// jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
}
}
android {
compileSdk = 36
defaultConfig {
minSdk = 21
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
lint {
// Those are just tutorials/demos. Don't fail the build because of them.
abortOnError = false
quiet = true
// FlowOperatorInvokedInComposition Lint detector crashes on lintAnalyzeDebug task
// due to version mismatch in kotlinx-metadata-jvm dependency. After bumping compose,
// verify if this can be removed and pass CI.
disable.add("FlowOperatorInvokedInComposition")
}
buildTypes {
debug {
matchingFallbacks.add("release")
}
}
}
androidComponents {
beforeVariants { variantBuilder ->
if (variantBuilder.buildType == "release") {
variantBuilder.enable = false
}
}
}
================================================
FILE: conventions/src/main/kotlin/ribs.android.library.gradle.kts
================================================
/*
* Copyright (C) 2023. Uber Technologies
*
* 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.
*/
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("android")
id("com.android.library")
id("ribs.spotless")
}
kotlin {
jvmToolchain(17)
explicitApi()
compilerOptions {
jvmTarget = JvmTarget.JVM_1_8
optIn.add("kotlin.RequiresOptIn")
freeCompilerArgs.add("-Xjvm-default=all")
// TODO: For Kotlin 2.2, delete the line above and uncomment the line below.
// jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
android {
compileSdk = 36
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
testOptions {
targetSdk = 35
unitTests {
isIncludeAndroidResources = true
}
}
}
androidComponents {
beforeVariants { variantBuilder ->
if (variantBuilder.buildType == "debug") {
variantBuilder.enable = false
}
}
}
================================================
FILE: conventions/src/main/kotlin/ribs.kotlin.library.gradle.kts
================================================
/*
* Copyright (C) 2023. Uber Technologies
*
* 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.
*/
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
id("ribs.spotless")
}
kotlin {
jvmToolchain(17)
explicitApi()
compilerOptions {
freeCompilerArgs.add("-Xjvm-default=all")
jvmTarget = JvmTarget.JVM_1_8
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
================================================
FILE: conventions/src/main/kotlin/ribs.spotless.gradle.kts
================================================
/*
* Copyright (C) 2023. Uber Technologies
*
* 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.
*/
plugins {
id("com.diffplug.spotless")
}
val libs = the()
configure {
format("misc") {
target("**/*.md", "**/.gitignore")
trimTrailingWhitespace()
endWithNewline()
}
kotlin {
target("**/*.kt")
ktlint(libs.versions.ktlint.get()).editorConfigOverride(
mapOf(
"indent_size" to "2",
"continuation_indent_size" to "4",
)
)
ktfmt(libs.versions.ktfmt.get()).googleStyle()
licenseHeaderFile(rootProject.file("config/spotless/copyright.kt"))
trimTrailingWhitespace()
endWithNewline()
}
java {
target("src/*/java/**/*.java")
googleJavaFormat(libs.versions.google.java.format.get())
licenseHeaderFile(rootProject.file("config/spotless/copyright.java"))
removeUnusedImports()
trimTrailingWhitespace()
endWithNewline()
}
kotlinGradle {
target("**/*.gradle.kts")
trimTrailingWhitespace()
endWithNewline()
}
groovyGradle {
target("**/*.gradle")
trimTrailingWhitespace()
endWithNewline()
}
}
================================================
FILE: demos/compose/README.md
================================================
The Uber RIBs Compose Demo showcases the integration of RIBs with Jetpack Compose, an Android UI toolkit for building native interfaces using a declarative approach. It demonstrates how to combine RIBs concepts such as Routers, Interactors, and Scopes with Jetpack Compose components like Composable functions and ComposeView.
Here's a summary of the key differences this demo offers:
- RIBs: RIBs (Router, Interactor, Builder) is a mobile app architecture developed by Uber, designed to manage complex navigation flows and business logic in a modular and scalable manner. RIBs have three core components: Router, Interactor, and Builder (now replaced with Motif Scope).
- Scope: In traditional RIBs architecture, the Builder creates and connects the components of a RIB, including the Router, Interactor, and View, while handling dependency injection. The Compose Demo replaces the Builder with Motif Scope, a lightweight dependency injection (DI) library. A Scope provides the necessary dependencies for a RIB, which can be shared across multiple RIBs, enhancing modularity and scalability.
- ComposePresenter: Traditional RIBs architecture employs a Presenter for communication between the Interactor and View. The Compose Demo introduces the ComposePresenter, containing a composable function that describes the UI, allowing RIBs to utilize Jetpack Compose for UI creation.
- ComposeView: While traditional RIBs architecture uses Android ViewGroup components for the View, the Compose Demo employs a ComposeView to host Jetpack Compose content within the RIB's view hierarchy.
- UI: The demo app leverages Jetpack Compose for UI definition in each RIB, with the MainView composable function displaying the Main RIB UI and hosting the content of the active child RIB.
- Coroutines: The Compose Demo uses coroutines to handle asynchronous tasks. Integrated into the RIBs architecture by extending the BasicInteractor with a coroutineScope property, coroutines enable non-blocking, concurrent, and readable asynchronous code while automatically managing their lifecycle.
- Flow: Flow, a Kotlin library for reactive data streams, is used in the Compose Demo to observe and manage state changes. For example, AuthStream employs MutableStateFlow and StateFlow to handle authentication state changes. Integrated with coroutines, Flow allows launching and consuming flows within a coroutine scope.
- For example, The MainInteractor observes authStream with the onEach and launchIn functions, reacting to state changes and updating the UI.
In conclusion, the demo offers an innovative way to build mobile apps using RIBs architecture, effectively creating a SIRs (Scope, Interactor, Router) Architecture.
================================================
FILE: demos/compose/build.gradle.kts
================================================
/*
* Copyright (C) 2025. Uber Technologies
*
* 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.
*/
plugins {
id("ribs.android.application")
alias(libs.plugins.kotlin.ksp)
alias(libs.plugins.compose.compiler)
}
android {
namespace = "com.uber.rib.compose"
defaultConfig {
applicationId = "com.uber.rib.compose"
}
buildFeatures {
compose = true
buildConfig = true
}
}
dependencies {
ksp(appLibs.motif.compiler)
implementation(project(":libraries:rib-android"))
implementation(project(":libraries:rib-android-compose"))
implementation(project(":libraries:rib-coroutines"))
implementation(appLibs.activity.compose)
implementation(libs.compose.foundation)
implementation(appLibs.compose.material)
implementation(libs.compose.runtime)
implementation(libs.compose.ui)
implementation(libs.compose.uitooling)
implementation(libs.savedstate)
implementation(libs.rxandroid2)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.rx2)
implementation(libs.autodispose.coroutines)
implementation(appLibs.motif.library)
// Flipper Debug tool integration
debugImplementation(libs.flipper)
debugImplementation(appLibs.soloader)
releaseImplementation(libs.flipper.noop)
// Flipper RIBs plugin
implementation(project(":tooling:rib-flipper-plugin"))
testImplementation(testLibs.junit)
}
================================================
FILE: demos/compose/src/main/AndroidManifest.xml
================================================
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/ComposeApplication.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose
import android.app.Application
import com.facebook.flipper.android.AndroidFlipperClient
import com.facebook.flipper.android.utils.FlipperUtils
import com.facebook.flipper.core.FlipperClient
import com.facebook.flipper.plugins.inspector.DescriptorMapping
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin
import com.facebook.soloader.SoLoader
import com.uber.rib.flipper.RibTreePlugin
class ComposeApplication : Application() {
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
val client: FlipperClient = AndroidFlipperClient.getInstance(this)
client.addPlugin(RibTreePlugin())
client.addPlugin(InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()))
client.start()
}
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/RootActivity.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root
import android.view.ViewGroup
import com.uber.rib.core.RibActivity
import com.uber.rib.core.ViewRouter
import motif.Creatable
import motif.Expose
import motif.NoDependencies
import motif.ScopeFactory
class RootActivity : RibActivity() {
override fun createRouter(parentViewGroup: ViewGroup): ViewRouter<*, *> {
return ScopeFactory.create(Parent::class.java)
.rootScope(this, findViewById(android.R.id.content))
.router()
}
@motif.Scope
interface Parent : Creatable {
fun rootScope(@Expose activity: RibActivity, parentViewGroup: ViewGroup): RootScope
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/RootInteractor.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root
import com.uber.rib.core.BasicInteractor
import com.uber.rib.core.EmptyPresenter
class RootInteractor(presenter: EmptyPresenter) :
BasicInteractor(presenter)
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/RootRouter.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root
import com.uber.rib.compose.root.main.MainRouter
import com.uber.rib.core.BasicViewRouter
class RootRouter(
view: RootView,
interactor: RootInteractor,
private val scope: RootScope,
) : BasicViewRouter(view, interactor) {
private var mainRouter: MainRouter? = null
override fun willAttach() {
attachMain()
}
override fun willDetach() {
detachMain()
}
private fun attachMain() {
if (mainRouter == null) {
mainRouter = scope.mainScope(view).router().also { attachChild(it) }
}
}
private fun detachMain() {
mainRouter?.let { detachChild(it) }
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/RootScope.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root
import android.view.ViewGroup
import com.uber.rib.compose.root.main.MainScope
import com.uber.rib.compose.util.AnalyticsClient
import com.uber.rib.compose.util.AnalyticsClientImpl
import com.uber.rib.compose.util.ExperimentClient
import com.uber.rib.compose.util.ExperimentClientImpl
import com.uber.rib.compose.util.LoggerClient
import com.uber.rib.compose.util.LoggerClientImpl
import com.uber.rib.core.EmptyPresenter
import com.uber.rib.core.RibActivity
import motif.Expose
@motif.Scope
interface RootScope {
fun router(): RootRouter
fun mainScope(parentViewGroup: ViewGroup): MainScope
@motif.Objects
abstract class Objects {
abstract fun router(): RootRouter
abstract fun interactor(): RootInteractor
abstract fun presenter(): EmptyPresenter
fun view(parentViewGroup: ViewGroup): RootView {
return RootView(parentViewGroup.context)
}
@Expose
fun analyticsClient(activity: RibActivity): AnalyticsClient {
return AnalyticsClientImpl(activity.application)
}
@Expose
fun experimentClient(activity: RibActivity): ExperimentClient {
return ExperimentClientImpl(activity.application)
}
@Expose
fun loggerClient(activity: RibActivity): LoggerClient {
return LoggerClientImpl(activity.application)
}
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/RootView.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.TextView
class RootView
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
) : FrameLayout(context, attrs, defStyle) {
init {
setBackgroundColor(Color.RED)
addView(
TextView(context).apply {
text = "root (view)"
setTextColor(Color.WHITE)
},
)
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/AuthStream.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class AuthStream {
private val _authFlow = MutableStateFlow(AuthInfo(false, "", ""))
private val authFlow = _authFlow.asStateFlow()
fun observe() = authFlow
fun accept(value: AuthInfo) {
_authFlow.update { value }
}
}
data class AuthInfo(
val isLoggedIn: Boolean,
val playerOne: String = "",
val playerTwo: String = "",
)
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/MainInteractor.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main
import com.uber.rib.core.BasicInteractor
import com.uber.rib.core.Bundle
import com.uber.rib.core.ComposePresenter
import com.uber.rib.core.coroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class MainInteractor(
presenter: ComposePresenter,
private val authStream: AuthStream,
private val childContent: MainRouter.ChildContent,
) : BasicInteractor(presenter) {
override fun didBecomeActive(savedInstanceState: Bundle?) {
super.didBecomeActive(savedInstanceState)
router.view.setContent { MainView(childContent = childContent) }
authStream
.observe()
.onEach {
if (it.isLoggedIn) {
router.detachLoggedOut()
router.attachLoggedIn(it)
} else {
router.detachLoggedIn()
router.attachLoggedOut()
}
}
.launchIn(coroutineScope)
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/MainRouter.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main
import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ComposeView
import com.uber.rib.compose.root.main.loggedin.LoggedInRouter
import com.uber.rib.compose.root.main.loggedout.LoggedOutRouter
import com.uber.rib.core.BasicViewRouter
class MainRouter(
view: ComposeView,
interactor: MainInteractor,
private val parentView: ViewGroup,
private val scope: MainScope,
private val childContent: ChildContent,
) : BasicViewRouter(view, interactor) {
private var loggedOutRouter: LoggedOutRouter? = null
private var loggedInRouter: LoggedInRouter? = null
override fun willAttach() {
super.willAttach()
parentView.addView(view)
}
override fun willDetach() {
parentView.removeView(view)
super.willDetach()
}
internal fun attachLoggedOut() {
if (loggedOutRouter == null) {
loggedOutRouter =
scope.loggedOutScope(childContent.fullScreenSlot).router().also { attachChild(it) }
}
}
internal fun attachLoggedIn(authInfo: AuthInfo) {
if (loggedInRouter == null) {
loggedInRouter =
scope.loggedInScope(childContent.fullScreenSlot, authInfo).router().also { attachChild(it) }
}
}
internal fun detachLoggedOut() {
loggedOutRouter?.let { detachChild(it) }
loggedOutRouter = null
}
internal fun detachLoggedIn() {
loggedInRouter?.let { detachChild(it) }
loggedInRouter = null
}
class ChildContent {
internal var fullScreenSlot: MutableState<@Composable () -> Unit> = mutableStateOf({})
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/MainScope.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main
import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.platform.ComposeView
import com.uber.rib.compose.root.main.loggedin.LoggedInScope
import com.uber.rib.compose.root.main.loggedout.LoggedOutScope
import com.uber.rib.core.ComposePresenter
import motif.Expose
@motif.Scope
interface MainScope {
fun router(): MainRouter
fun loggedOutScope(slot: MutableState<@Composable () -> Unit>): LoggedOutScope
fun loggedInScope(slot: MutableState<@Composable () -> Unit>, authInfo: AuthInfo): LoggedInScope
@motif.Objects
abstract class Objects {
abstract fun router(): MainRouter
abstract fun interactor(): MainInteractor
fun presenter(childContent: MainRouter.ChildContent): ComposePresenter {
return object : ComposePresenter() {
override val composable = @Composable { MainView(childContent) }
}
}
fun view(parentViewGroup: ViewGroup): ComposeView {
return ComposeView(parentViewGroup.context)
}
abstract fun childContent(): MainRouter.ChildContent
@Expose abstract fun authStream(): AuthStream
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/MainView.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun MainView(childContent: MainRouter.ChildContent) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier =
Modifier.fillMaxSize().padding(all = 4.dp).padding(top = 14.dp).background(Color(0xFFFFA500)),
) {
Text("Main RIB (Compose w/ CompView)")
Box(
modifier = Modifier.fillMaxWidth().weight(1.0f).padding(4.dp).background(Color.Yellow),
) {
childContent.fullScreenSlot.value.invoke()
}
}
}
@Preview
@Composable
fun MainViewPreview() {
MainView(MainRouter.ChildContent())
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/LoggedInEvent.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin
sealed class LoggedInEvent {
object LogOutClick : LoggedInEvent()
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/LoggedInInteractor.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin
import com.uber.rib.compose.root.main.AuthInfo
import com.uber.rib.compose.root.main.AuthStream
import com.uber.rib.compose.root.main.loggedin.offgame.OffGameInteractor
import com.uber.rib.compose.root.main.loggedin.tictactoe.TicTacToeInteractor
import com.uber.rib.compose.util.EventStream
import com.uber.rib.core.BasicInteractor
import com.uber.rib.core.Bundle
import com.uber.rib.core.ComposePresenter
import com.uber.rib.core.coroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class LoggedInInteractor(
presenter: ComposePresenter,
private val authInfo: AuthInfo,
private val authStream: AuthStream,
private val eventStream: EventStream,
private val scoreStream: ScoreStream,
) :
BasicInteractor(presenter),
OffGameInteractor.Listener,
TicTacToeInteractor.Listener {
override fun didBecomeActive(savedInstanceState: Bundle?) {
super.didBecomeActive(savedInstanceState)
eventStream
.observe()
.onEach {
when (it) {
is LoggedInEvent.LogOutClick -> authStream.accept(AuthInfo(false))
}
}
.launchIn(coroutineScope)
router.attachOffGame(authInfo)
}
override fun onStartGame() {
router.detachOffGame()
router.attachTicTacToe(authInfo)
}
override fun onGameWon(winner: String?) {
if (winner != null) {
coroutineScope.launch { scoreStream.addVictory(winner) }
}
router.detachTicTacToe()
router.attachOffGame(authInfo)
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/LoggedInRouter.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import com.uber.rib.compose.root.main.AuthInfo
import com.uber.rib.compose.root.main.loggedin.offgame.OffGameRouter
import com.uber.rib.compose.root.main.loggedin.tictactoe.TicTacToeRouter
import com.uber.rib.core.BasicComposeRouter
import com.uber.rib.core.ComposePresenter
class LoggedInRouter(
presenter: ComposePresenter,
interactor: LoggedInInteractor,
slot: MutableState<@Composable () -> Unit>,
private val scope: LoggedInScope,
private val childContent: ChildContent,
) : BasicComposeRouter(presenter, interactor, slot) {
private var offGameRouter: OffGameRouter? = null
private var ticTacToeRouter: TicTacToeRouter? = null
internal fun attachOffGame(authInfo: AuthInfo) {
if (offGameRouter == null) {
offGameRouter =
scope.offGameScope(childContent.fullScreenSlot, authInfo).router().also { attachChild(it) }
}
}
internal fun attachTicTacToe(authInfo: AuthInfo) {
if (ticTacToeRouter == null) {
ticTacToeRouter =
scope.ticTacToeScope(childContent.fullScreenSlot, authInfo).router().also {
attachChild(it)
}
}
}
internal fun detachOffGame() {
offGameRouter?.let { detachChild(it) }
offGameRouter = null
}
internal fun detachTicTacToe() {
ticTacToeRouter?.let { detachChild(it) }
ticTacToeRouter = null
}
class ChildContent {
internal var fullScreenSlot: MutableState<@Composable () -> Unit> = mutableStateOf({})
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/LoggedInScope.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import com.uber.rib.compose.root.main.AuthInfo
import com.uber.rib.compose.root.main.loggedin.offgame.OffGameInteractor
import com.uber.rib.compose.root.main.loggedin.offgame.OffGameScope
import com.uber.rib.compose.root.main.loggedin.tictactoe.TicTacToeInteractor
import com.uber.rib.compose.root.main.loggedin.tictactoe.TicTacToeScope
import com.uber.rib.compose.util.EventStream
import com.uber.rib.core.ComposePresenter
import motif.Expose
@motif.Scope
interface LoggedInScope {
fun router(): LoggedInRouter
fun offGameScope(slot: MutableState<@Composable () -> Unit>, authInfo: AuthInfo): OffGameScope
fun ticTacToeScope(
slot: MutableState<@Composable () -> Unit>,
authInfo: AuthInfo,
): TicTacToeScope
@motif.Objects
abstract class Objects {
abstract fun router(): LoggedInRouter
abstract fun interactor(): LoggedInInteractor
abstract fun childContent(): LoggedInRouter.ChildContent
fun presenter(
eventStream: EventStream,
childContent: LoggedInRouter.ChildContent,
): ComposePresenter {
return object : ComposePresenter() {
override val composable = @Composable { LoggedInView(eventStream, childContent) }
}
}
fun eventStream() = EventStream()
@Expose
fun scoreSteam(authInfo: AuthInfo): ScoreStream {
return ScoreStream(authInfo.playerOne, authInfo.playerTwo)
}
@Expose
abstract fun startGameListener(interactor: LoggedInInteractor): OffGameInteractor.Listener
@Expose
abstract fun gameWonListener(interactor: LoggedInInteractor): TicTacToeInteractor.Listener
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/LoggedInView.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.uber.rib.compose.util.CustomButton
import com.uber.rib.compose.util.EventStream
@Composable
fun LoggedInView(
eventStream: EventStream,
childContent: LoggedInRouter.ChildContent,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier = Modifier.fillMaxSize().background(Color.Green),
) {
Text("Logged In! (Compose RIB)")
Spacer(Modifier.height(16.dp))
Box(
modifier = Modifier.fillMaxWidth().weight(1.0f).padding(4.dp).background(Color.LightGray),
) {
childContent.fullScreenSlot.value.invoke()
}
CustomButton(
analyticsId = "8a570808-07a4",
onClick = { eventStream.notify(LoggedInEvent.LogOutClick) },
modifier = Modifier.fillMaxWidth().padding(16.dp),
) {
Text(text = "LOGOUT")
}
}
}
@Preview
@Composable
fun LoggedInViewPreview() {
LoggedInView(EventStream(), LoggedInRouter.ChildContent())
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/ScoreStream.kt
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin
import com.uber.rib.core.RibDispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
class ScoreStream(playerOne: String, playerTwo: String) {
private val _scoresFlow =
MutableStateFlow(
mapOf(
playerOne to 0,
playerTwo to 0,
),
)
private val scoresFlow = _scoresFlow.asStateFlow()
suspend fun addVictory(userName: String) =
withContext(RibDispatchers.Default) {
_scoresFlow.update { scores ->
scores
.toMutableMap()
.apply {
if (userName in scores) {
set(userName, getValue(userName) + 1)
}
}
.toMap()
}
}
fun scores() = scoresFlow
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/offgame/OffGameEvent.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.offgame
sealed class OffGameEvent {
object StartGame : OffGameEvent()
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/offgame/OffGameInteractor.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.offgame
import com.uber.rib.compose.root.main.loggedin.ScoreStream
import com.uber.rib.compose.util.EventStream
import com.uber.rib.compose.util.StateStream
import com.uber.rib.core.BasicInteractor
import com.uber.rib.core.Bundle
import com.uber.rib.core.ComposePresenter
import com.uber.rib.core.coroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class OffGameInteractor(
presenter: ComposePresenter,
private val eventStream: EventStream,
private val stateStream: StateStream,
private val scoreStream: ScoreStream,
private val listener: Listener,
) : BasicInteractor(presenter) {
override fun didBecomeActive(savedInstanceState: Bundle?) {
super.didBecomeActive(savedInstanceState)
eventStream
.observe()
.onEach {
when (it) {
is OffGameEvent.StartGame -> {
listener.onStartGame()
}
}
}
.launchIn(coroutineScope)
scoreStream
.scores()
.onEach {
val currentState = stateStream.current()
stateStream.dispatch(
currentState.copy(
playerOneWins = it[currentState.playerOne] ?: 0,
playerTwoWins = it[currentState.playerTwo] ?: 0,
),
)
}
.launchIn(coroutineScope)
}
interface Listener {
fun onStartGame()
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/offgame/OffGameRouter.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.offgame
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import com.uber.rib.core.BasicComposeRouter
import com.uber.rib.core.ComposePresenter
class OffGameRouter(
presenter: ComposePresenter,
interactor: OffGameInteractor,
slot: MutableState<@Composable () -> Unit>,
) : BasicComposeRouter(presenter, interactor, slot)
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/offgame/OffGameScope.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.offgame
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import com.uber.rib.compose.root.main.AuthInfo
import com.uber.rib.compose.util.EventStream
import com.uber.rib.compose.util.StateStream
import com.uber.rib.core.ComposePresenter
@motif.Scope
interface OffGameScope {
fun router(): OffGameRouter
@motif.Objects
abstract class Objects {
abstract fun router(): OffGameRouter
abstract fun interactor(): OffGameInteractor
fun presenter(
stateStream: StateStream,
eventStream: EventStream,
): ComposePresenter {
return object : ComposePresenter() {
override val composable =
@Composable {
OffGameView(
stateStream.observe().collectAsState(initial = stateStream.current()),
eventStream,
)
}
}
}
fun eventStream() = EventStream()
fun stateStream(authInfo: AuthInfo) =
StateStream(
OffGameViewModel(
playerOne = authInfo.playerOne,
playerTwo = authInfo.playerTwo,
),
)
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/offgame/OffGameView.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.offgame
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.uber.rib.compose.util.CustomButton
import com.uber.rib.compose.util.EventStream
@Composable
fun OffGameView(viewModel: State, eventStream: EventStream) {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp, alignment = Alignment.Bottom),
) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(text = viewModel.value.playerOne)
Text(text = "Win Count: ${viewModel.value.playerOneWins}")
}
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(text = viewModel.value.playerTwo)
Text(text = "Win Count: ${viewModel.value.playerTwoWins}")
}
CustomButton(
analyticsId = "26882559-fc45",
onClick = { eventStream.notify(OffGameEvent.StartGame) },
modifier = Modifier.fillMaxWidth(),
) {
Text(text = "START GAME")
}
}
}
@Preview
@Composable
fun OffGameViewPreview() {
val viewModel = remember { mutableStateOf(OffGameViewModel("James", "Alejandro", 3, 0)) }
OffGameView(viewModel, EventStream())
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/offgame/OffGameViewModel.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.offgame
data class OffGameViewModel(
val playerOne: String = "",
val playerTwo: String = "",
val playerOneWins: Int = 0,
val playerTwoWins: Int = 0,
)
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/tictactoe/Board.kt
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.tictactoe
import javax.inject.Inject
class Board @Inject constructor() {
var cells: Array>
var currentRow = 0
var currentCol = 0
/** Return true if it is a draw (i.e., no more EMPTY cell) */
fun isDraw(): Boolean {
for (row in 0 until ROWS) {
for (col in 0 until COLS) {
if (cells[row][col] == null) {
return false
}
}
}
return !hasWon(MarkerType.CROSS) && !hasWon(MarkerType.NOUGHT)
}
/** Return true if the player with "theSeed" has won after placing at (currentRow, currentCol) */
fun hasWon(theSeed: MarkerType): Boolean {
return (cells[currentRow][0] == theSeed &&
cells[currentRow][1] == theSeed &&
cells[currentRow][2] == theSeed ||
cells[0][currentCol] == theSeed &&
cells[1][currentCol] == theSeed &&
cells[2][currentCol] == theSeed ||
currentRow == currentCol &&
cells[0][0] == theSeed &&
cells[1][1] == theSeed &&
cells[2][2] == theSeed ||
currentRow + currentCol == 2 &&
cells[0][2] == theSeed &&
cells[1][1] == theSeed &&
cells[2][0] == theSeed)
}
enum class MarkerType {
CROSS,
NOUGHT,
}
companion object {
const val ROWS = 3
const val COLS = 3
}
init {
cells = Array(ROWS) { arrayOfNulls(COLS) }
for (row in 0 until ROWS) {
for (col in 0 until COLS) {
cells[row][col] = null
}
}
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/tictactoe/BoardCoordinate.kt
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.tictactoe
data class BoardCoordinate(val x: Int, val y: Int)
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/tictactoe/TicTacToeEvent.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.tictactoe
sealed class TicTacToeEvent {
object XpButtonClick : TicTacToeEvent()
class BoardClick(val coordinate: BoardCoordinate) : TicTacToeEvent()
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/tictactoe/TicTacToeInteractor.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.tictactoe
import com.uber.rib.compose.root.main.AuthInfo
import com.uber.rib.compose.util.EventStream
import com.uber.rib.compose.util.StateStream
import com.uber.rib.core.BasicInteractor
import com.uber.rib.core.Bundle
import com.uber.rib.core.ComposePresenter
import com.uber.rib.core.coroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class TicTacToeInteractor(
presenter: ComposePresenter,
private val authInfo: AuthInfo,
private val eventStream: EventStream,
private val stateStream: StateStream,
private val listener: Listener,
) : BasicInteractor(presenter) {
var currentPlayer: Board.MarkerType = Board.MarkerType.CROSS
override fun didBecomeActive(savedInstanceState: Bundle?) {
super.didBecomeActive(savedInstanceState)
eventStream
.observe()
.onEach {
when (it) {
is TicTacToeEvent.BoardClick -> {
val board: Board = stateStream.current().board
val coord = it.coordinate
if (board.cells[coord.x][coord.y] == null) {
if (currentPlayer == Board.MarkerType.CROSS) {
board.cells[coord.x][coord.y] = Board.MarkerType.CROSS
board.currentRow = coord.x
board.currentCol = coord.y
currentPlayer = Board.MarkerType.NOUGHT
} else {
board.cells[coord.x][coord.y] = Board.MarkerType.NOUGHT
board.currentRow = coord.x
board.currentCol = coord.y
currentPlayer = Board.MarkerType.CROSS
}
}
if (board.hasWon(Board.MarkerType.CROSS)) {
listener.onGameWon(authInfo.playerOne)
} else if (board.hasWon(Board.MarkerType.NOUGHT)) {
listener.onGameWon(authInfo.playerTwo)
} else if (board.isDraw()) {
listener.onGameWon(null)
}
val newPlayerName =
if (currentPlayer == Board.MarkerType.CROSS) {
authInfo.playerOne
} else {
authInfo.playerTwo
}
stateStream.dispatch(
stateStream
.current()
.copy(
board = board,
currentPlayer = newPlayerName,
),
)
}
TicTacToeEvent.XpButtonClick -> TODO("Go somewhere")
}
}
.launchIn(coroutineScope)
}
interface Listener {
fun onGameWon(winnerName: String?)
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/tictactoe/TicTacToeRouter.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.tictactoe
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import com.uber.rib.core.BasicComposeRouter
import com.uber.rib.core.ComposePresenter
class TicTacToeRouter(
presenter: ComposePresenter,
interactor: TicTacToeInteractor,
slot: MutableState<@Composable () -> Unit>,
) : BasicComposeRouter(presenter, interactor, slot)
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/tictactoe/TicTacToeScope.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.tictactoe
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import com.uber.rib.compose.root.main.AuthInfo
import com.uber.rib.compose.util.EventStream
import com.uber.rib.compose.util.StateStream
import com.uber.rib.core.ComposePresenter
@motif.Scope
interface TicTacToeScope {
fun router(): TicTacToeRouter
@motif.Objects
abstract class Objects {
abstract fun router(): TicTacToeRouter
abstract fun interactor(): TicTacToeInteractor
fun presenter(
stateStream: StateStream,
eventStream: EventStream,
): ComposePresenter {
return object : ComposePresenter() {
override val composable =
@Composable {
TicTacToeView(
stateStream.observe().collectAsState(initial = stateStream.current()),
eventStream,
)
}
}
}
fun eventStream() = EventStream()
fun stateStream(authInfo: AuthInfo) =
StateStream(TicTacToeViewModel(authInfo.playerOne, Board()))
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/tictactoe/TicTacToeView.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.tictactoe
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.uber.rib.compose.util.EventStream
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun TicTacToeView(viewModel: State, eventStream: EventStream) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier = Modifier.fillMaxSize().background(Color.Blue),
) {
Text("Current Player: ${viewModel.value.currentPlayer}", color = Color.White)
Box(
modifier = Modifier.aspectRatio(1f).fillMaxSize(),
) {
LazyVerticalGrid(columns = GridCells.Fixed(3), modifier = Modifier.fillMaxSize()) {
val board = viewModel.value.board
items(9) { i ->
val row = i / 3
val col = i % 3
Text(
text =
when (board.cells[row][col]) {
Board.MarkerType.CROSS -> "X"
Board.MarkerType.NOUGHT -> "O"
else -> " "
},
textAlign = TextAlign.Center,
modifier =
Modifier.fillMaxWidth()
.aspectRatio(1f)
.padding(16.dp)
.background(Color.LightGray)
.clickable(
enabled = board.cells[row][col] == null,
onClick = {
eventStream.notify(TicTacToeEvent.BoardClick(BoardCoordinate(row, col)))
},
)
.padding(32.dp),
)
}
}
}
}
}
@Preview
@Composable
fun ProductSelectionViewPreview() {
val board = Board()
board.cells[0][2] = Board.MarkerType.CROSS
board.cells[1][0] = Board.MarkerType.NOUGHT
board.cells[2][1] = Board.MarkerType.CROSS
val viewModel = remember { mutableStateOf(TicTacToeViewModel("James", board)) }
TicTacToeView(viewModel, EventStream())
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedin/tictactoe/TicTacToeViewModel.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedin.tictactoe
data class TicTacToeViewModel(
val currentPlayer: String,
val board: Board,
)
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedout/LoggedOutEvent.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedout
sealed class LoggedOutEvent {
class PlayerNameChanged(val name: String, val num: Int) : LoggedOutEvent()
object LogInClick : LoggedOutEvent()
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedout/LoggedOutInteractor.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedout
import com.uber.rib.compose.root.main.AuthInfo
import com.uber.rib.compose.root.main.AuthStream
import com.uber.rib.compose.util.EventStream
import com.uber.rib.compose.util.StateStream
import com.uber.rib.core.BasicInteractor
import com.uber.rib.core.Bundle
import com.uber.rib.core.ComposePresenter
import com.uber.rib.core.coroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class LoggedOutInteractor(
presenter: ComposePresenter,
private val authStream: AuthStream,
private val eventStream: EventStream,
private val stateStream: StateStream,
) : BasicInteractor(presenter) {
override fun didBecomeActive(savedInstanceState: Bundle?) {
super.didBecomeActive(savedInstanceState)
eventStream
.observe()
.onEach {
when (it) {
is LoggedOutEvent.PlayerNameChanged -> {
with(stateStream) {
dispatch(
current()
.copy(
playerOne = if (it.num == 1) it.name else current().playerOne,
playerTwo = if (it.num == 2) it.name else current().playerTwo,
),
)
}
}
LoggedOutEvent.LogInClick -> {
val currentState = stateStream.current()
authStream.accept(AuthInfo(true, currentState.playerOne, currentState.playerTwo))
}
}
}
.launchIn(coroutineScope)
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedout/LoggedOutRouter.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedout
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import com.uber.rib.core.BasicComposeRouter
import com.uber.rib.core.ComposePresenter
class LoggedOutRouter(
presenter: ComposePresenter,
interactor: LoggedOutInteractor,
slot: MutableState<@Composable () -> Unit>,
) : BasicComposeRouter(presenter, interactor, slot)
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedout/LoggedOutScope.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedout
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import com.uber.rib.compose.util.EventStream
import com.uber.rib.compose.util.StateStream
import com.uber.rib.core.ComposePresenter
@motif.Scope
interface LoggedOutScope {
fun router(): LoggedOutRouter
@motif.Objects
abstract class Objects {
abstract fun router(): LoggedOutRouter
abstract fun interactor(): LoggedOutInteractor
fun presenter(
stateStream: StateStream,
eventStream: EventStream,
): ComposePresenter {
return object : ComposePresenter() {
override val composable =
@Composable {
LoggedOutView(
stateStream.observe().collectAsState(initial = stateStream.current()),
eventStream,
)
}
}
}
fun eventStream() = EventStream()
fun stateStream() = StateStream(LoggedOutViewModel())
}
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedout/LoggedOutView.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedout
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.uber.rib.compose.util.EventStream
@Composable
fun LoggedOutView(viewModel: State, eventStream: EventStream) {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp, alignment = Alignment.Bottom),
) {
TextField(
value = viewModel.value.playerOne,
onValueChange = { eventStream.notify(LoggedOutEvent.PlayerNameChanged(it, 1)) },
placeholder = { Text(text = "Player One Name") },
modifier = Modifier.fillMaxWidth(),
)
TextField(
value = viewModel.value.playerTwo,
onValueChange = { eventStream.notify(LoggedOutEvent.PlayerNameChanged(it, 2)) },
placeholder = { Text(text = "Player Two Name") },
modifier = Modifier.fillMaxWidth(),
)
Button(
colors =
ButtonDefaults.buttonColors(
backgroundColor = Color.Black,
contentColor = Color.White,
),
onClick = { eventStream.notify(LoggedOutEvent.LogInClick) },
modifier = Modifier.fillMaxWidth(),
) {
Text(text = "LOGIN")
}
}
}
@Preview
@Composable
fun LoggedOutViewPreview() {
val viewModel = remember { mutableStateOf(LoggedOutViewModel("James", "Alejandro")) }
LoggedOutView(viewModel, EventStream())
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/root/main/loggedout/LoggedOutViewModel.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.root.main.loggedout
data class LoggedOutViewModel(
val playerOne: String = "",
val playerTwo: String = "",
)
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/util/AnalyticsClient.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.util
import android.app.Application
import android.util.Log
import android.widget.Toast
class AnalyticsClientImpl(private val application: Application) : AnalyticsClient {
override fun trackClick(id: String) {
track(id, EventType.CLICK)
}
override fun trackImpression(id: String) {
track(id, EventType.IMPRESSION)
}
private fun track(id: String, type: EventType) {
val message = "$type for $id @ ${System.currentTimeMillis()}"
Toast.makeText(application.applicationContext, message, Toast.LENGTH_SHORT).show()
Log.d(this::class.java.simpleName, message)
}
enum class EventType {
CLICK,
IMPRESSION,
}
}
object NoOpAnalyticsClient : AnalyticsClient {
override fun trackClick(id: String) = Unit
override fun trackImpression(id: String) = Unit
}
interface AnalyticsClient {
fun trackClick(id: String)
fun trackImpression(id: String)
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/util/CustomButton.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.util
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@Composable
fun CustomButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
analyticsId: String? = null,
content: @Composable RowScope.() -> Unit,
) {
val analyticsClient = AnalyticsLocal.current
val onClickWrapper: () -> Unit = {
analyticsId?.let { analyticsClient.trackClick(it) }
onClick()
}
Button(
onClick = onClickWrapper,
modifier = modifier,
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Black, contentColor = Color.White),
content = content,
)
LaunchedEffect(null) { analyticsId?.let { analyticsClient.trackImpression(it) } }
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/util/CustomClientProvider.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.util
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
val AnalyticsLocal = staticCompositionLocalOf { NoOpAnalyticsClient }
val ExperimentsLocal = staticCompositionLocalOf { NoOpExperimentClient }
val LoggerLocal = staticCompositionLocalOf { NoOpLoggerClient }
@Composable
fun CustomClientProvider(
analyticsClient: AnalyticsClient,
experimentClient: ExperimentClient,
loggerClient: LoggerClient,
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
AnalyticsLocal provides analyticsClient,
ExperimentsLocal provides experimentClient,
LoggerLocal provides loggerClient,
content = content,
)
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/util/EventStream.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.util
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
class EventStream {
private val _sharedFlow = MutableSharedFlow(extraBufferCapacity = 1)
private val sharedFlow = _sharedFlow.asSharedFlow()
fun notify(event: T) = _sharedFlow.tryEmit(event)
fun observe() = sharedFlow
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/util/ExperimentClient.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.util
import android.app.Application
import android.util.Log
import android.widget.Toast
import java.lang.Math.random
class ExperimentClientImpl(private val application: Application) : ExperimentClient {
override fun isTreated(id: String): Boolean {
val result = random() > 0.5
val message = "isTreated($id) = $result"
Toast.makeText(application.applicationContext, message, Toast.LENGTH_SHORT).show()
Log.d(this::class.java.simpleName, message)
return result
}
}
object NoOpExperimentClient : ExperimentClient {
override fun isTreated(id: String) = false
}
interface ExperimentClient {
fun isTreated(id: String): Boolean
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/util/LoggerClient.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.util
import android.app.Application
import android.util.Log
import android.widget.Toast
class LoggerClientImpl(private val application: Application) : LoggerClient {
override fun log(message: String) {
Toast.makeText(application.applicationContext, message, Toast.LENGTH_SHORT).show()
Log.d(this::class.java.simpleName, message)
}
}
object NoOpLoggerClient : LoggerClient {
override fun log(message: String) = Unit
}
interface LoggerClient {
fun log(message: String)
}
================================================
FILE: demos/compose/src/main/kotlin/com/uber/rib/compose/util/StateStream.kt
================================================
/*
* Copyright (C) 2021. Uber Technologies
*
* 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.
*/
package com.uber.rib.compose.util
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class StateStream(default: T) {
private val _stateFlow = MutableStateFlow(default)
private val stateFlow = _stateFlow.asStateFlow()
fun dispatch(viewModel: T) = _stateFlow.update { viewModel }
fun observe() = stateFlow
fun current() = stateFlow.value
}
================================================
FILE: demos/compose/src/main/res/drawable/ic_launcher_background.xml
================================================
================================================
FILE: demos/compose/src/main/res/drawable-v24/ic_launcher_foreground.xml
================================================
================================================
FILE: demos/compose/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
================================================
FILE: demos/compose/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
================================================
FILE: demos/compose/src/main/res/values/ids.xml
================================================
================================================
FILE: demos/compose/src/main/res/values/strings.xml
================================================
Compose RIBs
================================================
FILE: demos/compose/src/main/res/values/themes.xml
================================================
================================================
FILE: demos/flipper/README.md
================================================
# RIB Demo: Flipper Debugging Tool
## Goal
This demo shows off RIBs plugin for Flipper Debugging Tool. It re-uses one of our tutorial application, and integrates
it with Flipper tool and our Flipper RIBs plugin.
## Integration
To integrate Flipper to your app, please refer to [Flipper official documentation](https://fbflipper.com/docs/getting-started/android-native)
Then, to enable RIBs plugin, simply add it to the list of used plugin during Flipper initialization.
In summary, your application ```onCreate()``` method should look similar to the one below:
```
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
final FlipperClient client = AndroidFlipperClient.getInstance(this);
client.addPlugin(new RibTreePlugin());
client.start();
}
}
}
```
================================================
FILE: demos/flipper/build.gradle.kts
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
plugins {
id("ribs.android.application")
alias(libs.plugins.kotlin.kapt)
alias(libs.plugins.kotlin.ksp)
}
android {
namespace = "com.uber.rib.flipper"
defaultConfig {
applicationId = "com.uber.rib.flipper"
}
buildFeatures {
buildConfig = true
}
}
dependencies {
ksp(libs.dagger.compiler)
kapt(project(":libraries:rib-compiler-test"))
implementation(project(":libraries:rib-android"))
implementation(libs.androidx.appcompat)
implementation(libs.dagger.library)
implementation(libs.rxbinding)
implementation(appLibs.percent)
compileOnly(appLibs.jsr250)
testImplementation(project(":libraries:rib-test"))
// Flipper Debug tool integration
debugImplementation(libs.flipper)
debugImplementation(appLibs.soloader)
releaseImplementation(libs.flipper.noop)
// Flipper RIBs plugin
implementation(project(":tooling:rib-flipper-plugin"))
}
================================================
FILE: demos/flipper/src/main/AndroidManifest.xml
================================================
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/RootActivity.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib;
import android.view.ViewGroup;
import com.uber.rib.core.RibActivity;
import com.uber.rib.core.ViewRouter;
import com.uber.rib.root.RootBuilder;
/** The sample app's single activity. */
public class RootActivity extends RibActivity {
@SuppressWarnings("unchecked")
@Override
protected ViewRouter, ?> createRouter(ViewGroup parentViewGroup) {
RootBuilder rootBuilder = new RootBuilder(new RootBuilder.ParentComponent() {});
return rootBuilder.build(parentViewGroup);
}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/SampleApplication.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib;
import android.app.Application;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.soloader.SoLoader;
import com.uber.rib.flipper.BuildConfig;
import com.uber.rib.flipper.RibTreePlugin;
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
final FlipperClient client = AndroidFlipperClient.getInstance(this);
client.addPlugin(new RibTreePlugin());
client.start();
}
}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/root/RootBuilder.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.root;
import static java.lang.annotation.RetentionPolicy.CLASS;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.uber.rib.core.InteractorBaseComponent;
import com.uber.rib.core.ViewBuilder;
import com.uber.rib.flipper.R;
import com.uber.rib.root.loggedin.LoggedInBuilder;
import com.uber.rib.root.loggedout.LoggedOutBuilder;
import com.uber.rib.root.loggedout.LoggedOutInteractor;
import dagger.Binds;
import dagger.BindsInstance;
import dagger.Provides;
import java.lang.annotation.Retention;
import javax.inject.Scope;
/** Builder for the {@link RootScope}. */
public class RootBuilder extends ViewBuilder {
public RootBuilder(ParentComponent dependency) {
super(dependency);
}
/**
* Builds a new {@link RootRouter}.
*
* @param parentViewGroup parent view group that this router's view will be added to.
* @return a new {@link RootRouter}.
*/
public RootRouter build(ViewGroup parentViewGroup) {
RootView view = createView(parentViewGroup);
RootInteractor interactor = new RootInteractor();
Component component =
DaggerRootBuilder_Component.builder()
.parentComponent(getDependency())
.view(view)
.interactor(interactor)
.build();
return component.rootRouter();
}
@Override
protected RootView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) {
return (RootView) inflater.inflate(R.layout.root_rib, parentViewGroup, false);
}
public interface ParentComponent {
// Define dependencies required from your parent interactor here.
}
@dagger.Module
public abstract static class Module {
@RootScope
@Provides
static LoggedOutInteractor.Listener loggedOutListener(RootInteractor rootInteractor) {
return rootInteractor.new LoggedOutListener();
}
@RootScope
@Binds
abstract RootInteractor.RootPresenter presenter(RootView view);
@RootScope
@Provides
static RootRouter router(Component component, RootView view, RootInteractor interactor) {
return new RootRouter(
view,
interactor,
component,
new LoggedOutBuilder(component),
new LoggedInBuilder(component));
}
}
@RootScope
@dagger.Component(modules = Module.class, dependencies = ParentComponent.class)
interface Component
extends InteractorBaseComponent,
LoggedOutBuilder.ParentComponent,
LoggedInBuilder.ParentComponent,
BuilderComponent {
@dagger.Component.Builder
interface Builder {
@BindsInstance
Builder interactor(RootInteractor interactor);
@BindsInstance
Builder view(RootView view);
Builder parentComponent(ParentComponent component);
Component build();
}
}
interface BuilderComponent {
RootRouter rootRouter();
}
@Scope
@Retention(CLASS)
@interface RootScope {}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/root/RootInteractor.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.root;
import androidx.annotation.Nullable;
import com.uber.rib.core.Bundle;
import com.uber.rib.core.Interactor;
import com.uber.rib.core.RibInteractor;
import com.uber.rib.root.loggedout.LoggedOutInteractor;
import javax.inject.Inject;
/** Coordinates Business Logic for {@link RootBuilder.RootScope}. */
@RibInteractor
public class RootInteractor extends Interactor {
@Inject RootPresenter presenter;
@Override
protected void didBecomeActive(@Nullable Bundle savedInstanceState) {
super.didBecomeActive(savedInstanceState);
getRouter().attachLoggedOut();
}
class LoggedOutListener implements LoggedOutInteractor.Listener {
@Override
public void login(String userNameA, String userNameB) {
// Switch to logged in. Let’s just ignore userName for now.
getRouter().detachLoggedOut();
getRouter().attachLoggedIn();
}
}
/** Presenter interface implemented by this RIB's view. */
interface RootPresenter {}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/root/RootRouter.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.root;
import androidx.annotation.Nullable;
import com.uber.rib.core.ViewRouter;
import com.uber.rib.root.loggedin.LoggedInBuilder;
import com.uber.rib.root.loggedout.LoggedOutBuilder;
import com.uber.rib.root.loggedout.LoggedOutRouter;
/** Adds and removes children of {@link RootBuilder.RootScope}. */
public class RootRouter extends ViewRouter {
private final LoggedOutBuilder loggedOutBuilder;
private final LoggedInBuilder loggedInBuilder;
@Nullable private LoggedOutRouter loggedOutRouter;
RootRouter(
RootView view,
RootInteractor interactor,
RootBuilder.Component component,
LoggedOutBuilder loggedOutBuilder,
LoggedInBuilder loggedInBuilder) {
super(view, interactor, component);
this.loggedOutBuilder = loggedOutBuilder;
this.loggedInBuilder = loggedInBuilder;
}
void attachLoggedOut() {
loggedOutRouter = loggedOutBuilder.build(getView());
attachChild(loggedOutRouter);
getView().addView(loggedOutRouter.getView());
}
void detachLoggedOut() {
if (loggedOutRouter != null) {
detachChild(loggedOutRouter);
getView().removeView(loggedOutRouter.getView());
loggedOutRouter = null;
}
}
void attachLoggedIn() {
attachChild(loggedInBuilder.build());
}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/root/RootView.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.root;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
/** Top level view for {@link RootBuilder.RootScope}. */
public class RootView extends FrameLayout implements RootInteractor.RootPresenter {
public RootView(Context context) {
this(context, null);
}
public RootView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RootView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/LoggedInBuilder.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.root.loggedin;
import static java.lang.annotation.RetentionPolicy.CLASS;
import com.uber.rib.core.Builder;
import com.uber.rib.core.EmptyPresenter;
import com.uber.rib.core.InteractorBaseComponent;
import com.uber.rib.root.RootView;
import com.uber.rib.root.loggedin.offgame.OffGameBuilder;
import com.uber.rib.root.loggedin.offgame.OffGameInteractor;
import com.uber.rib.root.loggedin.tictactoe.TicTacToeBuilder;
import dagger.BindsInstance;
import dagger.Provides;
import java.lang.annotation.Retention;
import javax.inject.Qualifier;
import javax.inject.Scope;
public class LoggedInBuilder extends Builder {
public LoggedInBuilder(ParentComponent dependency) {
super(dependency);
}
/**
* Builds a new {@link LoggedInRouter}.
*
* @return a new {@link LoggedInRouter}.
*/
public LoggedInRouter build() {
LoggedInInteractor interactor = new LoggedInInteractor();
Component component =
DaggerLoggedInBuilder_Component.builder()
.parentComponent(getDependency())
.interactor(interactor)
.build();
return component.loggedinRouter();
}
public interface ParentComponent {
RootView rootView();
}
@dagger.Module
public abstract static class Module {
@LoggedInScope
@Provides
static EmptyPresenter presenter() {
return new EmptyPresenter();
}
@LoggedInScope
@Provides
static LoggedInRouter router(
Component component, LoggedInInteractor interactor, RootView rootView) {
return new LoggedInRouter(
interactor,
component,
rootView,
new OffGameBuilder(component),
new TicTacToeBuilder(component));
}
@LoggedInScope
@Provides
static OffGameInteractor.Listener listener(LoggedInInteractor interactor) {
return interactor.new OffGameListener();
}
}
@LoggedInScope
@dagger.Component(modules = Module.class, dependencies = ParentComponent.class)
public interface Component
extends InteractorBaseComponent,
BuilderComponent,
OffGameBuilder.ParentComponent,
TicTacToeBuilder.ParentComponent {
@dagger.Component.Builder
interface Builder {
@BindsInstance
Builder interactor(LoggedInInteractor interactor);
Builder parentComponent(ParentComponent component);
Component build();
}
}
interface BuilderComponent {
LoggedInRouter loggedinRouter();
}
@Scope
@Retention(CLASS)
@interface LoggedInScope {}
@Qualifier
@Retention(CLASS)
@interface LoggedInInternal {}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/LoggedInInteractor.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.root.loggedin;
import androidx.annotation.Nullable;
import com.uber.rib.core.Bundle;
import com.uber.rib.core.EmptyPresenter;
import com.uber.rib.core.Interactor;
import com.uber.rib.core.RibInteractor;
import com.uber.rib.root.loggedin.offgame.OffGameInteractor;
/** Coordinates Business Logic for {@link LoggedInScope}. */
@RibInteractor
public class LoggedInInteractor extends Interactor {
@Override
protected void didBecomeActive(@Nullable Bundle savedInstanceState) {
super.didBecomeActive(savedInstanceState);
// when first logging in we should be in the OffGame state
getRouter().attachOffGame();
}
class OffGameListener implements OffGameInteractor.Listener {
@Override
public void onStartGame() {
getRouter().detachOffGame();
getRouter().attachTicTacToe();
}
}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/LoggedInRouter.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.root.loggedin;
import android.view.ViewGroup;
import com.uber.rib.core.Router;
import com.uber.rib.root.loggedin.offgame.OffGameBuilder;
import com.uber.rib.root.loggedin.offgame.OffGameRouter;
import com.uber.rib.root.loggedin.tictactoe.TicTacToeBuilder;
import com.uber.rib.root.loggedin.tictactoe.TicTacToeRouter;
/** Adds and removes children of {@link LoggedInBuilder.LoggedInScope}. */
public class LoggedInRouter extends Router {
private final ViewGroup parentView;
private final OffGameBuilder offGameBuilder;
private final TicTacToeBuilder ticTacToeBuilder;
private OffGameRouter offGameRouter;
private TicTacToeRouter ticTacToeRouter;
LoggedInRouter(
LoggedInInteractor interactor,
LoggedInBuilder.Component component,
ViewGroup parentView,
OffGameBuilder offGameBuilder,
TicTacToeBuilder ticTacToeBuilder) {
super(interactor, component);
this.parentView = parentView;
this.offGameBuilder = offGameBuilder;
this.ticTacToeBuilder = ticTacToeBuilder;
}
@Override
protected void willDetach() {
super.willDetach();
detachOffGame();
detachTicTacToe();
}
void attachOffGame() {
offGameRouter = offGameBuilder.build(parentView);
attachChild(offGameRouter);
parentView.addView(offGameRouter.getView());
}
void detachOffGame() {
if (offGameRouter != null) {
detachChild(offGameRouter);
parentView.removeView(offGameRouter.getView());
offGameRouter = null;
}
}
void attachTicTacToe() {
ticTacToeRouter = ticTacToeBuilder.build(parentView);
attachChild(ticTacToeRouter);
parentView.addView(ticTacToeRouter.getView());
}
void detachTicTacToe() {
if (ticTacToeRouter != null) {
detachChild(ticTacToeRouter);
parentView.removeView(ticTacToeRouter.getView());
ticTacToeRouter = null;
}
}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/offgame/OffGameBuilder.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.root.loggedin.offgame;
import static java.lang.annotation.RetentionPolicy.CLASS;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.uber.rib.core.InteractorBaseComponent;
import com.uber.rib.core.ViewBuilder;
import com.uber.rib.flipper.R;
import dagger.Binds;
import dagger.BindsInstance;
import dagger.Provides;
import java.lang.annotation.Retention;
import javax.inject.Qualifier;
import javax.inject.Scope;
/** Builder for the {@link OffGameScope}. */
public class OffGameBuilder
extends ViewBuilder {
public OffGameBuilder(ParentComponent dependency) {
super(dependency);
}
/**
* Builds a new {@link OffGameRouter}.
*
* @param parentViewGroup parent view group that this router's view will be added to.
* @return a new {@link OffGameRouter}.
*/
public OffGameRouter build(ViewGroup parentViewGroup) {
OffGameView view = createView(parentViewGroup);
OffGameInteractor interactor = new OffGameInteractor();
Component component =
DaggerOffGameBuilder_Component.builder()
.parentComponent(getDependency())
.view(view)
.interactor(interactor)
.build();
return component.offgameRouter();
}
@Override
protected OffGameView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) {
return (OffGameView) inflater.inflate(R.layout.off_game_rib, parentViewGroup, false);
}
public interface ParentComponent {
OffGameInteractor.Listener listener();
}
@dagger.Module
public abstract static class Module {
@OffGameScope
@Binds
abstract OffGameInteractor.OffGamePresenter presenter(OffGameView view);
@OffGameScope
@Provides
static OffGameRouter router(
Component component, OffGameView view, OffGameInteractor interactor) {
return new OffGameRouter(view, interactor, component);
}
}
@OffGameScope
@dagger.Component(modules = Module.class, dependencies = ParentComponent.class)
interface Component extends InteractorBaseComponent, BuilderComponent {
@dagger.Component.Builder
interface Builder {
@BindsInstance
Builder interactor(OffGameInteractor interactor);
@BindsInstance
Builder view(OffGameView view);
Builder parentComponent(ParentComponent component);
Component build();
}
}
interface BuilderComponent {
OffGameRouter offgameRouter();
}
@Scope
@Retention(CLASS)
@interface OffGameScope {}
@Qualifier
@Retention(CLASS)
@interface OffGameInternal {}
}
================================================
FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/offgame/OffGameInteractor.java
================================================
/*
* Copyright (C) 2017. Uber Technologies
*
* 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.
*/
package com.uber.rib.root.loggedin.offgame;
import androidx.annotation.Nullable;
import com.uber.rib.core.Bundle;
import com.uber.rib.core.Interactor;
import com.uber.rib.core.RibInteractor;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
import javax.inject.Inject;
/** Coordinates Business Logic for {@link OffGameScope}. */
@RibInteractor
public class OffGameInteractor
extends Interactor {
@Inject Listener listener;
@Inject OffGamePresenter presenter;
@Override
protected void didBecomeActive(@Nullable Bundle savedInstanceState) {
super.didBecomeActive(savedInstanceState);
presenter
.startGameRequest()
.subscribe(
new Consumer