Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions apps/minimal-tester/android/brownfield/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("com.facebook.react")
id("expo-brownfield-setup")
}

group = "com.community.minimaltester"

version = "1.0.0"

react { autolinkLibrariesWithApp() }

android {
namespace = "com.community.minimaltester.brownfield"
compileSdk = 36

buildFeatures { buildConfig = true }

defaultConfig {
minSdk = 24
consumerProguardFiles("consumer-rules.pro")
buildConfigField(
"boolean",
"IS_NEW_ARCHITECTURE_ENABLED",
properties["newArchEnabled"].toString(),
)
buildConfigField("boolean", "IS_HERMES_ENABLED", properties["hermesEnabled"].toString())
buildConfigField(
"boolean",
"IS_EDGE_TO_EDGE_ENABLED",
properties["edgeToEdgeEnabled"].toString(),
)
buildConfigField(
"String",
"REACT_NATIVE_RELEASE_LEVEL",
"\"${findProperty("reactNativeReleaseLevel") ?: "stable"}\"",
)
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions { jvmTarget = "17" }
}

dependencies {
api("com.facebook.react:react-android")
api("com.facebook.react:hermes-android")
compileOnly("androidx.fragment:fragment-ktx:1.6.1")
}
Empty file.
21 changes: 21 additions & 0 deletions apps/minimal-tester/android/brownfield/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.community.minimaltester.brownfield

import android.app.Application
import android.content.res.Configuration
import androidx.appcompat.app.AppCompatActivity
import expo.modules.ApplicationLifecycleDispatcher

object BrownfieldLifecycleDispatcher {
fun onApplicationCreate(application: Application) {
ApplicationLifecycleDispatcher.onApplicationCreate(application)
}

fun onConfigurationChanged(application: Application, newConfig: Configuration) {
ApplicationLifecycleDispatcher.onConfigurationChanged(application, newConfig)
}
}

open class BrownfieldActivity : AppCompatActivity() {
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
BrownfieldLifecycleDispatcher.onConfigurationChanged(this.application, newConfig)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.community.minimaltester.brownfield

import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.commit

class ReactNativeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): FrameLayout {
return ReactNativeViewFactory.createFrameLayout(
requireContext(),
requireActivity(),
RootComponent.Main,
)
}

companion object {
private const val TAG = "ReactNativeFragment"

fun createFragmentHost(activity: Activity): ViewGroup {
val layout =
object : FrameLayout(activity) {
init {
id = generateViewId()
}
}

val fragment = createAndCommit(activity, layout)

return layout
}

internal fun createAndCommit(
activity: Activity,
container: ViewGroup,
): ReactNativeFragment {
val fragmentManager = (activity as FragmentActivity).supportFragmentManager

val fragment = ReactNativeFragment()

fragmentManager.commit(true) {
setReorderingAllowed(true)
add(container.id, fragment, TAG)
}

return fragment
}

internal fun findIn(activity: Activity): ReactNativeFragment? {
val activity = activity ?: return null
return (activity as FragmentActivity).supportFragmentManager.findFragmentByTag(TAG)
as ReactNativeFragment?
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.community.minimaltester.brownfield

import android.app.Activity
import android.app.Application
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import com.facebook.react.PackageList
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.common.ReleaseLevel
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
import com.facebook.react.modules.core.DeviceEventManagerModule
import expo.modules.ExpoReactHostFactory
import expo.modules.brownfield.BrownfieldNavigationState

class ReactNativeHostManager {
companion object {
val shared: ReactNativeHostManager by lazy { ReactNativeHostManager() }
private var reactHost: ReactHost? = null
}

fun getReactHost(): ReactHost? {
return reactHost
}

fun initialize(application: Application) {
if (reactHost != null) {
return
}

DefaultNewArchitectureEntryPoint.releaseLevel =
try {
ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
} catch (e: IllegalArgumentException) {
ReleaseLevel.STABLE
}
loadReactNative(application)
BrownfieldLifecycleDispatcher.onApplicationCreate(application)

reactHost = ExpoReactHostFactory.getDefaultReactHost(
context = application.applicationContext,
packageList = PackageList(application).packages
)
}
}

fun Activity.showReactNativeFragment() {
ReactNativeHostManager.shared.initialize(this.application)
val fragment = ReactNativeFragment.createFragmentHost(this)
setContentView(fragment)
setUpNativeBackHandling()
}

fun Activity.setUpNativeBackHandling() {
val componentActivity = this as? ComponentActivity
if (componentActivity == null) {
return
}

val backCallback =
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (BrownfieldNavigationState.nativeBackEnabled) {
isEnabled = false
componentActivity.onBackPressedDispatcher?.onBackPressed()
isEnabled = true
} else {
val reactHost = ReactNativeHostManager.shared.getReactHost()
reactHost?.currentReactContext?.let { reactContext ->
val deviceEventManager =
reactContext.getNativeModule(DeviceEventManagerModule::class.java)
deviceEventManager?.emitHardwareBackPressed()
}
}
}
}

componentActivity.onBackPressedDispatcher?.addCallback(componentActivity, backCallback)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.community.minimaltester.brownfield

import android.content.Context
import android.os.Bundle
import android.widget.FrameLayout
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.facebook.react.ReactDelegate
import com.facebook.react.ReactInstanceManager
import com.facebook.react.ReactRootView

enum class RootComponent(val key: String) {
Main("main")
}

object ReactNativeViewFactory {
fun createFrameLayout(
context: Context,
activity: FragmentActivity,
rootComponent: RootComponent,
launchOptions: Bundle? = null,
): FrameLayout {
val reactHost = ReactNativeHostManager.shared.getReactHost()

val reactDelegate = ReactDelegate(activity, reactHost!!, rootComponent.key, launchOptions)

activity.lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
reactDelegate.onHostResume()
}

override fun onPause(owner: LifecycleOwner) {
reactDelegate.onHostPause()
}

override fun onDestroy(owner: LifecycleOwner) {
reactDelegate.onHostDestroy()
owner.lifecycle.removeObserver(this) // Cleanup to avoid leaks
}
}
)

reactDelegate.loadApp()
return reactDelegate.reactRootView!!

}
}
11 changes: 11 additions & 0 deletions apps/minimal-tester/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ buildscript {
classpath('com.android.tools.build:gradle')
classpath('com.facebook.react:react-native-gradle-plugin')
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
classpath('expo.modules:publish')
}
}

