Skip to content

Commit

Permalink
Support for the example app on Android (#24)
Browse files Browse the repository at this point in the history
* 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 <4123478+tido64@users.noreply.github.com>

* Adds a todo with a link to an issue

Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com>
  • Loading branch information
arazabishov and tido64 committed Mar 25, 2020
1 parent d4d43a4 commit 08441e7
Show file tree
Hide file tree
Showing 32 changed files with 1,073 additions and 292 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,35 @@ jobs:
pod install
../scripts/xcodebuild-ios.sh TemplateExample.xcworkspace build
working-directory: template-example
Android:
strategy:
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Set up Node
uses: actions/setup-node@v1
with:
# node has a bug where it crashes compiling a regular
# expression of react-native cli. Using a specific
# node version helps to workaround this problem:
# https://github.com/facebook/react-native/issues/26598
node-version: 12.9.1
- name: Install
run: |
yarn
working-directory: example
- name: Build
shell: bash
run: |
set -eo pipefail
yarn build:android
./gradlew clean build check test
working-directory: example

80 changes: 66 additions & 14 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
import java.nio.file.Paths
buildscript {
ext.kotlinVersion = "1.3.70"

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

def buildscriptDir = buildscript.sourceFile.getParent()
apply from: "$buildscriptDir/../../test-app-util.gradle"
repositories {
jcenter()
google()
}

dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.android.tools.build:gradle:3.6.1"
}
}

repositories {
maven {
url("${findNodeModulesPath(rootDir, "react-native")}/android")
}

jcenter()
google()
}

apply plugin: "com.android.application"
apply plugin: "kotlin-android"
apply plugin: "kotlin-android-extensions"
apply plugin: "kotlin-kapt"

def testAppDir = file("$projectDir/../../")

apply from: file("${testAppDir}/test-app.gradle")
applyTestAppModule(project, "com.sample")

project.ext.react = [enableHermes: true]

android {
compileSdkVersion 29
Expand All @@ -19,20 +48,43 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

packagingOptions {
pickFirst "lib/armeabi-v7a/libc++_shared.so"
pickFirst "lib/arm64-v8a/libc++_shared.so"
pickFirst "lib/x86_64/libc++_shared.so"
pickFirst "lib/x86/libc++_shared.so"
}
}

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

dependencies {
debugImplementation files("$hermesPath/hermes-debug.aar")
implementation "com.google.dagger:dagger:2.27"
implementation "com.google.dagger:dagger-android:2.27"
implementation "com.google.dagger:dagger-android-support:2.27"

kapt("com.google.dagger:dagger-compiler:2.27")
kapt("com.google.dagger:dagger-android-processor:2.27")

releaseImplementation files("$hermesPath/hermes-release.aar")
debugImplementation files("$hermesPath/hermes-debug.aar")

implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"

implementation("com.squareup.moshi:moshi-kotlin:1.9.2")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.2")

testImplementation "junit:junit:4.13"
androidTestImplementation "androidx.test.ext:junit:1.1.1"
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0"
}
3 changes: 3 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".TestApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
Expand All @@ -21,6 +22,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name=".ComponentActivity" />
</application>

</manifest>
29 changes: 29 additions & 0 deletions android/app/src/main/java/com/sample/ComponentActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.sample

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.facebook.react.ReactActivity
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
import com.facebook.soloader.SoLoader

