工程实践

React Native 踩坑实录

by Cheng, 2024-06-03


Android 上架应用商店

由于隐私问题被拒绝

国内某些应用商店会提示 「非服务所必需,存在若干SDK未经用户许可读取个人隐私信息(ANDROID-ID)」。

究其原始,是 expo-application 在获取 ANDROID_ID

// node_modules/expo-application/src/Application.ts
export const applicationId: string | null = ExpoApplication
  ? ExpoApplication.applicationId || null
  : null;
// node_modules/expo-application/android/src/main/java/expo/modules/application/ApplicationModule.kt
constants["androidId"] = Settings.Secure.getString(mContext.contentResolver, Settings.Secure.ANDROID_ID)

解决方案

用固定的数值替换掉获取 ANDROID_ID 的代码即可。

Modal 中的 toast 层级会被 Modal 覆盖

toast_masked_by_modal.png

使用 react-native-root-siblings 包裹 Modal 的内容

<Modal
  isVisible={show}
  onBackdropPress={() => {
    setShow(false);
  }}
  onBackButtonPress={() => {
    setShow(false);
  }}
  onModalHide={() => {
    setAddNewProp(false);

    // setVisible(true);
  }}
  animationIn={"slideInUp"}
  animationOut={"slideOutDown"}
  className={"m-0 justify-end"}
  useNativeDriverForBackdrop={false}
  swipeDirection={["down"]}
  onSwipeComplete={() => {
    setShow(false);
  }}
  onModalShow={() => textInputRef.current?.focus()}
  avoidKeyboard={true}
>
 <RootSiblingParent>
   <View className={"h-full w-full flex-row items-center justify-start"}>
   </View>
 </RootSiblingParent>
</Modal>

TextInput 即使设置了 textAlign="center" 游标也会在最后边

input_caret_issue.png

添加 numberOfLinesmultiline 属性

<TextInput
  ref={textInputRef}
  className={"flex-1"}
  placeholder={"请输入"}
  value={text}
  onChangeText={setText}
  textAlign={"center"}
  numberOfLines={1}
  multiline
/>

页面中同时存在多个 Modal 会在 iOS 中卡死

这是 react-native 中的一个 bug,具体解决方案是在 ModalonModalHide 回调中显示另外的 Modal

I can't show multiple modals one after another

Unfortunately right now react-native doesn't allow multiple modals to be displayed at the same time. This means that, in react-native-modal, if you want to immediately show a new modal after closing one you must first make sure that the modal that your closing has completed its hiding animation by using the onModalHide prop.

I can't show multiple modals at the same time

See the question above. Showing multiple modals (or even alerts/dialogs) at the same time is not doable because of a react-native bug. That said, I would strongly advice against using multiple modals at the same time because, most often than not, this leads to a bad UX, especially on mobile (just my opinion).

iOS 返回应用崩溃

支付跳转到第三方引用:微信、支付宝,支付完成后,APP 闪退,错误信息为:

NSInvalidArgumentException: Application tried to present modally a view controller <RCTModalHostViewController: 0x10b693460> that is already being presented by <UIViewController: 0x109e25db0>.

这个问题是由 react-native-modal 导致的,具体原因是跳转的时候,对话框还展示着。解决方案也很明确:确保跳转的时候,对话框关闭。

const first = () => {
    setShowModal(false);

    setTimeout(() => {
      then();
    }, 1000);
  };

TextInput 类型为文本时的键盘处理

在 iOS 下,可以通过 enterKeyHint 或者 returnKeyType 来与键盘进行交互,让其展示对应的快捷按键。比如让数字键盘展示一个 Done 的按钮,让键盘缩回。
decimal-done.png

但是对于文本类型的输入框来说。同样也能控制回车键的交互,但是达不到对应的需求,比如输入完成时缩回键盘。
text-done.png

对于这种情况可以通过 inputAccessoryViewID 属性配合 InputAccessoryView 使用,对整个键盘进行处理。

<TextInput inputAccessoryViewID="Done" />
<InputAccessoryView nativeID="Done">
  <View className={"flex-row items-center bg-white py-3 px-6 border-t border-t-[#F3F3F3]"}>
    <View className="flex-1" />
    <TouchableOpacity
      onPress={Keyboard.dismiss}
    >
      <Text className={"text-base font-medium text-[#4E83E4]"}>完成</Text>
    </TouchableOpacity>
  </View>
