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
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.callstack.reactnativebrownfield

import android.os.Bundle
import androidx.activity.ComponentActivity
import com.facebook.react.ReactDelegate
import com.facebook.react.ReactHost
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler

class ReactDelegateWrapper(
private val activity: ComponentActivity?,
private val reactHost: ReactHost,
moduleName: String,
launchOptions: Bundle?
): ReactDelegate(activity, reactHost, moduleName, launchOptions){
private lateinit var hardwareBackHandler: () -> Unit
private val backBtnHandler = DefaultHardwareBackBtnHandler {
hardwareBackHandler()
}

/**
* This is invoked when there is no more RN Stack to pop.
* What it means that this is now the initial RN screen.
*/
fun setHardwareBackHandler(backHandler: () -> Unit) {
hardwareBackHandler = backHandler
}

override fun onHostResume() {
reactHost.onHostResume(activity, backBtnHandler)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import android.app.Application
import android.content.Context
import android.os.Bundle
import android.widget.FrameLayout
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.facebook.react.ReactDelegate
import com.facebook.react.ReactInstanceEventListener
import com.facebook.react.ReactInstanceManager
import com.facebook.react.ReactNativeHost
Expand Down Expand Up @@ -99,32 +99,44 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative
context: Context,
activity: FragmentActivity?,
moduleName: String,
reactDelegate: ReactDelegateWrapper? = null,
launchOptions: Bundle? = null,
): FrameLayout {
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
val reactHost = getDefaultReactHost(
context,
shared.reactNativeHost
)
val reactDelegate = ReactDelegate(activity, reactHost, moduleName, launchOptions)

activity?.lifecycle?.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
reactDelegate.onHostResume()
}
val resolvedDelegate = reactDelegate ?: ReactDelegateWrapper(activity, reactHost, moduleName, launchOptions)

override fun onPause(owner: LifecycleOwner) {
reactDelegate.onHostPause()
val mBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// invoked for JS stack back navigation
resolvedDelegate.onBackPressed()
}
}

override fun onDestroy(owner: LifecycleOwner) {
reactDelegate.onHostDestroy()
owner.lifecycle.removeObserver(this) // Cleanup to avoid leaks
}
})
// Register back press callback
activity?.onBackPressedDispatcher?.addCallback(mBackPressedCallback)
// invoked on the last RN screen exit
resolvedDelegate.setHardwareBackHandler {
mBackPressedCallback.isEnabled = false
activity?.onBackPressedDispatcher?.onBackPressed()
}
Comment thread
andrei-zgirvaci marked this conversation as resolved.

reactDelegate.loadApp()
return reactDelegate.reactRootView!!
/**
* When createView method is called in ReactNativeFragment, a reactDelegate
* instance is required. In such a case, we use the lifeCycle events of the fragment.
* When createView method is called elsewhere, then reactDelegate is not required.
* In such a case, we set the lifeCycle observer.
*/
if (reactDelegate == null) {
activity?.lifecycle?.addObserver(getLifeCycleObserver(resolvedDelegate))
}

resolvedDelegate.loadApp()
return resolvedDelegate.reactRootView!!
}

val instanceManager: ReactInstanceManager? = shared.reactNativeHost?.reactInstanceManager
Expand All @@ -137,5 +149,22 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative

return reactView
}

