Skip to content

Commit 08441e7

Browse files
arazabishovtido64
andauthored
Support for the example app on Android (#24)
* Supporting example app for Android * Refactoring test-app code and applying suggestions * Downgrading react-native to v0.60.6 * CI pipeline definition for android * Printing current working directory on CI * Configure pipeline to build android code without emulator * Skip using a task to install sdk manager * Experimenting with builds on windows * Using a specific node version * Checking what is causing the problem with windows ci * Print file on windows * Print file on windows * Explicitly using cmd * Running gradle command separately * Cleaning-up the build definition file * Addressing PR comments * Attempting to run bash on windows ci agent * Move file level var to the method def * Merging android job definitions * Treat gradlew as executable in build.yml * Update error message. Co-Authored-By: Tommy Nguyen <[email protected]> * Adds a todo with a link to an issue Co-authored-by: Tommy Nguyen <[email protected]>
1 parent d4d43a4 commit 08441e7

32 files changed

+1073
-292
lines changed

.github/workflows/build.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,35 @@ jobs:
5454
pod install
5555
../scripts/xcodebuild-ios.sh TemplateExample.xcworkspace build
5656
working-directory: template-example
57+
Android:
58+
strategy:
59+
matrix:
60+
os: [macos-latest, windows-latest]
61+
runs-on: ${{ matrix.os }}
62+
steps:
63+
- name: Checkout
64+
uses: actions/checkout@v2
65+
- name: Set up JDK
66+
uses: actions/setup-java@v1
67+
with:
68+
java-version: 1.8
69+
- name: Set up Node
70+
uses: actions/setup-node@v1
71+
with:
72+
# node has a bug where it crashes compiling a regular
73+
# expression of react-native cli. Using a specific
74+
# node version helps to workaround this problem:
75+
# https://github.com/facebook/react-native/issues/26598
76+
node-version: 12.9.1
77+
- name: Install
78+
run: |
79+
yarn
80+
working-directory: example
81+
- name: Build
82+
shell: bash
83+
run: |
84+
set -eo pipefail
85+
yarn build:android
86+
./gradlew clean build check test
87+
working-directory: example
88+

android/app/build.gradle

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,40 @@
1-
import java.nio.file.Paths
1+
buildscript {
2+
ext.kotlinVersion = "1.3.70"
23

3-
apply plugin: 'com.android.application'
4-
apply plugin: 'kotlin-android'
5-
apply plugin: 'kotlin-android-extensions'
4+
def buildscriptDir = buildscript.sourceFile.getParent()
5+
apply from: "$buildscriptDir/../test-app-util.gradle"
66

7-
def buildscriptDir = buildscript.sourceFile.getParent()
8-
apply from: "$buildscriptDir/../../test-app-util.gradle"
7+
repositories {
8+
jcenter()
9+
google()
10+
}
11+
12+
dependencies {
13+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
14+
classpath "com.android.tools.build:gradle:3.6.1"
15+
}
16+
}
17+
18+
repositories {
19+
maven {
20+
url("${findNodeModulesPath(rootDir, "react-native")}/android")
21+
}
22+
23+
jcenter()
24+
google()
25+
}
26+
27+
apply plugin: "com.android.application"
28+
apply plugin: "kotlin-android"
29+
apply plugin: "kotlin-android-extensions"
30+
apply plugin: "kotlin-kapt"
31+
32+
def testAppDir = file("$projectDir/../../")
33+
34+
apply from: file("${testAppDir}/test-app.gradle")
35+
applyTestAppModule(project, "com.sample")
36+
37+
project.ext.react = [enableHermes: true]
938

1039
android {
1140
compileSdkVersion 29
@@ -19,20 +48,43 @@ android {
1948
versionName "1.0"
2049
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
2150
}
51+
52+
packagingOptions {
53+
pickFirst "lib/armeabi-v7a/libc++_shared.so"
54+
pickFirst "lib/arm64-v8a/libc++_shared.so"
55+
pickFirst "lib/x86_64/libc++_shared.so"
56+
pickFirst "lib/x86/libc++_shared.so"
57+
}
2258
}
2359

24-
def hermesEnginePath = findNodeModulePath("hermes-engine")
60+
// TODO: switch back to using path below when running on react-native v0.61.5
61+
// def hermesEnginePath = findNodeModulesPath(projectDir, "hermes-engine")
62+
def hermesEnginePath = findNodeModulesPath(projectDir, "hermesvm")
2563
def hermesPath = "$hermesEnginePath/android"
2664

2765
dependencies {
28-
debugImplementation files("$hermesPath/hermes-debug.aar")
66+
implementation "com.google.dagger:dagger:2.27"
67+
implementation "com.google.dagger:dagger-android:2.27"
68+
implementation "com.google.dagger:dagger-android-support:2.27"
69+
70+
kapt("com.google.dagger:dagger-compiler:2.27")
71+
kapt("com.google.dagger:dagger-android-processor:2.27")
72+
2973
releaseImplementation files("$hermesPath/hermes-release.aar")
74+
debugImplementation files("$hermesPath/hermes-debug.aar")
3075

3176
implementation "com.facebook.react:react-native:+"
32-
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
33-
implementation 'androidx.appcompat:appcompat:1.0.2'
34-
implementation 'androidx.core:core-ktx:1.0.2'
35-
testImplementation 'junit:junit:4.12'
36-
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
37-
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
77+
78+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
79+
implementation "androidx.appcompat:appcompat:1.1.0"
80+
implementation "androidx.core:core-ktx:1.2.0"
81+
implementation "androidx.recyclerview:recyclerview:1.1.0"
82+
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
83+
84+
implementation("com.squareup.moshi:moshi-kotlin:1.9.2")
85+
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.2")
86+
87+
testImplementation "junit:junit:4.13"
88+
androidTestImplementation "androidx.test.ext:junit:1.1.1"
89+
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0"
3890
}

android/app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<uses-permission android:name="android.permission.INTERNET" />
66

77
<application
8+
android:name=".TestApp"
89
android:allowBackup="true"
910
android:icon="@mipmap/ic_launcher"
1011
android:label="@string/app_name"
@@ -21,6 +22,8 @@
2122
<category android:name="android.intent.category.LAUNCHER" />
2223
</intent-filter>
2324
</activity>
25+
26+
<activity android:name=".ComponentActivity" />
2427
</application>
2528

2629
</manifest>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.sample
2+
3+
import android.app.Activity
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import com.facebook.react.ReactActivity
7+
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
8+
import com.facebook.soloader.SoLoader
9+
10+
class ComponentActivity : ReactActivity(), DefaultHardwareBackBtnHandler {
11+
companion object {
12+
private const val COMPONENT_NAME = "extra:componentName";
13+
14+
fun newIntent(activity: Activity, componentName: String): Intent {
15+
return Intent(activity, ComponentActivity::class.java).apply {
16+
putExtra(COMPONENT_NAME, componentName)
17+
}
18+
}
19+
}
20+
21+
override fun onCreate(savedInstanceState: Bundle?) {
22+
super.onCreate(savedInstanceState)
23+
24+
SoLoader.init(this, false)
25+
26+
val componentName = intent.getStringExtra(COMPONENT_NAME)
27+
loadApp(componentName)
28+
}
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.sample
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import android.widget.TextView
6+
import androidx.recyclerview.widget.RecyclerView
7+
8+
class ComponentListAdapter(
9+
private val layoutInflater: LayoutInflater,
10+
private val components: List<ComponentViewModel>,
11+
private val listener: (ComponentViewModel) -> Unit
12+
) : RecyclerView.Adapter<ComponentListAdapter.ComponentViewHolder>() {
13+
14+
override fun getItemCount() = components.size
15+
16+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ComponentViewHolder {
17+
return ComponentViewHolder(
18+
layoutInflater.inflate(
19+
R.layout.recyclerview_item_component, parent, false
20+
) as TextView
21+
)
22+
}
23+
24+
override fun onBindViewHolder(holder: ComponentViewHolder, position: Int) {
25+
holder.bindTo(components[position])
26+
}
27+
28+
inner class ComponentViewHolder(private val view: TextView) : RecyclerView.ViewHolder(view) {
29+
init {
30+
view.setOnClickListener { listener(components[adapterPosition]) }
31+
}
32+
33+
fun bindTo(component: ComponentViewModel) {
34+
view.text = component.displayName
35+
}
36+
}
37+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.sample
2+
3+
data class ComponentViewModel(val name: String, val displayName: String)
Lines changed: 26 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,43 @@
11
package com.sample
22

33
import android.os.Bundle
4-
import android.view.KeyEvent
4+
import android.view.LayoutInflater
55
import androidx.appcompat.app.AppCompatActivity
6-
import com.facebook.react.PackageList
7-
import com.facebook.react.ReactInstanceManager
8-
import com.facebook.react.ReactRootView
9-
import com.facebook.react.TestAppPackageList
10-
import com.facebook.react.common.LifecycleState
11-
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
12-
import com.facebook.soloader.SoLoader
6+
import androidx.recyclerview.widget.DividerItemDecoration
7+
import androidx.recyclerview.widget.LinearLayoutManager
8+
import androidx.recyclerview.widget.RecyclerView
9+
import com.sample.manifest.ManifestProvider
10+
import dagger.android.AndroidInjection
11+
import javax.inject.Inject
1312

14-
class MainActivity : AppCompatActivity(), DefaultHardwareBackBtnHandler {
15-
private lateinit var reactRootView: ReactRootView
16-
private lateinit var reactInstanceManager: ReactInstanceManager
13+
class MainActivity : AppCompatActivity() {
1714

18-
override fun onCreate(savedInstanceState: Bundle?) {
19-
super.onCreate(savedInstanceState)
20-
21-
SoLoader.init(this, false)
22-
23-
reactRootView = ReactRootView(this)
24-
setContentView(reactRootView)
25-
26-
reactInstanceManager = ReactInstanceManager.builder()
27-
.setInitialLifecycleState(LifecycleState.BEFORE_RESUME)
28-
.addPackages(PackageList(application).packages)
29-
.addPackages(TestAppPackageList().packages)
30-
.setUseDeveloperSupport(BuildConfig.DEBUG)
31-
.setCurrentActivity(this)
32-
.setBundleAssetName("index.android.bundle")
33-
.setJSMainModulePath("index")
34-
.setApplication(application)
35-
.build()
15+
@Inject
16+
lateinit var manifestProvider: ManifestProvider
3617

37-
reactRootView.startReactApplication(
38-
reactInstanceManager, "TestComponent", null
39-
)
18+
private val listener = { component: ComponentViewModel ->
19+
startActivity(ComponentActivity.newIntent(this, component.name))
4020
}
4121

42-
override fun invokeDefaultOnBackPressed() {
43-
onBackPressed()
44-
}
22+
override fun onCreate(savedInstanceState: Bundle?) {
23+
AndroidInjection.inject(this)
4524

46-
override fun onBackPressed() {
47-
reactInstanceManager.onBackPressed()
48-
}
25+
super.onCreate(savedInstanceState)
26+
setContentView(R.layout.activity_main)
4927

50-
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
51-
if (keyCode == KeyEvent.KEYCODE_MENU) {
52-
reactInstanceManager.showDevOptionsDialog()
53-
return true
28+
val manifest = manifestProvider.manifest
29+
?: throw IllegalStateException("app.json is not provided or TestApp is misconfigured")
30+
val components = manifest.components.map {
31+
ComponentViewModel(it.key, it.value.displayName)
5432
}
55-
return super.onKeyUp(keyCode, event)
56-
}
57-
58-
59-
override fun onPause() {
60-
super.onPause()
61-
62-
reactInstanceManager.onHostPause(this)
63-
}
64-
65-
override fun onResume() {
66-
super.onResume()
6733

68-
reactInstanceManager.onHostResume(this, this)
69-
}
34+
supportActionBar?.title = manifest.displayName
7035

71-
override fun onDestroy() {
72-
super.onDestroy()
36+
findViewById<RecyclerView>(R.id.recyclerview).apply {
37+
layoutManager = LinearLayoutManager(context)
38+
adapter = ComponentListAdapter(LayoutInflater.from(context), components, listener)
7339

74-
reactInstanceManager.onHostDestroy(this)
75-
reactRootView.unmountReactApplication()
40+
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
41+
}
7642
}
7743
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.sample
2+
3+
import android.app.Application
4+
import com.facebook.react.ReactApplication
5+
import com.facebook.react.ReactNativeHost
6+
import com.sample.di.DaggerTestAppComponent
7+
import dagger.android.AndroidInjector
8+
import dagger.android.DispatchingAndroidInjector
9+
import dagger.android.HasAndroidInjector
10+
import javax.inject.Inject
11+
12+
class TestApp : Application(), HasAndroidInjector, ReactApplication {
13+
14+
@Inject
15+
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
16+
17+
@Inject
18+
lateinit var reactNativeHostInternal: ReactNativeHost
19+
20+
override fun onCreate() {
21+
super.onCreate()
22+
23+
val testAppComponent = DaggerTestAppComponent.builder()
24+
.binds(this)
25+
.build()
26+
27+
testAppComponent.inject(this)
28+
}
29+
30+
override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector
31+
32+
override fun getReactNativeHost() = reactNativeHostInternal
33+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.sample.di
2+
3+
import javax.inject.Scope
4+
5+
@Scope
6+
@Retention(AnnotationRetention.RUNTIME)
7+
annotation class ActivityScope
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.sample.di
2+
3+
import android.app.Application
4+
import android.content.Context
5+
import com.facebook.react.ReactNativeHost
6+
import com.sample.MainActivity
7+
import com.sample.react.TestAppReactNativeHost
8+
import dagger.Binds
9+
import dagger.Module
10+
import dagger.android.ContributesAndroidInjector
11+
12+
@Module
13+
abstract class TestAppBindings {
14+
15+
@Binds
16+
abstract fun bindsContext(application: Application): Context
17+
18+
@Binds
19+
abstract fun bindsReactNativeHost(reactNativeHost: TestAppReactNativeHost): ReactNativeHost
20+
21+
@ActivityScope
22+
@ContributesAndroidInjector
23+
abstract fun contributeMainActivityInjector(): MainActivity
24+
}

0 commit comments

Comments
 (0)