Skip to content

Commit

Permalink
TicTacToe sample app in RIBs using Compose (#480)
Browse files Browse the repository at this point in the history
* Default app icon

* App scaffold

* Add Flipper

* Root Android view-based RIB

* Sample clients for XP & analytics plus data & event streams

* Main RIB w/ Compose interop

* LoggedOut Compose RIB

* LoggedIn, TicTacToe, & OffGame Compose RIBs
  • Loading branch information
jbarr21 authored Sep 29, 2021
1 parent 340b18c commit 1ed4c47
Show file tree
Hide file tree
Showing 65 changed files with 2,372 additions and 1 deletion.
67 changes: 67 additions & 0 deletions android/demos/compose/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: "org.jetbrains.kotlin.kapt"

android {
compileSdkVersion deps.build.compileSdkVersion
buildToolsVersion deps.build.buildToolsVersion

defaultConfig {
minSdkVersion deps.build.minSdkVersion
targetSdkVersion deps.build.targetSdkVersion
applicationId "com.uber.rib.compose"
versionCode 1
versionName "1.0"
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion "${deps.versions.androidx.compose}"
}
compileOptions {
sourceCompatibility deps.build.javaVersion
targetCompatibility deps.build.javaVersion
}

// No need for lint. This is just a tutorial.
lintOptions {
abortOnError false
quiet true
}
}

dependencies {
kapt deps.uber.motifCompiler
implementation project(":libraries:rib-android")
implementation project(":libraries:rib-android-compose")
implementation deps.androidx.activityCompose
implementation deps.androidx.annotations
implementation deps.androidx.appcompat
implementation deps.androidx.composeAnimation
implementation deps.androidx.composeFoundation
implementation deps.androidx.composeMaterial
implementation deps.androidx.composeNavigation
implementation deps.androidx.composeRuntimeRxJava2
implementation deps.androidx.composeUi
implementation deps.androidx.composeViewModel
implementation deps.androidx.composeUiTooling
implementation deps.external.rxandroid2
implementation deps.kotlin.coroutines
implementation deps.kotlin.coroutinesAndroid
implementation deps.kotlin.coroutinesRx2
implementation deps.kotlin.stdlib
implementation deps.uber.autodisposeCoroutines
implementation deps.uber.motif


// Flipper Debug tool integration
debugImplementation 'com.facebook.flipper:flipper:0.93.0'
debugImplementation 'com.facebook.soloader:soloader:0.10.1'
releaseImplementation 'com.facebook.flipper:flipper-noop:0.93.0'

// Flipper RIBs plugin
implementation project(":tooling:rib-flipper-plugin")

testImplementation deps.test.junit
}
26 changes: 26 additions & 0 deletions android/demos/compose/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.uber.rib.compose">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".ComposeApplication"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Android">

<activity
android:name=".root.RootActivity"
android:label="RIB Compose"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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<NoDependencies> {
fun rootScope(@Expose activity: RibActivity, parentViewGroup: ViewGroup): RootScope
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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<EmptyPresenter, RootRouter>(presenter)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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<RootView, RootInteractor>(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)
this@RootRouter.view.addView(it.view)
}
}
}

private fun detachMain() {
mainRouter?.let {
this@RootRouter.view.removeView(it.view)
detachChild(it)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.savedstate.ViewTreeSavedStateRegistryOwner
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, activity: RibActivity): RootView {
return RootView(parentViewGroup.context).apply {
ViewTreeLifecycleOwner.set(this, activity)
ViewTreeSavedStateRegistryOwner.set(this, activity)
}
}

@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)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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)
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.jakewharton.rxrelay2.BehaviorRelay
import io.reactivex.Observable

class AuthStream {
private val authRelay = BehaviorRelay.createDefault(AuthInfo(false, "", ""))

fun observe(): Observable<AuthInfo> = authRelay.hide()

fun accept(value: AuthInfo) {
authRelay.accept(value)
}
}

data class AuthInfo(val isLoggedIn: Boolean, val playerOne: String = "", val playerTwo: String = "")
Loading

0 comments on commit 1ed4c47

Please sign in to comment.