Expand All @@ -21,4 +22,14 @@ allprojects {
}

apply plugin: "expo-root-project"
apply plugin: "expo-brownfield-publish"
apply plugin: "com.facebook.react.rootproject"

expoBrownfieldPublishPlugin {
libraryName = "brownfield"
publications {
localDefault {
type = "localMaven"
}
}
}
9 changes: 9 additions & 0 deletions apps/minimal-tester/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,12 @@ expoAutolinking.useExpoVersionCatalog()

include ':app'
includeBuild(expoAutolinking.reactNativeGradlePlugin)
include ':brownfield'
def brownfieldPluginsPath = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('expo-brownfield/package.json')")
}.standardOutput.asText.get().trim(),
"../gradle-plugins"
).absolutePath
includeBuild(brownfieldPluginsPath)
12 changes: 8 additions & 4 deletions apps/minimal-tester/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@
},
"jsEngine": "hermes",
"plugins": [
["expo-build-properties", {
"ios": {
"buildReactNativeFromSource": true
"expo-brownfield",
[
"expo-build-properties",
{
"ios": {
"buildReactNativeFromSource": true
}
}
}]
]
]
}
}
6 changes: 6 additions & 0 deletions apps/minimal-tester/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
$BROWNFIELD_TARGET_NAME = 'minimaltesterbrownfield'
require File.join(File.dirname(`node --print "require.resolve('expo-brownfield/package.json')"`), "plugin/src/ios/scripts/reorder_build_phases.rb")
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")

Expand Down Expand Up @@ -60,4 +62,8 @@ target 'minimaltester' do
:ccache_enabled => ccache_enabled?(podfile_properties),
)
end

target 'minimaltesterbrownfield' do
inherit! :complete
end
end
Loading
Loading