private fun getLifeCycleObserver(reactDelegate: ReactDelegateWrapper): DefaultLifecycleObserver {
return 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
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,60 @@ package com.callstack.reactnativebrownfield;
import android.annotation.TargetApi
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.facebook.infer.annotation.Assertions
import com.facebook.react.ReactFragment
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.WritableMap
import com.facebook.react.common.LifecycleState
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.devsupport.DoubleTapReloadRecognizer
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
import com.facebook.react.modules.core.PermissionAwareActivity
import com.facebook.react.modules.core.PermissionListener

class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
private lateinit var doubleTapReloadRecognizer: DoubleTapReloadRecognizer
private lateinit var permissionsCallback: Callback
private var permissionListener: PermissionListener? = null
private lateinit var moduleName: String

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* ReactFragment.onCreate will throw an exception if we do not provide arg_component_name as arguments.
* We silently catch this exception. The reason is we want to invoke the super<Fragment>.onCreate in
* ReactFragment. Then initialise the mReactDelegate with ReactDelegateWrapper instead of ReactDelegate.
*
* So we purposely force ReactFragment.onCreate to throw an exception, so that we can provide our own
* implementation for mReactDelegate: ReactDelegateWrapper
*/
try{
super.onCreate(savedInstanceState)
} catch (e: IllegalStateException){
Log.w("ReactNativeFragment", "ReactFragment threw due to missing arg_component_name: ${e.message} - This is an expected behaviour.")
}
Comment thread
andrei-zgirvaci marked this conversation as resolved.

moduleName = arguments?.getString(ARG_MODULE_NAME)!!
this.mReactDelegate = this.reactHost?.let {
ReactDelegateWrapper(activity,
it, moduleName, arguments?.getBundle("arg_launch_options"))
}

doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ReactNativeBrownfield.shared.createView(this.requireContext(), activity, moduleName, this.mReactDelegate as ReactDelegateWrapper)
}

override fun getReactHost(): ReactHost? {
return activity?.let {
getDefaultReactHost(
Expand All @@ -40,36 +70,6 @@ class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
return ReactNativeBrownfield.shared.reactNativeHost
}

override fun onResume() {
super.onResume()
if (ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager?.onHostResume(
activity,
activity as DefaultHardwareBackBtnHandler
)
}
}

override fun onPause() {
super.onPause()
if (ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager?.onHostPause(
activity
)
}
}

override fun onDestroy() {
super.onDestroy()
if (ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
val reactInstanceMgr = ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager

if (reactInstanceMgr.lifecycleState != LifecycleState.RESUMED) {
reactInstanceMgr.onHostDestroy(activity)
}
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
Expand Down Expand Up @@ -118,22 +118,16 @@ class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
.didDoubleTapR(keyCode, it)
}
if (didDoubleTapR == true) {
ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager.devSupportManager.handleReloadJS()
reactDelegate.reload()
handled = true
}
}
return handled
}

fun onBackPressed(backBtnHandler: DefaultHardwareBackBtnHandler) {
if (ReactNativeBrownfieldModule.shouldPopToNative) {
backBtnHandler.invokeDefaultOnBackPressed()
} else if (ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager.onBackPressed()
}
}

companion object {
private const val ARG_MODULE_NAME = "arg_module_name"

@JvmStatic
@JvmOverloads
fun createReactNativeFragment(
Expand All @@ -142,7 +136,7 @@ class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
): ReactNativeFragment {
val fragment = ReactNativeFragment()
val args = Bundle()
args.putString(ARG_COMPONENT_NAME, moduleName)
args.putString(ARG_MODULE_NAME, moduleName)
if (initialProps != null) {
args.putBundle(ARG_LAUNCH_OPTIONS, initialProps)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.callstack.reactnativebrownfield.ReactNativeBrownfield
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler

class MainActivity : AppCompatActivity(), DefaultHardwareBackBtnHandler {
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand Down Expand Up @@ -60,10 +59,6 @@ class MainActivity : AppCompatActivity(), DefaultHardwareBackBtnHandler {
}
}

override fun invokeDefaultOnBackPressed() {
super.onBackPressed()
}

fun startReactNativeFragment() {
val intent = Intent(this, ReactNativeFragmentActivity::class.java)
startActivity(intent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ import android.os.Bundle
import android.view.KeyEvent
import androidx.appcompat.app.AppCompatActivity
import com.callstack.reactnativebrownfield.ReactNativeFragment
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler

class ReactNativeFragmentActivity : AppCompatActivity(), DefaultHardwareBackBtnHandler {
override fun invokeDefaultOnBackPressed() {
super.onBackPressed()
}
class ReactNativeFragmentActivity : AppCompatActivity() {

public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -31,13 +27,4 @@ class ReactNativeFragmentActivity : AppCompatActivity(), DefaultHardwareBackBtnH
}
return handled || super.onKeyUp(keyCode, event)
}

override fun onBackPressed() {
val activeFragment = supportFragmentManager.findFragmentById(R.id.container_main)
if (activeFragment is ReactNativeFragment) {
activeFragment.onBackPressed(this)
} else {
super.onBackPressed()
}
}
}
Loading