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

RIBs

[![Android CI](https://github.com/uber/RIBs/actions/workflows/android.yml/badge.svg?branch=main)](https://github.com/uber/RIBs/actions/workflows/android.yml) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Maven Central](https://img.shields.io/maven-central/v/com.uber.rib/rib-android.svg)](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() { @Override public void accept(Object object) throws Exception { listener.onStartGame(); } }); } public interface Listener { void onStartGame(); } /** Presenter interface implemented by this RIB's view. */ interface OffGamePresenter { Observable startGameRequest(); } } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/offgame/OffGameRouter.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 com.uber.rib.core.ViewRouter; /** Adds and removes children of {@link OffGameBuilder.OffGameScope}. */ public class OffGameRouter extends ViewRouter { public OffGameRouter( OffGameView view, OffGameInteractor interactor, OffGameBuilder.Component component) { super(view, interactor, component); } } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/offgame/OffGameView.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 android.content.Context; import android.util.AttributeSet; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.Nullable; import com.jakewharton.rxbinding2.view.RxView; import com.uber.rib.core.Initializer; import com.uber.rib.flipper.R; import io.reactivex.Observable; /** Top level view for {@link OffGameBuilder.OffGameScope}. */ public class OffGameView extends LinearLayout implements OffGameInteractor.OffGamePresenter { private Button button; private TextView playerOneName; private TextView playerTwoName; private TextView playerOneScore; private TextView playerTwoScore; public OffGameView(Context context) { this(context, null); } public OffGameView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public OffGameView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Initializer @Override protected void onFinishInflate() { super.onFinishInflate(); button = (Button) findViewById(R.id.start_game_button); playerOneName = (TextView) findViewById(R.id.player_one_name); playerTwoName = (TextView) findViewById(R.id.player_two_name); playerOneScore = (TextView) findViewById(R.id.player_one_win_count); playerTwoScore = (TextView) findViewById(R.id.player_two_win_count); } @Override public Observable startGameRequest() { return RxView.clicks(button); } } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/tictactoe/Board.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.tictactoe; import javax.inject.Inject; class Board { static final int ROWS = 3; static final int COLS = 3; MarkerType[][] cells; int currentRow; int currentCol; @Inject Board() { cells = new MarkerType[ROWS][COLS]; for (int row = 0; row < ROWS; ++row) { for (int col = 0; col < COLS; ++col) { cells[row][col] = null; } } } /** Return true if it is a draw (i.e., no more EMPTY cell) */ boolean isDraw() { for (int row = 0; row < ROWS; ++row) { for (int col = 0; col < COLS; ++col) { 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) */ boolean hasWon(MarkerType theSeed) { 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 MarkerType { CROSS, NOUGHT } } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/tictactoe/BoardCoordinate.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.tictactoe; class BoardCoordinate { private final int x; private final int y; BoardCoordinate(int x, int y) { this.x = x; this.y = y; } int getX() { return x; } int getY() { return y; } } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/tictactoe/TicTacToeBuilder.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.tictactoe; 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 TicTacToeScope}. */ public class TicTacToeBuilder extends ViewBuilder { public TicTacToeBuilder(ParentComponent dependency) { super(dependency); } /** * Builds a new {@link TicTacToeRouter}. * * @param parentViewGroup parent view group that this router's view will be added to. * @return a new {@link TicTacToeRouter}. */ public TicTacToeRouter build(ViewGroup parentViewGroup) { TicTacToeView view = createView(parentViewGroup); TicTacToeInteractor interactor = new TicTacToeInteractor(); Component component = DaggerTicTacToeBuilder_Component.builder() .parentComponent(getDependency()) .view(view) .interactor(interactor) .build(); return component.tictactoeRouter(); } @Override protected TicTacToeView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) { return (TicTacToeView) inflater.inflate(R.layout.tic_tac_toe_rib, parentViewGroup, false); } public interface ParentComponent { // TODO: Define dependencies required from your parent interactor here. } @dagger.Module public abstract static class Module { @TicTacToeScope @Binds abstract TicTacToeInteractor.TicTacToePresenter presenter(TicTacToeView view); @TicTacToeScope @Provides static TicTacToeRouter router( Component component, TicTacToeView view, TicTacToeInteractor interactor) { return new TicTacToeRouter(view, interactor, component); } } @TicTacToeScope @dagger.Component(modules = Module.class, dependencies = ParentComponent.class) interface Component extends InteractorBaseComponent, BuilderComponent { @dagger.Component.Builder interface Builder { @BindsInstance Builder interactor(TicTacToeInteractor interactor); @BindsInstance Builder view(TicTacToeView view); Builder parentComponent(ParentComponent component); Component build(); } } interface BuilderComponent { TicTacToeRouter tictactoeRouter(); } @Scope @Retention(CLASS) @interface TicTacToeScope {} @Qualifier @Retention(CLASS) @interface TicTacToeInternal {} } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/tictactoe/TicTacToeInteractor.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.tictactoe; 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.loggedin.tictactoe.Board.MarkerType; import io.reactivex.Observable; import io.reactivex.functions.Consumer; import javax.inject.Inject; /** Coordinates Business Logic for {@link TicTacToeScope}. */ @RibInteractor public class TicTacToeInteractor extends Interactor { @Inject Board board; @Inject TicTacToePresenter presenter; private final String playerOne = "Fake name 1"; private final String playerTwo = "Fake name 2"; private MarkerType currentPlayer = MarkerType.CROSS; @Override protected void didBecomeActive(@Nullable Bundle savedInstanceState) { super.didBecomeActive(savedInstanceState); presenter .squareClicks() .subscribe( new Consumer() { @Override public void accept(BoardCoordinate xy) throws Exception { if (board.cells[xy.getX()][xy.getY()] == null) { if (currentPlayer == MarkerType.CROSS) { board.cells[xy.getX()][xy.getY()] = MarkerType.CROSS; board.currentRow = xy.getX(); board.currentCol = xy.getY(); presenter.addCross(xy); currentPlayer = MarkerType.NOUGHT; } else { board.cells[xy.getX()][xy.getY()] = MarkerType.NOUGHT; board.currentRow = xy.getX(); board.currentCol = xy.getY(); presenter.addNought(xy); currentPlayer = MarkerType.CROSS; } } if (board.hasWon(MarkerType.CROSS)) { presenter.setPlayerWon(playerOne); } else if (board.hasWon(MarkerType.NOUGHT)) { presenter.setPlayerWon(playerTwo); } else if (board.isDraw()) { presenter.setPlayerTie(); } else { updateCurrentPlayer(); } } }); updateCurrentPlayer(); } private void updateCurrentPlayer() { if (currentPlayer == MarkerType.CROSS) { presenter.setCurrentPlayerName(playerOne); } else { presenter.setCurrentPlayerName(playerTwo); } } /** Presenter interface implemented by this RIB's view. */ interface TicTacToePresenter { Observable squareClicks(); void setCurrentPlayerName(String currentPlayer); void setPlayerWon(String playerName); void setPlayerTie(); void addCross(BoardCoordinate xy); void addNought(BoardCoordinate xy); } public interface Listener {} } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/tictactoe/TicTacToeRouter.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.tictactoe; import com.uber.rib.core.ViewRouter; /** Adds and removes children of {@link TicTacToeBuilder.TicTacToeScope}. */ public class TicTacToeRouter extends ViewRouter { public TicTacToeRouter( TicTacToeView view, TicTacToeInteractor interactor, TicTacToeBuilder.Component component) { super(view, interactor, component); } } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedin/tictactoe/TicTacToeView.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.tictactoe; import android.content.Context; import android.util.AttributeSet; import android.widget.TextView; import androidx.annotation.Nullable; import androidx.percentlayout.widget.PercentRelativeLayout; import com.jakewharton.rxbinding2.view.RxView; import com.uber.rib.core.Initializer; import com.uber.rib.flipper.R; import io.reactivex.Observable; import io.reactivex.functions.Function; import java.util.ArrayList; /** Top level view for {@link TicTacToeBuilder.TicTacToeScope}. */ public class TicTacToeView extends PercentRelativeLayout implements TicTacToeInteractor.TicTacToePresenter { private TextView[][] imageButtons; private TextView titleView; public TicTacToeView(Context context) { this(context, null); } public TicTacToeView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public TicTacToeView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Initializer @Override protected void onFinishInflate() { super.onFinishInflate(); imageButtons = new TextView[3][]; imageButtons[0] = new TextView[] { (TextView) findViewById(R.id.button11), (TextView) findViewById(R.id.button12), (TextView) findViewById(R.id.button13) }; imageButtons[1] = new TextView[] { (TextView) findViewById(R.id.button21), (TextView) findViewById(R.id.button22), (TextView) findViewById(R.id.button23) }; imageButtons[2] = new TextView[] { (TextView) findViewById(R.id.button31), (TextView) findViewById(R.id.button32), (TextView) findViewById(R.id.button33) }; titleView = (TextView) findViewById(R.id.title); } @Override public Observable squareClicks() { ArrayList> observables = new ArrayList<>(); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { final int finalI = i; final int finalJ = j; observables.add( RxView.clicks(imageButtons[i][j]) .map( new Function() { @Override public BoardCoordinate apply(Object irrelevant) throws Exception { return new BoardCoordinate(finalI, finalJ); } })); } } return Observable.merge(observables); } @Override public void addCross(BoardCoordinate xy) { TextView textView = imageButtons[xy.getX()][xy.getY()]; textView.setText("x"); textView.setClickable(false); } @Override public void addNought(BoardCoordinate xy) { TextView textView = imageButtons[xy.getX()][xy.getY()]; textView.setText("O"); textView.setClickable(false); } @Override public void setCurrentPlayerName(String currentPlayer) { titleView.setText("Current Player: " + currentPlayer); } @Override public void setPlayerWon(String playerName) { titleView.setText("Player won: " + playerName + "!!!"); } @Override public void setPlayerTie() { titleView.setText("Tie game!"); } } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedout/LoggedOutBuilder.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.loggedout; 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 LoggedOutScope}. */ public class LoggedOutBuilder extends ViewBuilder { public LoggedOutBuilder(ParentComponent dependency) { super(dependency); } /** * Builds a new {@link LoggedOutRouter}. * * @param parentViewGroup parent view group that this router's view will be added to. * @return a new {@link LoggedOutRouter}. */ public LoggedOutRouter build(ViewGroup parentViewGroup) { LoggedOutView view = createView(parentViewGroup); LoggedOutInteractor interactor = new LoggedOutInteractor(); Component component = DaggerLoggedOutBuilder_Component.builder() .parentComponent(getDependency()) .view(view) .interactor(interactor) .build(); return component.loggedoutRouter(); } @Override protected LoggedOutView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) { return (LoggedOutView) inflater.inflate(R.layout.logged_out_rib, parentViewGroup, false); } public interface ParentComponent { LoggedOutInteractor.Listener listener(); } @dagger.Module public abstract static class Module { @LoggedOutScope @Binds abstract LoggedOutInteractor.LoggedOutPresenter presenter(LoggedOutView view); @LoggedOutScope @Provides static LoggedOutRouter router( Component component, LoggedOutView view, LoggedOutInteractor interactor) { return new LoggedOutRouter(view, interactor, component); } // TODO: Create provider methods for dependencies created by this Rib. These should be static. } @LoggedOutScope @dagger.Component(modules = Module.class, dependencies = ParentComponent.class) interface Component extends InteractorBaseComponent, BuilderComponent { @dagger.Component.Builder interface Builder { @BindsInstance Builder interactor(LoggedOutInteractor interactor); @BindsInstance Builder view(LoggedOutView view); Builder parentComponent(ParentComponent component); Component build(); } } interface BuilderComponent { LoggedOutRouter loggedoutRouter(); } @Scope @Retention(CLASS) @interface LoggedOutScope {} @Qualifier @Retention(CLASS) @interface LoggedOutInternal {} } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedout/LoggedOutInteractor.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.loggedout; import androidx.annotation.Nullable; import androidx.core.util.Pair; import com.uber.rib.core.Bundle; import com.uber.rib.core.Interactor; import com.uber.rib.core.RibInteractor; import com.uber.rib.root.loggedout.LoggedOutBuilder.LoggedOutScope; import io.reactivex.Observable; import io.reactivex.functions.Consumer; import javax.inject.Inject; /** Coordinates Business Logic for {@link LoggedOutScope}. */ @RibInteractor public class LoggedOutInteractor extends Interactor { @Inject Listener listener; @Inject LoggedOutPresenter presenter; @Override protected void didBecomeActive(@Nullable Bundle savedInstanceState) { super.didBecomeActive(savedInstanceState); presenter .loginName() .subscribe( new Consumer>() { @Override public void accept(Pair names) throws Exception { if (!isEmpty(names.first) && !isEmpty(names.second)) { listener.login(names.first, names.second); } } }); } private boolean isEmpty(@Nullable String string) { return string == null || string.length() == 0; } /** Presenter interface implemented by this RIB's view. */ interface LoggedOutPresenter { Observable> loginName(); } public interface Listener { void login(String userNameA, String userNameB); } } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedout/LoggedOutRouter.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.loggedout; import com.uber.rib.core.ViewRouter; /** Adds and removes children of {@link LoggedOutBuilder.LoggedOutScope}. */ public class LoggedOutRouter extends ViewRouter { public LoggedOutRouter( LoggedOutView view, LoggedOutInteractor interactor, LoggedOutBuilder.Component component) { super(view, interactor, component); } } ================================================ FILE: demos/flipper/src/main/java/com/uber/rib/root/loggedout/LoggedOutView.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.loggedout; import android.content.Context; import android.util.AttributeSet; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.Nullable; import androidx.core.util.Pair; import com.jakewharton.rxbinding2.view.RxView; import com.uber.rib.flipper.R; import io.reactivex.Observable; import io.reactivex.functions.Function; /** Top level view for {@link LoggedOutBuilder.LoggedOutScope}. */ public class LoggedOutView extends LinearLayout implements LoggedOutInteractor.LoggedOutPresenter { public LoggedOutView(Context context) { this(context, null); } public LoggedOutView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public LoggedOutView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public Observable> loginName() { return RxView.clicks(findViewById(R.id.login_button)) .map( new Function>() { @Override public Pair apply(Object o) throws Exception { TextView playerNameOne = (TextView) findViewById(R.id.player_name_1); TextView playerNameTwo = (TextView) findViewById(R.id.player_name_2); return Pair.create( playerNameOne.getText().toString(), playerNameTwo.getText().toString()); } }); } } ================================================ FILE: demos/flipper/src/main/res/layout/logged_out_rib.xml ================================================