class ComponentActivity : ReactActivity(), DefaultHardwareBackBtnHandler {
companion object {
private const val COMPONENT_NAME = "extra:componentName";

fun newIntent(activity: Activity, componentName: String): Intent {
return Intent(activity, ComponentActivity::class.java).apply {
putExtra(COMPONENT_NAME, componentName)
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

SoLoader.init(this, false)

val componentName = intent.getStringExtra(COMPONENT_NAME)
loadApp(componentName)
}
}
37 changes: 37 additions & 0 deletions android/app/src/main/java/com/sample/ComponentListAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.sample

import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class ComponentListAdapter(
private val layoutInflater: LayoutInflater,
private val components: List<ComponentViewModel>,
private val listener: (ComponentViewModel) -> Unit
) : RecyclerView.Adapter<ComponentListAdapter.ComponentViewHolder>() {

override fun getItemCount() = components.size

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ComponentViewHolder {
return ComponentViewHolder(
layoutInflater.inflate(
R.layout.recyclerview_item_component, parent, false
) as TextView
)
}

override fun onBindViewHolder(holder: ComponentViewHolder, position: Int) {
holder.bindTo(components[position])
}

inner class ComponentViewHolder(private val view: TextView) : RecyclerView.ViewHolder(view) {
init {
view.setOnClickListener { listener(components[adapterPosition]) }
}

fun bindTo(component: ComponentViewModel) {
view.text = component.displayName
}
}
}
3 changes: 3 additions & 0 deletions android/app/src/main/java/com/sample/ComponentViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.sample

data class ComponentViewModel(val name: String, val displayName: String)
86 changes: 26 additions & 60 deletions android/app/src/main/java/com/sample/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,77 +1,43 @@
package com.sample

import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.facebook.react.PackageList
import com.facebook.react.ReactInstanceManager
import com.facebook.react.ReactRootView
import com.facebook.react.TestAppPackageList
import com.facebook.react.common.LifecycleState
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
import com.facebook.soloader.SoLoader
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.sample.manifest.ManifestProvider
import dagger.android.AndroidInjection
import javax.inject.Inject

class MainActivity : AppCompatActivity(), DefaultHardwareBackBtnHandler {
private lateinit var reactRootView: ReactRootView
private lateinit var reactInstanceManager: ReactInstanceManager
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

SoLoader.init(this, false)

reactRootView = ReactRootView(this)
setContentView(reactRootView)

reactInstanceManager = ReactInstanceManager.builder()
.setInitialLifecycleState(LifecycleState.BEFORE_RESUME)
.addPackages(PackageList(application).packages)
.addPackages(TestAppPackageList().packages)
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.setApplication(application)
.build()
@Inject
lateinit var manifestProvider: ManifestProvider

reactRootView.startReactApplication(
reactInstanceManager, "TestComponent", null
)
private val listener = { component: ComponentViewModel ->
startActivity(ComponentActivity.newIntent(this, component.name))
}

override fun invokeDefaultOnBackPressed() {
onBackPressed()
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)

override fun onBackPressed() {
reactInstanceManager.onBackPressed()
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_MENU) {
reactInstanceManager.showDevOptionsDialog()
return true
val manifest = manifestProvider.manifest
?: throw IllegalStateException("app.json is not provided or TestApp is misconfigured")
val components = manifest.components.map {
ComponentViewModel(it.key, it.value.displayName)
}
return super.onKeyUp(keyCode, event)
}


override fun onPause() {
super.onPause()

reactInstanceManager.onHostPause(this)
}

override fun onResume() {
super.onResume()

reactInstanceManager.onHostResume(this, this)
}
supportActionBar?.title = manifest.displayName

override fun onDestroy() {
super.onDestroy()
findViewById<RecyclerView>(R.id.recyclerview).apply {
layoutManager = LinearLayoutManager(context)
adapter = ComponentListAdapter(LayoutInflater.from(context), components, listener)

reactInstanceManager.onHostDestroy(this)
reactRootView.unmountReactApplication()
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
}
}
}
33 changes: 33 additions & 0 deletions android/app/src/main/java/com/sample/TestApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.sample

import android.app.Application
import com.facebook.react.ReactApplication
import com.facebook.react.ReactNativeHost
import com.sample.di.DaggerTestAppComponent
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import javax.inject.Inject

class TestApp : Application(), HasAndroidInjector, ReactApplication {

@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>

@Inject
lateinit var reactNativeHostInternal: ReactNativeHost

override fun onCreate() {
super.onCreate()

val testAppComponent = DaggerTestAppComponent.builder()
.binds(this)
.build()

testAppComponent.inject(this)
}

override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector

override fun getReactNativeHost() = reactNativeHostInternal
}
7 changes: 7 additions & 0 deletions android/app/src/main/java/com/sample/di/ActivityScope.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sample.di

import javax.inject.Scope

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
24 changes: 24 additions & 0 deletions android/app/src/main/java/com/sample/di/TestAppBindings.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.sample.di

import android.app.Application
import android.content.Context
import com.facebook.react.ReactNativeHost
import com.sample.MainActivity
import com.sample.react.TestAppReactNativeHost
import dagger.Binds
import dagger.Module
import dagger.android.ContributesAndroidInjector

@Module
abstract class TestAppBindings {

@Binds
abstract fun bindsContext(application: Application): Context

@Binds
abstract fun bindsReactNativeHost(reactNativeHost: TestAppReactNativeHost): ReactNativeHost

@ActivityScope
@ContributesAndroidInjector
abstract fun contributeMainActivityInjector(): MainActivity
}

0 comments on commit 08441e7

Please sign in to comment.