</InputAccessoryView>

text-accessory-view.png

decimal-accessory-view.png

Unable to load script.Make sure you are either running a Metro server or that your bundle 'index.android.bundle' is packaged correctly for release

2024-06-09T16:45:08.png

确保 Android Emulator 里边的网络连接正常!

打包时提示 error: style attribute 'attr/*** not found

* What went wrong:
Execution failed for task ':xxx:verifyReleaseResources'.
> A failure occurred while executing com.android.build.gradle.tasks.VerifyLibraryResourcesTask$Action
   > Android resource linking failed
     ERROR: /path/to/project/node_modules/xxx/android/build/intermediates/merged_res/release/values/values.xml:1587: AAPT: error: style attribute 'attr/statusBarBackground (aka com.xxx:attr/statusBarBackground)' not found.

在项目对应的 src/main/res/values/attrs.xml 添加对应项即可。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="statusBarBackground" format="reference|color"/>
</resources>

资源引用

有些时候需要引入其他资源,对于这些资源文件,通常只需要使用其内容即可。对于这些资源,直接引用会报错。要正确获取其内容,需要执行以下步骤。

确保文件能被打包资源识别

通常 React Native 项目会使用 ~Metro bundler~ 那么只需要在 metro.config.js 文件中加入对应内容即可。

const config = getDefaultConfig(__dirname);

// 需要识别的文件扩展
config.resolver.assetExts.push('ext');

module.exports = config;

引入资源文件

加入了上述配置之后,就可以像 import 其他文件一样 import f from 'file.ext'

对于 webpack 来说,使用 raw-loader 就可以获取到文件的内容了,但是 Metro 中,执行上述代码,获取的只是模块的 module id。还需要采取其它步骤。

获取文件内容

要获取文件内容需要引入额外的两个包: expo-assetexpo-file-system

安装完上面的包之后,就可以完成模块的下载和文件内容的读取了

from(Asset.loadAsync(module)).pipe(
  switchMap(([{ localUri }]) =>
    iif(
      () => localUri != null,
      defer(() =>
        from(readAsStringAsync(localUri)).pipe(
          //content is here!
          map((content) => content),
        ),
      ),
      of(''),
    ),
  ),
);

Android 打包长时间卡住

一直能正常打包的脚本,最近抽风,卡在特定的步骤

<=========----> 73% EXECUTING [10m 20s]
> IDLE
> IDLE
> IDLE
> IDLE
> :app:createBundleReleaseJsAndAssets
> IDLE
> IDLE
> IDLE

通过日志和抓包发现是在打包的时候,请求了某一服务,服务的连通性出了问题。通过 hosts 将此域名指向了 127.0.0.1 问题解决了。

Fragment 中获取 ReactContext

import androidx.fragment.app.Fragment
import com.facebook.react.ReactApplication
import com.facebook.react.ReactInstanceManager
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule

class XXXFragment : Fragment() {
    private lateinit var reactContext: ReactContext
    private lateinit var reactInstanceManager: ReactInstanceManager

    override fun onAttach(context: Context) {
        super.onAttach(context)

        reactInstanceManager =
            activity?.application?.let {
                (it as ReactApplication).reactNativeHost.reactInstanceManager
            }!!

        reactContext = reactInstanceManager.currentReactContext as ReactContext
    }

    private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap?) {
        reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
            .emit(eventName, params)
    }
}

View config getter callback for component MyNativeModule must be a function (received undefined).

一个已经开发完成的 React Native 原生模块加载的时候突然报错:View config getter callback for component MyNativeModule must be a function (received undefined). 尝试了各种方法也没有找到原因和解决方法。使用 create-react-native-library 创建了一个新的模块之后,对比了一下文件,果然发现了不同的地方。老的模块的 package.json 中比新创建的模块中的多了

"devDependencies": {
  "react": "18.2.0",
  "react-native": "0.74.3"
}

再对比了一下主项目中的 react 和 react-native 的版本,发现主项目中的 react-native 版本为0.74.5。在删除了模块 package.json 中的 devDependencies 之后,功能恢复如初。

未完待续~

React NativeiOSExpoAndroid

作者: Cheng

2025 © typecho & elise & Cheng