getTurboModule(
27 | const std::string name,
28 | const JavaTurboModule::InitParams ¶ms) override;
29 |
30 | /**
31 | * Test-only method. Allows user to verify whether a TurboModule can be
32 | * created by instances of this class.
33 | */
34 | bool canCreateTurboModule(std::string name);
35 | };
36 |
37 | } // namespace react
38 | } // namespace facebook
39 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/skillnation/examplernfbhooks/newarchitecture/components/MainComponentsRegistry.java:
--------------------------------------------------------------------------------
1 | package com.skillnation.examplernfbhooks.newarchitecture.components;
2 |
3 | import com.facebook.jni.HybridData;
4 | import com.facebook.proguard.annotations.DoNotStrip;
5 | import com.facebook.react.fabric.ComponentFactory;
6 | import com.facebook.soloader.SoLoader;
7 |
8 | /**
9 | * Class responsible to load the custom Fabric Components. This class has native methods and needs a
10 | * corresponding C++ implementation/header file to work correctly (already placed inside the jni/
11 | * folder for you).
12 | *
13 | * Please note that this class is used ONLY if you opt-in for the New Architecture (see the
14 | * `newArchEnabled` property). Is ignored otherwise.
15 | */
16 | @DoNotStrip
17 | public class MainComponentsRegistry {
18 | static {
19 | SoLoader.loadLibrary("fabricjni");
20 | }
21 |
22 | @DoNotStrip private final HybridData mHybridData;
23 |
24 | @DoNotStrip
25 | private native HybridData initHybrid(ComponentFactory componentFactory);
26 |
27 | @DoNotStrip
28 | private MainComponentsRegistry(ComponentFactory componentFactory) {
29 | mHybridData = initHybrid(componentFactory);
30 | }
31 |
32 | @DoNotStrip
33 | public static MainComponentsRegistry register(ComponentFactory componentFactory) {
34 | return new MainComponentsRegistry(componentFactory);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "start": "expo start --dev-client",
6 | "android": "expo run:android",
7 | "ios": "expo run:ios",
8 | "web": "expo start --web",
9 | "eject": "expo eject"
10 | },
11 | "dependencies": {
12 | "@react-native-firebase/app": "^14.11.1",
13 | "@react-native-firebase/auth": "^14.11.1",
14 | "@react-native-firebase/database": "^14.11.1",
15 | "@react-native-firebase/firestore": "^14.11.1",
16 | "@react-native-firebase/storage": "^14.11.1",
17 | "@react-navigation/drawer": "^6.4.2",
18 | "@react-navigation/native": "^6.0.10",
19 | "@react-navigation/native-stack": "^6.6.2",
20 | "expo": "~45.0.0",
21 | "expo-dev-client": "~1.0.0",
22 | "expo-splash-screen": "~0.15.1",
23 | "expo-status-bar": "~1.3.0",
24 | "react": "17.0.2",
25 | "react-dom": "17.0.2",
26 | "react-native": "0.68.2",
27 | "react-native-gesture-handler": "~2.2.1",
28 | "react-native-reanimated": "~2.8.0",
29 | "react-native-safe-area-context": "4.2.4",
30 | "react-native-screens": "~3.11.1",
31 | "react-native-web": "0.17.7"
32 | },
33 | "devDependencies": {
34 | "@babel/core": "^7.12.9",
35 | "@types/react": "~17.0.21",
36 | "@types/react-native": "~0.66.13",
37 | "babel-plugin-module-resolver": "^4.1.0",
38 | "typescript": "~4.3.5"
39 | },
40 | "resolutions": {
41 | "@types/react": "^17"
42 | },
43 | "private": true
44 | }
45 |
--------------------------------------------------------------------------------
/example/android/app/BUCK:
--------------------------------------------------------------------------------
1 | # To learn about Buck see [Docs](https://buckbuild.com/).
2 | # To run your application with Buck:
3 | # - install Buck
4 | # - `npm start` - to start the packager
5 | # - `cd android`
6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
8 | # - `buck install -r android/app` - compile, install and run application
9 | #
10 |
11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
12 |
13 | lib_deps = []
14 |
15 | create_aar_targets(glob(["libs/*.aar"]))
16 |
17 | create_jar_targets(glob(["libs/*.jar"]))
18 |
19 | android_library(
20 | name = "all-libs",
21 | exported_deps = lib_deps,
22 | )
23 |
24 | android_library(
25 | name = "app-code",
26 | srcs = glob([
27 | "src/main/java/**/*.java",
28 | ]),
29 | deps = [
30 | ":all-libs",
31 | ":build_config",
32 | ":res",
33 | ],
34 | )
35 |
36 | android_build_config(
37 | name = "build_config",
38 | package = "com.skillnation.examplernfbhooks",
39 | )
40 |
41 | android_resource(
42 | name = "res",
43 | package = "com.skillnation.examplernfbhooks",
44 | res = "src/main/res",
45 | )
46 |
47 | android_binary(
48 | name = "app",
49 | keystore = "//android/keystores:debug",
50 | manifest = "src/main/AndroidManifest.xml",
51 | package_type = "debug",
52 | deps = [
53 | ":app-code",
54 | ],
55 | )
56 |
--------------------------------------------------------------------------------
/src/auth/useCreateUserWithEmailAndPassword.ts:
--------------------------------------------------------------------------------
1 | import { useState, useMemo, useCallback } from 'react';
2 | import type { CreateUserOptions, EmailAndPasswordActionHook } from './types';
3 | import type { AuthError, FirebaseModuleAuth } from '../util/types';
4 | import type { FirebaseAuthTypes } from '@react-native-firebase/auth';
5 |
6 | export default (
7 | auth: FirebaseModuleAuth,
8 | options?: CreateUserOptions
9 | ): EmailAndPasswordActionHook => {
10 | const [error, setError] = useState();
11 | const [registeredUser, setRegisteredUser] =
12 | useState();
13 | const [loading, setLoading] = useState(false);
14 |
15 | const createUserWithEmailAndPassword = useCallback(
16 | async (email: string, password: string) => {
17 | setLoading(true);
18 | try {
19 | const user = await auth().createUserWithEmailAndPassword(
20 | email,
21 | password
22 | );
23 | if (options && options.sendEmailVerification && user.user) {
24 | await user.user.sendEmailVerification(
25 | options.emailVerificationOptions
26 | );
27 | }
28 | setRegisteredUser(user);
29 | } catch (_error) {
30 | setError(_error as AuthError);
31 | } finally {
32 | setLoading(false);
33 | }
34 | },
35 | [auth, options]
36 | );
37 |
38 | return useMemo(
39 | () => [createUserWithEmailAndPassword, registeredUser, loading, error],
40 | [createUserWithEmailAndPassword, error, loading, registeredUser]
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/example/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp:
--------------------------------------------------------------------------------
1 | #include "MainApplicationTurboModuleManagerDelegate.h"
2 | #include "MainApplicationModuleProvider.h"
3 |
4 | namespace facebook {
5 | namespace react {
6 |
7 | jni::local_ref
8 | MainApplicationTurboModuleManagerDelegate::initHybrid(
9 | jni::alias_ref) {
10 | return makeCxxInstance();
11 | }
12 |
13 | void MainApplicationTurboModuleManagerDelegate::registerNatives() {
14 | registerHybrid({
15 | makeNativeMethod(
16 | "initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid),
17 | makeNativeMethod(
18 | "canCreateTurboModule",
19 | MainApplicationTurboModuleManagerDelegate::canCreateTurboModule),
20 | });
21 | }
22 |
23 | std::shared_ptr
24 | MainApplicationTurboModuleManagerDelegate::getTurboModule(
25 | const std::string name,
26 | const std::shared_ptr jsInvoker) {
27 | // Not implemented yet: provide pure-C++ NativeModules here.
28 | return nullptr;
29 | }
30 |
31 | std::shared_ptr
32 | MainApplicationTurboModuleManagerDelegate::getTurboModule(
33 | const std::string name,
34 | const JavaTurboModule::InitParams ¶ms) {
35 | return MainApplicationModuleProvider(name, params);
36 | }
37 |
38 | bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule(
39 | std::string name) {
40 | return getTurboModule(name, nullptr) != nullptr ||
41 | getTurboModule(name, {.moduleName = name}) != nullptr;
42 | }
43 |
44 | } // namespace react
45 | } // namespace facebook
46 |
--------------------------------------------------------------------------------
/example/android/app/src/main/jni/Android.mk:
--------------------------------------------------------------------------------
1 | THIS_DIR := $(call my-dir)
2 |
3 | include $(REACT_ANDROID_DIR)/Android-prebuilt.mk
4 |
5 | # If you wish to add a custom TurboModule or Fabric component in your app you
6 | # will have to include the following autogenerated makefile.
7 | # include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk
8 | include $(CLEAR_VARS)
9 |
10 | LOCAL_PATH := $(THIS_DIR)
11 |
12 | # You can customize the name of your application .so file here.
13 | LOCAL_MODULE := example_appmodules
14 |
15 | LOCAL_C_INCLUDES := $(LOCAL_PATH)
16 | LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
17 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
18 |
19 | # If you wish to add a custom TurboModule or Fabric component in your app you
20 | # will have to uncomment those lines to include the generated source
21 | # files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni)
22 | #
23 | # LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
24 | # LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp)
25 | # LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
26 |
27 | # Here you should add any native library you wish to depend on.
28 | LOCAL_SHARED_LIBRARIES := \
29 | libfabricjni \
30 | libfbjni \
31 | libfolly_futures \
32 | libfolly_json \
33 | libglog \
34 | libjsi \
35 | libreact_codegen_rncore \
36 | libreact_debug \
37 | libreact_nativemodule_core \
38 | libreact_render_componentregistry \
39 | libreact_render_core \
40 | libreact_render_debug \
41 | libreact_render_graphics \
42 | librrc_view \
43 | libruntimeexecutor \
44 | libturbomodulejsijni \
45 | libyoga
46 |
47 | LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall
48 |
49 | include $(BUILD_SHARED_LIBRARY)
50 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
2 | require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
3 | require File.join(File.dirname(`node --print "require.resolve('@react-native-community/cli-platform-ios/package.json')"`), "native_modules")
4 |
5 | require 'json'
6 | podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
7 |
8 | platform :ios, podfile_properties['ios.deploymentTarget'] || '12.0'
9 | install! 'cocoapods',
10 | :deterministic_uuids => false
11 |
12 | target 'example' do
13 | use_expo_modules!
14 | config = use_native_modules!
15 |
16 | use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
17 |
18 | # Flags change depending on the env values.
19 | flags = get_default_flags()
20 |
21 | use_react_native!(
22 | :path => config[:reactNativePath],
23 | :hermes_enabled => flags[:hermes_enabled] || podfile_properties['expo.jsEngine'] == 'hermes',
24 | :fabric_enabled => flags[:fabric_enabled],
25 | # An absolute path to your application root.
26 | :app_path => "#{Dir.pwd}/.."
27 | )
28 |
29 | # Uncomment to opt-in to using Flipper
30 | # Note that if you have use_frameworks! enabled, Flipper will not work
31 | #
32 | # if !ENV['CI']
33 | # use_flipper!()
34 | # end
35 |
36 | post_install do |installer|
37 | react_native_post_install(installer)
38 | __apply_Xcode_12_5_M1_post_install_workaround(installer)
39 | end
40 |
41 | post_integrate do |installer|
42 | begin
43 | expo_patch_react_imports!(installer)
44 | rescue => e
45 | Pod::UI.warn e
46 | end
47 | end
48 |
49 | end
50 |
--------------------------------------------------------------------------------
/src/database/useObject.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo } from 'react';
2 | import { snapshotToData, ValOptions } from './helpers';
3 | import type { ObjectHook, ObjectValHook, Val } from './types';
4 | import { useIsEqualRef, useLoadingValue } from '../util';
5 | import type { FirebaseDatabaseTypes } from '@react-native-firebase/database';
6 |
7 | export const useObject = (
8 | query?: FirebaseDatabaseTypes.Query | null
9 | ): ObjectHook => {
10 | const { error, loading, reset, setError, setValue, value } = useLoadingValue<
11 | FirebaseDatabaseTypes.DataSnapshot,
12 | Error
13 | >();
14 | const ref = useIsEqualRef(query, reset);
15 |
16 | useEffect(() => {
17 | const _query = ref.current;
18 | if (!_query) {
19 | setValue(undefined);
20 | return;
21 | }
22 |
23 | _query.on('value', setValue, setError);
24 |
25 | return () => {
26 | _query.off('value', setValue);
27 | };
28 | }, [ref, setError, setValue]);
29 |
30 | return useMemo(
31 | () => [value, loading, error],
32 | [value, loading, error]
33 | );
34 | };
35 |
36 | export const useObjectVal = <
37 | T,
38 | KeyField extends string = '',
39 | RefField extends string = ''
40 | >(
41 | query?: FirebaseDatabaseTypes.Query | null,
42 | options?: ValOptions
43 | ): ObjectValHook => {
44 | const keyField = options ? options.keyField : undefined;
45 | const refField = options ? options.refField : undefined;
46 | const transform = options ? options.transform : undefined;
47 | const [snapshot, loading, error] = useObject(query);
48 | const value = useMemo(
49 | () =>
50 | (snapshot
51 | ? snapshotToData(snapshot, keyField, refField, transform)
52 | : undefined) as Val,
53 | [snapshot, keyField, refField, transform]
54 | );
55 |
56 | return useMemo>(
57 | () => [value, loading, error],
58 | [value, loading, error]
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/docs/storage.md:
--------------------------------------------------------------------------------
1 | # React Firebase Hooks - Cloud Storage
2 |
3 | React Firebase Hooks provides convenience listeners for files stored within
4 | Firebase Cloud Storage. The hooks wrap around the `getDownloadURL(...)` method.
5 |
6 | In addition to returning the download URL, the hooks provide an `error` and `loading` property
7 | to give a complete lifecycle for loading from Cloud Storage.
8 |
9 | All hooks can be imported from `react-firebase-hooks/storage`, e.g.
10 |
11 | ```js
12 | import { useDownloadURL } from 'react-firebase-hooks/storage';
13 | ```
14 |
15 | List of Cloud Storage hooks:
16 |
17 | - [useDownloadURL](#usedownloadurl)
18 |
19 | ### useDownloadURL
20 |
21 | ```js
22 | const [downloadUrl, loading, error] = useDownloadURL(reference);
23 | ```
24 |
25 | Retrieve the download URL for a storage reference.
26 |
27 | The `useDownloadURL` hook takes the following parameters:
28 |
29 | - `reference`: (optional) `storage.Reference` that you would like the download URL for
30 |
31 | Returns:
32 |
33 | - `downloadUrl`: A `string` download URL, or `undefined` if no storage reference is supplied
34 | - `loading`: A `boolean` to indicate whether the the download URL is still being loaded
35 | - `error`: Any `storage.StorageError` returned by Firebase when trying to load the user, or `undefined` if there is no error
36 |
37 | #### Full example
38 |
39 | ```js
40 | import { ref, getStorage } from 'firebase/storage';
41 | import { useDownloadURL } from 'react-firebase-hooks/storage';
42 |
43 | const storage = getStorage(firebaseApp);
44 |
45 | const DownloadURL = () => {
46 | const [value, loading, error] = useDownloadURL(
47 | ref(storage, 'path/to/file')
48 | );
49 |
50 | return (
51 |
52 |
53 | {error && Error: {error}}
54 | {loading && Download URL: Loading...}
55 | {!loading && value && (
56 |
57 | Download URL: {value}
58 |
59 | )}
60 |
61 |
62 | );
63 | };
64 | ```
65 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/rn_edit_text_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/skillnation/examplernfbhooks/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.skillnation.examplernfbhooks;
2 |
3 | import android.os.Build;
4 | import android.os.Bundle;
5 |
6 | import com.facebook.react.ReactActivity;
7 | import com.facebook.react.ReactActivityDelegate;
8 | import com.facebook.react.ReactRootView;
9 |
10 | import expo.modules.ReactActivityDelegateWrapper;
11 |
12 | public class MainActivity extends ReactActivity {
13 | @Override
14 | protected void onCreate(Bundle savedInstanceState) {
15 | // Set the theme to AppTheme BEFORE onCreate to support
16 | // coloring the background, status bar, and navigation bar.
17 | // This is required for expo-splash-screen.
18 | setTheme(R.style.AppTheme);
19 | super.onCreate(null);
20 | }
21 |
22 | /**
23 | * Returns the name of the main component registered from JavaScript.
24 | * This is used to schedule rendering of the component.
25 | */
26 | @Override
27 | protected String getMainComponentName() {
28 | return "main";
29 | }
30 |
31 | @Override
32 | protected ReactActivityDelegate createReactActivityDelegate() {
33 | return new ReactActivityDelegateWrapper(this,
34 | new ReactActivityDelegate(this, getMainComponentName())
35 | );
36 | }
37 |
38 | /**
39 | * Align the back button behavior with Android S
40 | * where moving root activities to background instead of finishing activities.
41 | * @see onBackPressed
42 | */
43 | @Override
44 | public void invokeDefaultOnBackPressed() {
45 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
46 | if (!moveTaskToBack(false)) {
47 | // For non-root activities, use the default implementation to finish them.
48 | super.invokeDefaultOnBackPressed();
49 | }
50 | return;
51 | }
52 |
53 | // Use the default back button implementation on Android S
54 | // because it's doing more than {@link Activity#moveTaskToBack} in fact.
55 | super.invokeDefaultOnBackPressed();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/skillnation/examplernfbhooks/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java:
--------------------------------------------------------------------------------
1 | package com.skillnation.examplernfbhooks.newarchitecture.modules;
2 |
3 | import com.facebook.jni.HybridData;
4 | import com.facebook.react.ReactPackage;
5 | import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.soloader.SoLoader;
8 | import java.util.List;
9 |
10 | /**
11 | * Class responsible to load the TurboModules. This class has native methods and needs a
12 | * corresponding C++ implementation/header file to work correctly (already placed inside the jni/
13 | * folder for you).
14 | *
15 | * Please note that this class is used ONLY if you opt-in for the New Architecture (see the
16 | * `newArchEnabled` property). Is ignored otherwise.
17 | */
18 | public class MainApplicationTurboModuleManagerDelegate
19 | extends ReactPackageTurboModuleManagerDelegate {
20 |
21 | private static volatile boolean sIsSoLibraryLoaded;
22 |
23 | protected MainApplicationTurboModuleManagerDelegate(
24 | ReactApplicationContext reactApplicationContext, List packages) {
25 | super(reactApplicationContext, packages);
26 | }
27 |
28 | protected native HybridData initHybrid();
29 |
30 | native boolean canCreateTurboModule(String moduleName);
31 |
32 | public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder {
33 | protected MainApplicationTurboModuleManagerDelegate build(
34 | ReactApplicationContext context, List packages) {
35 | return new MainApplicationTurboModuleManagerDelegate(context, packages);
36 | }
37 | }
38 |
39 | @Override
40 | protected synchronized void maybeLoadOtherSoLibraries() {
41 | if (!sIsSoLibraryLoaded) {
42 | // If you change the name of your application .so file in the Android.mk file,
43 | // make sure you update the name here as well.
44 | SoLoader.loadLibrary("example_appmodules");
45 | sIsSoLibraryLoaded = true;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/example/android/app/src/main/jni/MainComponentsRegistry.cpp:
--------------------------------------------------------------------------------
1 | #include "MainComponentsRegistry.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | namespace facebook {
9 | namespace react {
10 |
11 | MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {}
12 |
13 | std::shared_ptr
14 | MainComponentsRegistry::sharedProviderRegistry() {
15 | auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();
16 |
17 | // Custom Fabric Components go here. You can register custom
18 | // components coming from your App or from 3rd party libraries here.
19 | //
20 | // providerRegistry->add(concreteComponentDescriptorProvider<
21 | // AocViewerComponentDescriptor>());
22 | return providerRegistry;
23 | }
24 |
25 | jni::local_ref
26 | MainComponentsRegistry::initHybrid(
27 | jni::alias_ref,
28 | ComponentFactory *delegate) {
29 | auto instance = makeCxxInstance(delegate);
30 |
31 | auto buildRegistryFunction =
32 | [](EventDispatcher::Weak const &eventDispatcher,
33 | ContextContainer::Shared const &contextContainer)
34 | -> ComponentDescriptorRegistry::Shared {
35 | auto registry = MainComponentsRegistry::sharedProviderRegistry()
36 | ->createComponentDescriptorRegistry(
37 | {eventDispatcher, contextContainer});
38 |
39 | auto mutableRegistry =
40 | std::const_pointer_cast(registry);
41 |
42 | mutableRegistry->setFallbackComponentDescriptor(
43 | std::make_shared(
44 | ComponentDescriptorParameters{
45 | eventDispatcher, contextContainer, nullptr}));
46 |
47 | return registry;
48 | };
49 |
50 | delegate->buildRegistryFunction = buildRegistryFunction;
51 | return instance;
52 | }
53 |
54 | void MainComponentsRegistry::registerNatives() {
55 | registerHybrid({
56 | makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid),
57 | });
58 | }
59 |
60 | } // namespace react
61 | } // namespace facebook
62 |
--------------------------------------------------------------------------------
/src/firestore/useInternal.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo } from 'react';
2 | import { useIsEqualRef, useLoadingValue } from '../util';
3 | import type { CollectionHook, DocumentHook, Options } from './types';
4 | import type { FirebaseFirestoreTypes } from '@react-native-firebase/firestore';
5 |
6 | /**
7 | * Common abstraction for useDocument and useCollection.
8 | * It's not recommended using it directly, please use the documented hooks.
9 | * @param query
10 | * @param options
11 | */
12 | export const useInternal = <
13 | T = FirebaseFirestoreTypes.DocumentData,
14 | ReturnTypeHook extends CollectionHook | DocumentHook =
15 | | CollectionHook
16 | | DocumentHook,
17 | ValueType extends
18 | | FirebaseFirestoreTypes.QuerySnapshot
19 | | FirebaseFirestoreTypes.DocumentSnapshot =
20 | | FirebaseFirestoreTypes.QuerySnapshot
21 | | FirebaseFirestoreTypes.DocumentSnapshot,
22 | QueryType extends
23 | | FirebaseFirestoreTypes.Query
24 | | FirebaseFirestoreTypes.DocumentReference =
25 | | FirebaseFirestoreTypes.Query
26 | | FirebaseFirestoreTypes.DocumentReference
27 | >(
28 | query?: QueryType | null,
29 | options?: Options
30 | ) => {
31 | const { error, loading, reset, setError, setValue, value } = useLoadingValue<
32 | ValueType,
33 | Error
34 | >();
35 | const ref = useIsEqualRef(query, reset);
36 |
37 | useEffect(() => {
38 | if (!ref.current) {
39 | setValue(undefined);
40 | return;
41 | }
42 |
43 | const unsubscribe =
44 | options && options.snapshotListenOptions
45 | ? // @ts-expect-error Types are badly incompatible
46 | ref.current.onSnapshot(
47 | options.snapshotListenOptions,
48 | setValue,
49 | setError
50 | )
51 | : // @ts-expect-error Types are badly incompatible
52 | ref.current.onSnapshot(setValue, setError);
53 |
54 | return () => {
55 | unsubscribe();
56 | };
57 | // we need to use ref.current here explicitly
58 | // eslint-disable-next-line react-hooks/exhaustive-deps
59 | }, [ref.current, setError, setValue]);
60 |
61 | return useMemo(
62 | // @ts-expect-error No clue how to make that work with TS
63 | () => [value, loading, error],
64 | [value, loading, error]
65 | );
66 | };
67 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 |
25 | # Automatically convert third-party libraries to use AndroidX
26 | android.enableJetifier=true
27 |
28 | # Version of flipper SDK to use with React Native
29 | FLIPPER_VERSION=0.125.0
30 |
31 | # Use this property to specify which architecture you want to build.
32 | # You can also override it from the CLI using
33 | # ./gradlew -PreactNativeArchitectures=x86_64
34 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
35 |
36 | # Use this property to enable support to the new architecture.
37 | # This will allow you to use TurboModules and the Fabric render in
38 | # your application. You should enable this flag either if you want
39 | # to write custom TurboModules/Fabric components OR use libraries that
40 | # are providing them.
41 | newArchEnabled=false
42 |
43 | # The hosted JavaScript engine
44 | # Supported values: expo.jsEngine = "hermes" | "jsc"
45 | expo.jsEngine=jsc
46 |
47 | # Enable GIF support in React Native images (~200 B increase)
48 | expo.gif.enabled=true
49 | # Enable webp support in React Native images (~85 KB increase)
50 | expo.webp.enabled=true
51 | # Enable animated webp support (~3.4 MB increase)
52 | # Disabled by default because iOS doesn't support animated webp
53 | expo.webp.animated=false
54 |
--------------------------------------------------------------------------------
/src/util/useLoadingValue.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo, useReducer } from 'react';
2 |
3 | export type LoadingValue = {
4 | error?: E;
5 | loading: boolean;
6 | reset: () => void;
7 | setError: (error: E) => void;
8 | setValue: (value?: T) => void;
9 | value?: T;
10 | };
11 |
12 | type ReducerState = {
13 | error?: E;
14 | loading: boolean;
15 | value?: any;
16 | };
17 |
18 | type ErrorAction = { type: 'error'; error: E };
19 | type ResetAction = { type: 'reset'; defaultValue?: any };
20 | type ValueAction = { type: 'value'; value: any };
21 | type ReducerAction = ErrorAction | ResetAction | ValueAction;
22 |
23 | const defaultState = (defaultValue?: any) => {
24 | return {
25 | loading: defaultValue === undefined || defaultValue === null,
26 | value: defaultValue,
27 | };
28 | };
29 |
30 | const reducer =
31 | () =>
32 | (state: ReducerState, action: ReducerAction): ReducerState => {
33 | switch (action.type) {
34 | case 'error':
35 | return {
36 | ...state,
37 | error: action.error,
38 | loading: false,
39 | value: undefined,
40 | };
41 | case 'reset':
42 | return defaultState(action.defaultValue);
43 | case 'value':
44 | return {
45 | ...state,
46 | error: undefined,
47 | loading: false,
48 | value: action.value,
49 | };
50 | default:
51 | return state;
52 | }
53 | };
54 |
55 | export default (getDefaultValue?: () => T): LoadingValue => {
56 | const defaultValue = getDefaultValue ? getDefaultValue() : undefined;
57 | const [state, dispatch] = useReducer(
58 | reducer(),
59 | defaultState(defaultValue)
60 | );
61 |
62 | const reset = useCallback(() => {
63 | const _defaultValue = getDefaultValue ? getDefaultValue() : undefined;
64 | dispatch({ type: 'reset', defaultValue: _defaultValue });
65 | }, [getDefaultValue]);
66 |
67 | const setError = useCallback((error: E) => {
68 | dispatch({ type: 'error', error });
69 | }, []);
70 |
71 | const setValue = useCallback((value?: T) => {
72 | dispatch({ type: 'value', value });
73 | }, []);
74 |
75 | return useMemo(
76 | () => ({
77 | error: state.error,
78 | loading: state.loading,
79 | reset,
80 | setError,
81 | setValue,
82 | value: state.value,
83 | }),
84 | [state.error, state.loading, reset, setError, setValue, state.value]
85 | );
86 | };
87 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | executors:
4 | default:
5 | docker:
6 | - image: circleci/node:10
7 | working_directory: ~/project
8 |
9 | commands:
10 | attach_project:
11 | steps:
12 | - attach_workspace:
13 | at: ~/project
14 |
15 | jobs:
16 | install-dependencies:
17 | executor: default
18 | steps:
19 | - checkout
20 | - attach_project
21 | - restore_cache:
22 | keys:
23 | - dependencies-{{ checksum "package.json" }}
24 | - dependencies-
25 | - restore_cache:
26 | keys:
27 | - dependencies-example-{{ checksum "example/package.json" }}
28 | - dependencies-example-
29 | - run:
30 | name: Install dependencies
31 | command: |
32 | yarn install --cwd example --frozen-lockfile
33 | yarn install --frozen-lockfile
34 | - save_cache:
35 | key: dependencies-{{ checksum "package.json" }}
36 | paths: node_modules
37 | - save_cache:
38 | key: dependencies-example-{{ checksum "example/package.json" }}
39 | paths: example/node_modules
40 | - persist_to_workspace:
41 | root: .
42 | paths: .
43 |
44 | lint:
45 | executor: default
46 | steps:
47 | - attach_project
48 | - run:
49 | name: Lint files
50 | command: |
51 | yarn lint
52 |
53 | typescript:
54 | executor: default
55 | steps:
56 | - attach_project
57 | - run:
58 | name: Typecheck files
59 | command: |
60 | yarn typescript
61 |
62 | unit-tests:
63 | executor: default
64 | steps:
65 | - attach_project
66 | - run:
67 | name: Run unit tests
68 | command: |
69 | yarn test --coverage
70 | - store_artifacts:
71 | path: coverage
72 | destination: coverage
73 |
74 | build-package:
75 | executor: default
76 | steps:
77 | - attach_project
78 | - run:
79 | name: Build package
80 | command: |
81 | yarn prepare
82 |
83 | workflows:
84 | build-and-test:
85 | jobs:
86 | - install-dependencies
87 | - lint:
88 | requires:
89 | - install-dependencies
90 | - typescript:
91 | requires:
92 | - install-dependencies
93 | - unit-tests:
94 | requires:
95 | - install-dependencies
96 | - build-package:
97 | requires:
98 | - install-dependencies
99 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | import org.apache.tools.ant.taskdefs.condition.Os
2 |
3 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
4 |
5 | buildscript {
6 | ext {
7 | buildToolsVersion = findProperty('android.buildToolsVersion') ?: '31.0.0'
8 | minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '21')
9 | compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '31')
10 | targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '31')
11 | if (findProperty('android.kotlinVersion')) {
12 | kotlinVersion = findProperty('android.kotlinVersion')
13 | }
14 | frescoVersion = findProperty('expo.frescoVersion') ?: '2.5.0'
15 |
16 | if (System.properties['os.arch'] == 'aarch64') {
17 | // For M1 Users we need to use the NDK 24 which added support for aarch64
18 | ndkVersion = '24.0.8215888'
19 | } else {
20 | // Otherwise we default to the side-by-side NDK version from AGP.
21 | ndkVersion = '21.4.7075529'
22 | }
23 | }
24 | repositories {
25 | google()
26 | mavenCentral()
27 | }
28 | dependencies {
29 | classpath 'com.google.gms:google-services:4.3.3'
30 | classpath('com.android.tools.build:gradle:7.0.4')
31 | classpath('com.facebook.react:react-native-gradle-plugin')
32 | classpath('de.undercouch:gradle-download-task:4.1.2')
33 | // NOTE: Do not place your application dependencies here; they belong
34 | // in the individual module build.gradle files
35 | }
36 | }
37 |
38 | allprojects {
39 | repositories {
40 | mavenLocal()
41 | maven {
42 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
43 | url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android'))
44 | }
45 | maven {
46 | // Android JSC is installed from npm
47 | url(new File(['node', '--print', "require.resolve('jsc-android/package.json')"].execute(null, rootDir).text.trim(), '../dist'))
48 | }
49 |
50 | google()
51 | mavenCentral {
52 | // We don't want to fetch react-native from Maven Central as there are
53 | // older versions over there.
54 | content {
55 | excludeGroup 'com.facebook.react'
56 | }
57 | }
58 | maven { url 'https://www.jitpack.io' }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/example/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavigationContainer } from '@react-navigation/native';
3 | import { createDrawerNavigator } from '@react-navigation/drawer';
4 | import ChangeQuery_useCollectionData from './src/ChangeQuery_useCollectionData';
5 | import { Basic_useDocumentData } from './src/Basic_useDocumentData';
6 | import { Basic_useDocument } from './src/Basic_useDocument';
7 | import { Basic_useDocumentOnce } from './src/Basic_useDocumentOnce';
8 | import { Basic_useDocumentDataOnce } from './src/Basic_useDocumentDataOnce';
9 | import { Basic_useCollection } from './src/Basic_useCollection';
10 | import { Basic_useCollectionData } from './src/Basic_useCollectionData';
11 | import { Basic_useCollectionOnce } from './src/Basic_useCollectionOnce';
12 | import { Basic_useCollectionDataOnce } from './src/Basic_useCollectionDataOnce';
13 | import { ChangeQuery_useDocumentDataOnce } from './src/ChangeQuery_useDocumentDataOnce';
14 | import { ChangeQuery_useDocumentData } from './src/ChangeQuery_useDocumentData';
15 |
16 | const Drawer = createDrawerNavigator();
17 |
18 | export default function App() {
19 | return (
20 |
21 |
22 |
26 |
30 |
34 |
38 |
42 |
46 |
50 |
54 |
58 |
62 |
66 |
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/firestore/useInternalOnce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo } from 'react';
2 | import { useIsEqualRef, useLoadingValue } from '../util';
3 | import type {
4 | CollectionOnceHook,
5 | DocumentOnceHook,
6 | OnceOptions,
7 | Options,
8 | } from './types';
9 | import type { FirebaseFirestoreTypes } from '@react-native-firebase/firestore';
10 |
11 | /**
12 | * Common abstraction for useDocument*Once and useCollection*Once.
13 | * It's not recommended using it directly, please use the documented hooks.
14 | * @param query
15 | * @param options
16 | */
17 | export const useInternalOnce = <
18 | T = FirebaseFirestoreTypes.DocumentData,
19 | ReturnTypeHook extends CollectionOnceHook | DocumentOnceHook =
20 | | CollectionOnceHook
21 | | DocumentOnceHook,
22 | ValueType extends
23 | | FirebaseFirestoreTypes.QuerySnapshot
24 | | FirebaseFirestoreTypes.DocumentSnapshot =
25 | | FirebaseFirestoreTypes.QuerySnapshot
26 | | FirebaseFirestoreTypes.DocumentSnapshot,
27 | QueryType extends
28 | | FirebaseFirestoreTypes.Query
29 | | FirebaseFirestoreTypes.DocumentReference =
30 | | FirebaseFirestoreTypes.Query
31 | | FirebaseFirestoreTypes.DocumentReference
32 | >(
33 | query?: QueryType | null,
34 | options?: Options & OnceOptions
35 | ) => {
36 | const { error, loading, reset, setError, setValue, value } = useLoadingValue<
37 | ValueType,
38 | Error
39 | >();
40 | let effectActive = true;
41 | const ref = useIsEqualRef(query, reset);
42 |
43 | const loadData = async (
44 | queryArg?: QueryType | null,
45 | optionsArg?: Options & OnceOptions
46 | ) => {
47 | if (!queryArg) {
48 | setValue(undefined);
49 | return;
50 | }
51 |
52 | const getOptionsSource = optionsArg?.getOptions?.source;
53 | try {
54 | const result = await queryArg.get(
55 | getOptionsSource != null
56 | ? {
57 | source: getOptionsSource,
58 | }
59 | : undefined
60 | );
61 | if (effectActive) {
62 | setValue(result as ValueType); // TODO: can we improve on this as cast?
63 | }
64 | } catch (e) {
65 | if (effectActive) {
66 | setError(e as Error);
67 | }
68 | }
69 | };
70 |
71 | useEffect(() => {
72 | loadData(ref.current, options);
73 |
74 | return () => {
75 | // eslint-disable-next-line react-hooks/exhaustive-deps
76 | effectActive = false;
77 | };
78 | }, [ref.current]);
79 |
80 | const resArray = [
81 | value,
82 | loading,
83 | error,
84 | () => loadData(ref.current, options),
85 | ];
86 | // @ts-expect-error No idea how to make that work in TS
87 | // eslint-disable-next-line react-hooks/exhaustive-deps
88 | return useMemo(() => resArray, resArray);
89 | };
90 |
--------------------------------------------------------------------------------
/src/firestore/useCollection.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { snapshotToData } from './helpers';
3 | import type {
4 | CollectionDataHook,
5 | CollectionDataOnceHook,
6 | CollectionHook,
7 | CollectionOnceHook,
8 | Data,
9 | DataOptions,
10 | OnceDataOptions,
11 | OnceOptions,
12 | Options,
13 | } from './types';
14 | import type { FirebaseFirestoreTypes } from '@react-native-firebase/firestore';
15 | import { useInternalOnce } from './useInternalOnce';
16 | import { useInternal } from './useInternal';
17 |
18 | export const useCollection = (
19 | query?: FirebaseFirestoreTypes.Query | null,
20 | options?: Options
21 | ) => useInternal>(query, options);
22 |
23 | export const useCollectionOnce = (
24 | query?: FirebaseFirestoreTypes.Query | null,
25 | options?: Options & OnceOptions
26 | ) => useInternalOnce>(query, options);
27 |
28 | export const useCollectionData = <
29 | T = FirebaseFirestoreTypes.DocumentData,
30 | IDField extends string | undefined = undefined,
31 | RefField extends string | undefined = undefined
32 | >(
33 | query?: FirebaseFirestoreTypes.Query | null,
34 | options?: DataOptions
35 | ): CollectionDataHook => {
36 | const [snapshots, loading, error] = useCollection(query, options);
37 | const values = useGetValuesFromSnapshots(snapshots, options);
38 |
39 | return useMemo>(
40 | () => [values, loading, error],
41 | [values, loading, error]
42 | );
43 | };
44 |
45 | export const useCollectionDataOnce = <
46 | T = FirebaseFirestoreTypes.DocumentData,
47 | IDField extends string | undefined = undefined,
48 | RefField extends string | undefined = undefined
49 | >(
50 | query?: FirebaseFirestoreTypes.Query | null,
51 | options?: DataOptions &
52 | OnceDataOptions
53 | ) => {
54 | const [snapshots, loading, error] = useCollectionOnce(query, options);
55 | const values = useGetValuesFromSnapshots(snapshots, options);
56 |
57 | return useMemo>(
58 | () => [values, loading, error],
59 | [values, loading, error]
60 | );
61 | };
62 |
63 | const useGetValuesFromSnapshots = <
64 | T,
65 | IDField extends string | undefined = undefined,
66 | RefField extends string | undefined = undefined
67 | >(
68 | snapshots?: FirebaseFirestoreTypes.QuerySnapshot,
69 | options?: DataOptions &
70 | OnceDataOptions
71 | ) => {
72 | return useMemo(
73 | () =>
74 | snapshots?.docs.map((doc) =>
75 | snapshotToData(
76 | doc,
77 | options?.idField,
78 | options?.refField,
79 | options?.transform
80 | )
81 | ) as Data[],
82 | [snapshots, options?.idField, options?.refField, options?.transform]
83 | );
84 | };
85 |
--------------------------------------------------------------------------------
/example/ios/example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0.0
21 | CFBundleSignature
22 | ????
23 | CFBundleURLTypes
24 |
25 |
26 | CFBundleURLSchemes
27 |
28 | com.skillnation.examplernfbhooks
29 |
30 |
31 |
32 | CFBundleURLSchemes
33 |
34 | com.googleusercontent.apps.603247362474-2rrl5gp5jtpo2tql2372sirjguva5t2n
35 |
36 |
37 |
38 | CFBundleURLSchemes
39 |
40 | exp+example
41 |
42 |
43 |
44 | CFBundleVersion
45 | 1
46 | LSRequiresIPhoneOS
47 |
48 | NSAppTransportSecurity
49 |
50 | NSAllowsArbitraryLoads
51 |
52 | NSExceptionDomains
53 |
54 | localhost
55 |
56 | NSExceptionAllowsInsecureHTTPLoads
57 |
58 |
59 |
60 |
61 | UILaunchStoryboardName
62 | SplashScreen
63 | UIRequiredDeviceCapabilities
64 |
65 | armv7
66 |
67 | UIRequiresFullScreen
68 |
69 | UIStatusBarStyle
70 | UIStatusBarStyleDefault
71 | UISupportedInterfaceOrientations
72 |
73 | UIInterfaceOrientationPortrait
74 | UIInterfaceOrientationPortraitUpsideDown
75 |
76 | UISupportedInterfaceOrientations~ipad
77 |
78 | UIInterfaceOrientationPortrait
79 | UIInterfaceOrientationPortraitUpsideDown
80 | UIInterfaceOrientationLandscapeLeft
81 | UIInterfaceOrientationLandscapeRight
82 |
83 | UIUserInterfaceStyle
84 | Light
85 | UIViewControllerBasedStatusBarAppearance
86 |
87 |
88 |
--------------------------------------------------------------------------------
/src/firestore/useDocument.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { snapshotToData } from './helpers';
3 | import type { FirebaseFirestoreTypes } from '@react-native-firebase/firestore';
4 | import type {
5 | Data,
6 | DataOptions,
7 | DocumentDataHook,
8 | DocumentDataOnceHook,
9 | DocumentHook,
10 | DocumentOnceHook,
11 | OnceOptions,
12 | Options,
13 | OnceDataOptions,
14 | } from './types';
15 | import { useInternalOnce } from './useInternalOnce';
16 | import { useInternal } from './useInternal';
17 |
18 | export const useDocument = (
19 | docRef?: FirebaseFirestoreTypes.DocumentReference | null,
20 | optionsProp?: Options
21 | ): DocumentHook => useInternal>(docRef, optionsProp);
22 |
23 | export const useDocumentOnce = (
24 | docRef?: FirebaseFirestoreTypes.DocumentReference | null,
25 | options?: Options & OnceOptions
26 | ) => useInternalOnce>(docRef, options);
27 |
28 | export const useDocumentData = <
29 | T = FirebaseFirestoreTypes.DocumentData,
30 | IDField extends string | undefined = undefined,
31 | RefField extends string | undefined = undefined
32 | >(
33 | docRef?: FirebaseFirestoreTypes.DocumentReference | null,
34 | options?: DataOptions
35 | ): DocumentDataHook => {
36 | const [snapshot, loading, error] = useDocument(docRef, options);
37 | const value = useGetValuesFromSnapshots(snapshot, options);
38 |
39 | return useMemo>(
40 | () => [value, loading, error],
41 | [value, loading, error]
42 | );
43 | };
44 |
45 | export const useDocumentDataOnce = <
46 | T = FirebaseFirestoreTypes.DocumentData,
47 | IDField extends string | undefined = undefined,
48 | RefField extends string | undefined = undefined
49 | >(
50 | docRef?: FirebaseFirestoreTypes.DocumentReference | null,
51 | options?: DataOptions &
52 | OnceDataOptions
53 | ) => {
54 | const [snapshot, loading, error] = useDocumentOnce(docRef, options);
55 | const values = useGetValuesFromSnapshots(snapshot, options);
56 |
57 | return useMemo>(
58 | () => [values, loading, error],
59 | [values, loading, error]
60 | );
61 | };
62 |
63 | const useGetValuesFromSnapshots = <
64 | T,
65 | IDField extends string | undefined = undefined,
66 | RefField extends string | undefined = undefined
67 | >(
68 | docRef?: FirebaseFirestoreTypes.DocumentSnapshot,
69 | options?: DataOptions &
70 | OnceDataOptions
71 | ) => {
72 | return useMemo(
73 | () =>
74 | (docRef
75 | ? snapshotToData(
76 | docRef,
77 | options?.idField,
78 | options?.refField,
79 | options?.transform
80 | )
81 | : undefined) as Data,
82 | [docRef, options?.idField, options?.refField, options?.transform]
83 | );
84 | };
85 |
--------------------------------------------------------------------------------
/example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "idiom": "iphone",
5 | "size": "20x20",
6 | "scale": "2x",
7 | "filename": "App-Icon-20x20@2x.png"
8 | },
9 | {
10 | "idiom": "iphone",
11 | "size": "20x20",
12 | "scale": "3x",
13 | "filename": "App-Icon-20x20@3x.png"
14 | },
15 | {
16 | "idiom": "iphone",
17 | "size": "29x29",
18 | "scale": "1x",
19 | "filename": "App-Icon-29x29@1x.png"
20 | },
21 | {
22 | "idiom": "iphone",
23 | "size": "29x29",
24 | "scale": "2x",
25 | "filename": "App-Icon-29x29@2x.png"
26 | },
27 | {
28 | "idiom": "iphone",
29 | "size": "29x29",
30 | "scale": "3x",
31 | "filename": "App-Icon-29x29@3x.png"
32 | },
33 | {
34 | "idiom": "iphone",
35 | "size": "40x40",
36 | "scale": "2x",
37 | "filename": "App-Icon-40x40@2x.png"
38 | },
39 | {
40 | "idiom": "iphone",
41 | "size": "40x40",
42 | "scale": "3x",
43 | "filename": "App-Icon-40x40@3x.png"
44 | },
45 | {
46 | "idiom": "iphone",
47 | "size": "60x60",
48 | "scale": "2x",
49 | "filename": "App-Icon-60x60@2x.png"
50 | },
51 | {
52 | "idiom": "iphone",
53 | "size": "60x60",
54 | "scale": "3x",
55 | "filename": "App-Icon-60x60@3x.png"
56 | },
57 | {
58 | "idiom": "ipad",
59 | "size": "20x20",
60 | "scale": "1x",
61 | "filename": "App-Icon-20x20@1x.png"
62 | },
63 | {
64 | "idiom": "ipad",
65 | "size": "20x20",
66 | "scale": "2x",
67 | "filename": "App-Icon-20x20@2x.png"
68 | },
69 | {
70 | "idiom": "ipad",
71 | "size": "29x29",
72 | "scale": "1x",
73 | "filename": "App-Icon-29x29@1x.png"
74 | },
75 | {
76 | "idiom": "ipad",
77 | "size": "29x29",
78 | "scale": "2x",
79 | "filename": "App-Icon-29x29@2x.png"
80 | },
81 | {
82 | "idiom": "ipad",
83 | "size": "40x40",
84 | "scale": "1x",
85 | "filename": "App-Icon-40x40@1x.png"
86 | },
87 | {
88 | "idiom": "ipad",
89 | "size": "40x40",
90 | "scale": "2x",
91 | "filename": "App-Icon-40x40@2x.png"
92 | },
93 | {
94 | "idiom": "ipad",
95 | "size": "76x76",
96 | "scale": "1x",
97 | "filename": "App-Icon-76x76@1x.png"
98 | },
99 | {
100 | "idiom": "ipad",
101 | "size": "76x76",
102 | "scale": "2x",
103 | "filename": "App-Icon-76x76@2x.png"
104 | },
105 | {
106 | "idiom": "ipad",
107 | "size": "83.5x83.5",
108 | "scale": "2x",
109 | "filename": "App-Icon-83.5x83.5@2x.png"
110 | },
111 | {
112 | "idiom": "ios-marketing",
113 | "size": "1024x1024",
114 | "scale": "1x",
115 | "filename": "ItunesArtwork@2x.png"
116 | }
117 | ],
118 | "info": {
119 | "version": 1,
120 | "author": "expo"
121 | }
122 | }
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/src/firestore/types.ts:
--------------------------------------------------------------------------------
1 | import type { LoadingHook } from '../util';
2 | import type { FirebaseFirestoreTypes } from '@react-native-firebase/firestore';
3 |
4 | export type IDOptions<
5 | T,
6 | IDField extends string | undefined = undefined,
7 | RefField extends string | undefined = undefined
8 | > = {
9 | idField?: IDField;
10 | refField?: RefField;
11 | transform?: (val: any) => T;
12 | };
13 | export type Options = {
14 | snapshotListenOptions?: FirebaseFirestoreTypes.SnapshotListenOptions;
15 | };
16 | export type DataOptions<
17 | T,
18 | IDField extends string | undefined = undefined,
19 | RefField extends string | undefined = undefined
20 | > = Options & IDOptions;
21 | export type OnceOptions = {
22 | getOptions?: GetOptions;
23 | };
24 | export type GetOptions = {
25 | source?: 'default' | 'server' | 'cache';
26 | };
27 | export type OnceDataOptions<
28 | T,
29 | IDField extends string | undefined = undefined,
30 | RefField extends string | undefined = undefined
31 | > = OnceOptions & IDOptions;
32 | export type Data<
33 | T = FirebaseFirestoreTypes.DocumentData,
34 | IDField extends string | undefined = undefined,
35 | RefField extends string | undefined = undefined,
36 | // This is the type used with IDField and RefField merged onto the type
37 | ResultType = T &
38 | (IDField extends string ? Record : {}) &
39 | (RefField extends string
40 | ? Record>
41 | : {})
42 | > = IDField extends undefined
43 | ? RefField extends undefined
44 | ? // Here IDField and RefField are both undefined, so our type is really just the input type T
45 | T
46 | : // In any other case (ID or Ref possibly null) return the result type
47 | ResultType
48 | : ResultType;
49 |
50 | export type CollectionHook =
51 | LoadingHook, Error>;
52 |
53 | export type CollectionOnceHook = [
54 | ...CollectionHook,
55 | () => Promise
56 | ];
57 |
58 | export type CollectionDataHook<
59 | T = FirebaseFirestoreTypes.DocumentData,
60 | IDField extends string | undefined = undefined,
61 | RefField extends string | undefined = undefined
62 | > = LoadingHook[], Error>;
63 |
64 | export type CollectionDataOnceHook<
65 | T = FirebaseFirestoreTypes.DocumentData,
66 | IDField extends string | undefined = undefined,
67 | RefField extends string | undefined = undefined
68 | > = [...CollectionDataHook];
69 |
70 | export type DocumentHook = LoadingHook<
71 | FirebaseFirestoreTypes.DocumentSnapshot,
72 | Error
73 | >;
74 |
75 | export type DocumentOnceHook = [
76 | ...DocumentHook,
77 | () => Promise
78 | ];
79 |
80 | export type DocumentDataHook<
81 | T = FirebaseFirestoreTypes.DocumentData,
82 | IDField extends string | undefined = undefined,
83 | RefField extends string | undefined = undefined
84 | > = LoadingHook, Error>;
85 |
86 | export type DocumentDataOnceHook<
87 | T = FirebaseFirestoreTypes.DocumentData,
88 | IDField extends string | undefined = undefined,
89 | RefField extends string | undefined = undefined
90 | > = [...DocumentDataHook];
91 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/java/com/skillnation/examplernfbhooks/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.skillnation.examplernfbhooks;
8 |
9 | import android.content.Context;
10 | import com.facebook.flipper.android.AndroidFlipperClient;
11 | import com.facebook.flipper.android.utils.FlipperUtils;
12 | import com.facebook.flipper.core.FlipperClient;
13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping;
17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
22 | import com.facebook.react.ReactInstanceManager;
23 | import com.facebook.react.bridge.ReactContext;
24 | import com.facebook.react.modules.network.NetworkingModule;
25 | import okhttp3.OkHttpClient;
26 |
27 | public class ReactNativeFlipper {
28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
29 | if (FlipperUtils.shouldEnableFlipper(context)) {
30 | final FlipperClient client = AndroidFlipperClient.getInstance(context);
31 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
32 | client.addPlugin(new ReactFlipperPlugin());
33 | client.addPlugin(new DatabasesFlipperPlugin(context));
34 | client.addPlugin(new SharedPreferencesFlipperPlugin(context));
35 | client.addPlugin(CrashReporterPlugin.getInstance());
36 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
37 | NetworkingModule.setCustomClientBuilder(
38 | new NetworkingModule.CustomClientBuilder() {
39 | @Override
40 | public void apply(OkHttpClient.Builder builder) {
41 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
42 | }
43 | });
44 | client.addPlugin(networkFlipperPlugin);
45 | client.start();
46 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
47 | // Hence we run if after all native modules have been initialized
48 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
49 | if (reactContext == null) {
50 | reactInstanceManager.addReactInstanceEventListener(
51 | new ReactInstanceManager.ReactInstanceEventListener() {
52 | @Override
53 | public void onReactContextInitialized(ReactContext reactContext) {
54 | reactInstanceManager.removeReactInstanceEventListener(this);
55 | reactContext.runOnNativeModulesQueueThread(
56 | new Runnable() {
57 | @Override
58 | public void run() {
59 | client.addPlugin(new FrescoFlipperPlugin());
60 | }
61 | });
62 | }
63 | });
64 | } else {
65 | client.addPlugin(new FrescoFlipperPlugin());
66 | }
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🔥⚓️ react-native-firebase-hooks
2 |
3 | React Hooks for React Native Firebase
4 |
5 | ## Installation
6 |
7 | ```
8 | yarn add @skillnation/react-native-firebase-hooks
9 | ```
10 |
11 | _Note:_ This assumes you have setup [react-native-firebase](https://rnfirebase.io/) in your project.
12 |
13 | ## Usage
14 |
15 |
16 | ### Example
17 |
18 | ```tsx
19 | import React from 'react';
20 | import type { Product } from './types';
21 | import firestore from '@react-native-firebase/firestore';
22 | import { useCollectionData } from '@skillnation/react-native-firebase-hooks/firestore';
23 |
24 | export const App: React.FC = () => {
25 | const [products, isLoading, error] = useCollectionData(
26 | firestore().collection('products')
27 | );
28 |
29 | if (isLoading) return
30 | if (error) return
31 | return ;
32 | };
33 |
34 | ```
35 |
36 | ### Documentation
37 |
38 | ⚠️ These doc links redirect to [`react-firebase-hooks`](https://github.com/CSFrequency/react-firebase-hooks).
39 |
40 | The API is exactly the same as [`react-firebase-hooks`](https://github.com/CSFrequency/react-firebase-hooks).
41 | Only the imports are different:
42 |
43 | ```diff
44 | - import {} from 'react-firebase-hooks/MODULE_NAME'
45 | + import {} from '@skillnation/react-native-firebase-hooks/MODULE_NAME'
46 | ```
47 |
48 | - [Authentication Hooks](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2/auth)
49 | - [Cloud Firestore Hooks](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2/firestore)
50 | - [Cloud Functions Hooks](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2/functions)
51 | - [Cloud Messaging Hooks](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2/messaging)
52 | - [Cloud Storage Hooks](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2/storage)
53 | - [Realtime Database Hooks](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2/database)
54 |
55 | ## Why use this instead of `react-firebase-hooks`?
56 |
57 | `react-firebase-hooks` is [incompatible](https://github.com/CSFrequency/react-firebase-hooks/issues/181#issue-1046383491) with the latest versions of [`react-native-firebase`](https://rnfirebase.io/), especially from a types perspective.
58 | This library works internally mostly the same as react-firebase-hooks.
59 | So you really only want to use this lib for react-native if you are using [`react-native-firebase`](https://rnfirebase.io/).
60 |
61 | ## Migrating from `react-firebase-hooks`
62 |
63 | The only thing you have to do is to change the imports:
64 |
65 | ```diff
66 | - import {} from 'react-firebase-hooks/MODULE_NAME'
67 | + import {} from '@skillnation/react-native-firebase-hooks/MODULE_NAME'
68 | ```
69 |
70 | Note however, that this library is based on [`react-firebase-hooks` v4](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2), as this version was for the Firebase JS SDK v8, which had a very similar API to the native SDKs.
71 | We **do** include features of newer versions of `react-firebase-hooks` (such as the `reload()` callback on `use*Once` hooks, which was added in v5).
72 |
73 | However, any change in newer version that is for compatibility with Firebase Js SDJ v9 won't be added to this library. This is due to the fact that the firebase JS SDK has a different API from the native SDKs, so we can't have 100% feature parity.
74 |
75 | As an example [FirestoreDataConverter](https://firebase.google.com/docs/reference/js/firestore_.firestoredataconverter) is something that just exists in the Firebase v9 JS SDK, and can't be added here.
76 |
77 | ## Contributing
78 |
79 | See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
80 |
81 | ## License
82 |
83 | MIT
84 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/skillnation/examplernfbhooks/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.skillnation.examplernfbhooks;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.content.res.Configuration;
6 | import androidx.annotation.NonNull;
7 |
8 | import com.facebook.react.PackageList;
9 | import com.facebook.react.ReactApplication;
10 | import com.facebook.react.ReactInstanceManager;
11 | import com.facebook.react.ReactNativeHost;
12 | import com.facebook.react.ReactPackage;
13 | import com.facebook.react.config.ReactFeatureFlags;
14 | import com.facebook.soloader.SoLoader;
15 | import com.skillnation.examplernfbhooks.newarchitecture.MainApplicationReactNativeHost;
16 |
17 | import expo.modules.ApplicationLifecycleDispatcher;
18 | import expo.modules.ReactNativeHostWrapper;
19 |
20 | import java.lang.reflect.InvocationTargetException;
21 | import java.util.List;
22 |
23 | public class MainApplication extends Application implements ReactApplication {
24 | private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(
25 | this,
26 | new ReactNativeHost(this) {
27 | @Override
28 | public boolean getUseDeveloperSupport() {
29 | return BuildConfig.DEBUG;
30 | }
31 |
32 | @Override
33 | protected List getPackages() {
34 | @SuppressWarnings("UnnecessaryLocalVariable")
35 | List packages = new PackageList(this).getPackages();
36 | // Packages that cannot be autolinked yet can be added manually here, for example:
37 | // packages.add(new MyReactNativePackage());
38 | return packages;
39 | }
40 |
41 | @Override
42 | protected String getJSMainModuleName() {
43 | return "index";
44 | }
45 | });
46 |
47 | private final ReactNativeHost mNewArchitectureNativeHost =
48 | new ReactNativeHostWrapper(this, new MainApplicationReactNativeHost(this));
49 |
50 | @Override
51 | public ReactNativeHost getReactNativeHost() {
52 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
53 | return mNewArchitectureNativeHost;
54 | } else {
55 | return mReactNativeHost;
56 | }
57 | }
58 |
59 | @Override
60 | public void onCreate() {
61 | super.onCreate();
62 | // If you opted-in for the New Architecture, we enable the TurboModule system
63 | ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
64 | SoLoader.init(this, /* native exopackage */ false);
65 |
66 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
67 | ApplicationLifecycleDispatcher.onApplicationCreate(this);
68 | }
69 |
70 | @Override
71 | public void onConfigurationChanged(@NonNull Configuration newConfig) {
72 | super.onConfigurationChanged(newConfig);
73 | ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
74 | }
75 |
76 | /**
77 | * Loads Flipper in React Native templates. Call this in the onCreate method with something like
78 | * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
79 | *
80 | * @param context
81 | * @param reactInstanceManager
82 | */
83 | private static void initializeFlipper(
84 | Context context, ReactInstanceManager reactInstanceManager) {
85 | if (BuildConfig.DEBUG) {
86 | try {
87 | /*
88 | We use reflection here to pick up the class that initializes Flipper,
89 | since Flipper library is not available in release mode
90 | */
91 | Class> aClass = Class.forName("com.skillnation.examplernfbhooks.ReactNativeFlipper");
92 | aClass
93 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
94 | .invoke(null, context, reactInstanceManager);
95 | } catch (ClassNotFoundException e) {
96 | e.printStackTrace();
97 | } catch (NoSuchMethodException e) {
98 | e.printStackTrace();
99 | } catch (IllegalAccessException e) {
100 | e.printStackTrace();
101 | } catch (InvocationTargetException e) {
102 | e.printStackTrace();
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/example/ios/example/SplashScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@skillnation/react-native-firebase-hooks",
3 | "version": "0.5.0",
4 | "description": "React Hooks for React Native Firebase",
5 | "main": "dist/index.cjs.js",
6 | "module": "dist/index.esm.js",
7 | "typings": "index.d.ts",
8 | "source": "src/",
9 | "files": [
10 | "src",
11 | "lib",
12 | "android",
13 | "ios",
14 | "cpp",
15 | "auth",
16 | "storage",
17 | "database",
18 | "firestore",
19 | "react-native-firebase-hooks.podspec",
20 | "!lib/typescript/example",
21 | "!android/build",
22 | "!ios/build",
23 | "!**/__tests__",
24 | "!**/__fixtures__",
25 | "!**/__mocks__"
26 | ],
27 | "scripts": {
28 | "test": "jest",
29 | "typescript": "tsc --noEmit",
30 | "lint": "eslint \"**/*.{js,ts,tsx}\"",
31 | "prepare": "bob build",
32 | "release": "release-it",
33 | "example": "yarn --cwd example",
34 | "pods": "cd example && pod-install --quiet",
35 | "bootstrap": "yarn example && yarn && yarn pods"
36 | },
37 | "keywords": [
38 | "react-native",
39 | "ios",
40 | "android",
41 | "firebase",
42 | "hooks"
43 | ],
44 | "private": false,
45 | "repository": "https://github.com/skillnation/react-native-firebase-hooks",
46 | "author": "Hanno J. Gödecke (https://github.com/skillnation)",
47 | "license": "Apache-2.0",
48 | "bugs": {
49 | "url": "https://github.com/skillnation/react-native-firebase-hooks/issues"
50 | },
51 | "homepage": "https://github.com/skillnation/react-native-firebase-hooks#readme",
52 | "publishConfig": {
53 | "registry": "https://registry.npmjs.org/",
54 | "access": "public"
55 | },
56 | "devDependencies": {
57 | "@commitlint/config-conventional": "^11.0.0",
58 | "@react-native-community/eslint-config": "^2.0.0",
59 | "@react-native-firebase/app": "^14.11.1",
60 | "@react-native-firebase/auth": "^14.11.1",
61 | "@react-native-firebase/database": "^14.11.1",
62 | "@react-native-firebase/firestore": "^14.11.1",
63 | "@react-native-firebase/storage": "^14.11.1",
64 | "@release-it/conventional-changelog": "^2.0.0",
65 | "@types/jest": "^26.0.0",
66 | "@types/react": "^16.9.19",
67 | "@types/react-native": "0.62.13",
68 | "commitlint": "^11.0.0",
69 | "eslint": "^7.2.0",
70 | "eslint-config-prettier": "^7.0.0",
71 | "eslint-plugin-prettier": "^3.1.3",
72 | "husky": "^6.0.0",
73 | "jest": "^26.0.1",
74 | "pod-install": "^0.1.0",
75 | "prettier": "^2.0.5",
76 | "react": "16.13.1",
77 | "react-native": "0.63.4",
78 | "react-native-builder-bob": "^0.18.0",
79 | "release-it": "^14.2.2",
80 | "typescript": "^4.1.3"
81 | },
82 | "peerDependencies": {
83 | "@react-native-firebase/app": "*",
84 | "@react-native-firebase/auth": "*",
85 | "@react-native-firebase/database": "*",
86 | "@react-native-firebase/firestore": "*",
87 | "@react-native-firebase/storage": "*",
88 | "react": "*",
89 | "react-native": "*"
90 | },
91 | "jest": {
92 | "preset": "react-native",
93 | "modulePathIgnorePatterns": [
94 | "/example/node_modules",
95 | "/lib/"
96 | ]
97 | },
98 | "commitlint": {
99 | "extends": [
100 | "@commitlint/config-conventional"
101 | ]
102 | },
103 | "release-it": {
104 | "git": {
105 | "commitMessage": "chore: release ${version}",
106 | "tagName": "v${version}"
107 | },
108 | "npm": {
109 | "publish": true
110 | },
111 | "github": {
112 | "release": true
113 | },
114 | "plugins": {
115 | "@release-it/conventional-changelog": {
116 | "preset": "angular"
117 | }
118 | }
119 | },
120 | "eslintConfig": {
121 | "root": true,
122 | "extends": [
123 | "@react-native-community",
124 | "prettier"
125 | ],
126 | "rules": {
127 | "prettier/prettier": [
128 | "error",
129 | {
130 | "quoteProps": "consistent",
131 | "singleQuote": true,
132 | "tabWidth": 2,
133 | "trailingComma": "es5",
134 | "useTabs": false
135 | }
136 | ]
137 | }
138 | },
139 | "eslintIgnore": [
140 | "node_modules/",
141 | "lib/"
142 | ],
143 | "prettier": {
144 | "quoteProps": "consistent",
145 | "singleQuote": true,
146 | "tabWidth": 2,
147 | "trailingComma": "es5",
148 | "useTabs": false
149 | },
150 | "react-native-builder-bob": {
151 | "source": "src",
152 | "output": "lib",
153 | "targets": [
154 | "commonjs",
155 | "module",
156 | [
157 | "typescript",
158 | {
159 | "project": "tsconfig.build.json"
160 | }
161 | ]
162 | ]
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/skillnation/examplernfbhooks/newarchitecture/MainApplicationReactNativeHost.java:
--------------------------------------------------------------------------------
1 | package com.skillnation.examplernfbhooks.newarchitecture;
2 |
3 | import android.app.Application;
4 | import androidx.annotation.NonNull;
5 | import com.facebook.react.PackageList;
6 | import com.facebook.react.ReactInstanceManager;
7 | import com.facebook.react.ReactNativeHost;
8 | import com.facebook.react.ReactPackage;
9 | import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
10 | import com.facebook.react.bridge.JSIModulePackage;
11 | import com.facebook.react.bridge.JSIModuleProvider;
12 | import com.facebook.react.bridge.JSIModuleSpec;
13 | import com.facebook.react.bridge.JSIModuleType;
14 | import com.facebook.react.bridge.JavaScriptContextHolder;
15 | import com.facebook.react.bridge.ReactApplicationContext;
16 | import com.facebook.react.bridge.UIManager;
17 | import com.facebook.react.fabric.ComponentFactory;
18 | import com.facebook.react.fabric.CoreComponentsRegistry;
19 | import com.facebook.react.fabric.EmptyReactNativeConfig;
20 | import com.facebook.react.fabric.FabricJSIModuleProvider;
21 | import com.facebook.react.uimanager.ViewManagerRegistry;
22 | import com.skillnation.examplernfbhooks.BuildConfig;
23 | import com.skillnation.examplernfbhooks.newarchitecture.components.MainComponentsRegistry;
24 | import com.skillnation.examplernfbhooks.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate;
25 | import java.util.ArrayList;
26 | import java.util.List;
27 |
28 | /**
29 | * A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both
30 | * TurboModule delegates and the Fabric Renderer.
31 | *
32 | * Please note that this class is used ONLY if you opt-in for the New Architecture (see the
33 | * `newArchEnabled` property). Is ignored otherwise.
34 | */
35 | public class MainApplicationReactNativeHost extends ReactNativeHost {
36 | public MainApplicationReactNativeHost(Application application) {
37 | super(application);
38 | }
39 |
40 | @Override
41 | public boolean getUseDeveloperSupport() {
42 | return BuildConfig.DEBUG;
43 | }
44 |
45 | @Override
46 | protected List getPackages() {
47 | List packages = new PackageList(this).getPackages();
48 | // Packages that cannot be autolinked yet can be added manually here, for example:
49 | // packages.add(new MyReactNativePackage());
50 | // TurboModules must also be loaded here providing a valid TurboReactPackage implementation:
51 | // packages.add(new TurboReactPackage() { ... });
52 | // If you have custom Fabric Components, their ViewManagers should also be loaded here
53 | // inside a ReactPackage.
54 | return packages;
55 | }
56 |
57 | @Override
58 | protected String getJSMainModuleName() {
59 | return "index";
60 | }
61 |
62 | @NonNull
63 | @Override
64 | protected ReactPackageTurboModuleManagerDelegate.Builder
65 | getReactPackageTurboModuleManagerDelegateBuilder() {
66 | // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary
67 | // for the new architecture and to use TurboModules correctly.
68 | return new MainApplicationTurboModuleManagerDelegate.Builder();
69 | }
70 |
71 | @Override
72 | protected JSIModulePackage getJSIModulePackage() {
73 | return new JSIModulePackage() {
74 | @Override
75 | public List getJSIModules(
76 | final ReactApplicationContext reactApplicationContext,
77 | final JavaScriptContextHolder jsContext) {
78 | final List specs = new ArrayList<>();
79 |
80 | // Here we provide a new JSIModuleSpec that will be responsible of providing the
81 | // custom Fabric Components.
82 | specs.add(
83 | new JSIModuleSpec() {
84 | @Override
85 | public JSIModuleType getJSIModuleType() {
86 | return JSIModuleType.UIManager;
87 | }
88 |
89 | @Override
90 | public JSIModuleProvider getJSIModuleProvider() {
91 | final ComponentFactory componentFactory = new ComponentFactory();
92 | CoreComponentsRegistry.register(componentFactory);
93 |
94 | // Here we register a Components Registry.
95 | // The one that is generated with the template contains no components
96 | // and just provides you the one from React Native core.
97 | MainComponentsRegistry.register(componentFactory);
98 |
99 | final ReactInstanceManager reactInstanceManager = getReactInstanceManager();
100 |
101 | ViewManagerRegistry viewManagerRegistry =
102 | new ViewManagerRegistry(
103 | reactInstanceManager.getOrCreateViewManagers(reactApplicationContext));
104 |
105 | return new FabricJSIModuleProvider(
106 | reactApplicationContext,
107 | componentFactory,
108 | new EmptyReactNativeConfig(),
109 | viewManagerRegistry);
110 | }
111 | });
112 | return specs;
113 | }
114 | };
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/database/useList.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo } from 'react';
2 | import { snapshotToData, ValOptions } from './helpers';
3 | import useListReducer from './helpers/useListReducer';
4 | import type { ListHook, ListKeysHook, ListValsHook, Val } from './types';
5 | import { useIsEqualRef } from '../util';
6 | import type { FirebaseDatabaseTypes } from '@react-native-firebase/database';
7 | import database from '@react-native-firebase/database';
8 | database().ref().off();
9 | // import {
10 | // DataSnapshot,
11 | // Query,
12 | // onChildAdded as firebaseOnChildAdded,
13 | // onChildChanged as firebaseOnChildChanged,
14 | // onChildMoved as firebaseOnChildMoved,
15 | // onChildRemoved as firebaseOnChildRemoved,
16 | // onValue as firebaseOnValue,
17 | // off,
18 | // } from 'firebase/database';
19 |
20 | export const useList = (
21 | query?: FirebaseDatabaseTypes.Query | null
22 | ): ListHook => {
23 | const [state, dispatch] = useListReducer();
24 |
25 | const queryRef = useIsEqualRef(query, () => dispatch({ type: 'reset' }));
26 | const ref: FirebaseDatabaseTypes.Query | null | undefined = queryRef.current;
27 |
28 | useEffect(() => {
29 | if (!ref) {
30 | dispatch({ type: 'empty' });
31 | return;
32 | }
33 |
34 | const onChildAdded = (
35 | snapshot: FirebaseDatabaseTypes.DataSnapshot | null,
36 | previousKey?: string | null
37 | ) => {
38 | dispatch({ type: 'add', previousKey, snapshot });
39 | };
40 |
41 | const onChildChanged = (
42 | snapshot: FirebaseDatabaseTypes.DataSnapshot | null
43 | ) => {
44 | dispatch({ type: 'change', snapshot });
45 | };
46 |
47 | const onChildMoved = (
48 | snapshot: FirebaseDatabaseTypes.DataSnapshot | null,
49 | previousKey?: string | null
50 | ) => {
51 | dispatch({ type: 'move', previousKey, snapshot });
52 | };
53 |
54 | const onChildRemoved = (
55 | snapshot: FirebaseDatabaseTypes.DataSnapshot | null
56 | ) => {
57 | dispatch({ type: 'remove', snapshot });
58 | };
59 |
60 | const onError = (error: Error) => {
61 | dispatch({ type: 'error', error });
62 | };
63 |
64 | const onValue = (
65 | snapshots: FirebaseDatabaseTypes.DataSnapshot[] | null
66 | ) => {
67 | dispatch({ type: 'value', snapshots });
68 | };
69 |
70 | let childAddedHandler:
71 | | ReturnType
72 | | undefined;
73 | const onInitialLoad = (snapshot: FirebaseDatabaseTypes.DataSnapshot) => {
74 | const snapshotVal = snapshot.val();
75 | let childrenToProcess = snapshotVal
76 | ? Object.keys(snapshot.val()).length
77 | : 0;
78 |
79 | // If the list is empty then initialise the hook and use the default `onChildAdded` behaviour
80 | if (childrenToProcess === 0) {
81 | childAddedHandler = ref.on('child_added', onChildAdded, onError);
82 | onValue([]);
83 | } else {
84 | // Otherwise, we load the first batch of children all to reduce re-renders
85 | const children: FirebaseDatabaseTypes.DataSnapshot[] = [];
86 |
87 | const onChildAddedWithoutInitialLoad = (
88 | addedChild: FirebaseDatabaseTypes.DataSnapshot,
89 | previousKey?: string | null
90 | ) => {
91 | if (childrenToProcess > 0) {
92 | childrenToProcess--;
93 | children.push(addedChild);
94 |
95 | if (childrenToProcess === 0) {
96 | onValue(children);
97 | }
98 |
99 | return;
100 | }
101 |
102 | onChildAdded(addedChild, previousKey);
103 | };
104 |
105 | childAddedHandler = ref.on(
106 | 'child_added',
107 | onChildAddedWithoutInitialLoad,
108 | onError
109 | );
110 | }
111 | };
112 |
113 | ref.on('value', onInitialLoad, onError, { onlyOnce: true });
114 | const childChangedHandler = ref.on(
115 | 'child_changed',
116 | onChildChanged,
117 | onError
118 | );
119 | const childMovedHandler = ref.on('child_moved', onChildMoved, onError);
120 | const childRemovedHandler = ref.on(
121 | 'child_removed',
122 | onChildRemoved,
123 | onError
124 | );
125 |
126 | return () => {
127 | ref.off('child_added', childAddedHandler);
128 | ref.off('child_changed', childChangedHandler);
129 | ref.off('child_moved', childMovedHandler);
130 | ref.off('child_removed', childRemovedHandler);
131 | };
132 | }, [dispatch, ref]);
133 |
134 | return useMemo(
135 | () => [state.value.values, state.loading, state.error],
136 | [state.value.values, state.loading, state.error]
137 | );
138 | };
139 |
140 | export const useListKeys = (
141 | query?: FirebaseDatabaseTypes.Query | null
142 | ): ListKeysHook => {
143 | const [snapshots, loading, error] = useList(query);
144 | const values = useMemo(
145 | () =>
146 | snapshots
147 | ? snapshots.map((snapshot) => snapshot.key as string)
148 | : undefined,
149 | [snapshots]
150 | );
151 |
152 | return useMemo(
153 | () => [values, loading, error],
154 | [values, loading, error]
155 | );
156 | };
157 |
158 | export const useListVals = <
159 | T,
160 | KeyField extends string = '',
161 | RefField extends string = ''
162 | >(
163 | query?: FirebaseDatabaseTypes.Query | null,
164 | options?: ValOptions
165 | ): ListValsHook => {
166 | const keyField = options ? options.keyField : undefined;
167 | const refField = options ? options.refField : undefined;
168 | const transform = options ? options.transform : undefined;
169 | const [snapshots, loading, error] = useList(query);
170 | const values = useMemo(
171 | () =>
172 | (snapshots
173 | ? snapshots.map((snapshot) =>
174 | snapshotToData(snapshot, keyField, refField, transform)
175 | )
176 | : undefined) as Val[],
177 | [snapshots, keyField, refField, transform]
178 | );
179 |
180 | return useMemo>(
181 | () => [values, loading, error],
182 | [values, loading, error]
183 | );
184 | };
185 |
--------------------------------------------------------------------------------
/src/database/helpers/useListReducer.ts:
--------------------------------------------------------------------------------
1 | import { useReducer } from 'react';
2 | import type { FirebaseDatabaseTypes } from '@react-native-firebase/database';
3 |
4 | type KeyValueState = {
5 | keys?: string[];
6 | values?: FirebaseDatabaseTypes.DataSnapshot[];
7 | };
8 |
9 | type ReducerState = {
10 | error?: Error;
11 | loading: boolean;
12 | value: KeyValueState;
13 | };
14 |
15 | type AddAction = {
16 | type: 'add';
17 | previousKey?: string | null;
18 | snapshot: FirebaseDatabaseTypes.DataSnapshot | null;
19 | };
20 | type ChangeAction = {
21 | type: 'change';
22 | snapshot: FirebaseDatabaseTypes.DataSnapshot | null;
23 | };
24 | type EmptyAction = { type: 'empty' };
25 | type ErrorAction = { type: 'error'; error: Error };
26 | type MoveAction = {
27 | type: 'move';
28 | previousKey?: string | null;
29 | snapshot: FirebaseDatabaseTypes.DataSnapshot | null;
30 | };
31 | type RemoveAction = {
32 | type: 'remove';
33 | snapshot: FirebaseDatabaseTypes.DataSnapshot | null;
34 | };
35 | type ResetAction = { type: 'reset' };
36 | type ValueAction = {
37 | type: 'value';
38 | snapshots: FirebaseDatabaseTypes.DataSnapshot[] | null;
39 | };
40 | type ReducerAction =
41 | | AddAction
42 | | ChangeAction
43 | | EmptyAction
44 | | ErrorAction
45 | | MoveAction
46 | | RemoveAction
47 | | ResetAction
48 | | ValueAction;
49 |
50 | const initialState: ReducerState = {
51 | loading: true,
52 | value: {
53 | keys: [],
54 | values: [],
55 | },
56 | };
57 |
58 | const listReducer = (
59 | state: ReducerState,
60 | action: ReducerAction
61 | ): ReducerState => {
62 | switch (action.type) {
63 | case 'add':
64 | if (!action.snapshot) {
65 | return state;
66 | }
67 | return {
68 | ...state,
69 | error: undefined,
70 | value: addChild(state.value, action.snapshot, action.previousKey),
71 | };
72 | case 'change':
73 | if (!action.snapshot) {
74 | return state;
75 | }
76 | return {
77 | ...state,
78 | error: undefined,
79 | value: changeChild(state.value, action.snapshot),
80 | };
81 | case 'error':
82 | return {
83 | ...state,
84 | error: action.error,
85 | loading: false,
86 | value: {
87 | keys: undefined,
88 | values: undefined,
89 | },
90 | };
91 | case 'move':
92 | if (!action.snapshot) {
93 | return state;
94 | }
95 | return {
96 | ...state,
97 | error: undefined,
98 | value: moveChild(state.value, action.snapshot, action.previousKey),
99 | };
100 | case 'remove':
101 | if (!action.snapshot) {
102 | return state;
103 | }
104 | return {
105 | ...state,
106 | error: undefined,
107 | value: removeChild(state.value, action.snapshot),
108 | };
109 | case 'reset':
110 | return initialState;
111 | case 'value':
112 | return {
113 | ...state,
114 | error: undefined,
115 | loading: false,
116 | value: setValue(action.snapshots),
117 | };
118 | case 'empty':
119 | return {
120 | ...state,
121 | loading: false,
122 | value: {
123 | keys: undefined,
124 | values: undefined,
125 | },
126 | };
127 | default:
128 | return state;
129 | }
130 | };
131 |
132 | const setValue = (
133 | snapshots: FirebaseDatabaseTypes.DataSnapshot[] | null
134 | ): KeyValueState => {
135 | if (!snapshots) {
136 | return {
137 | keys: [],
138 | values: [],
139 | };
140 | }
141 |
142 | const keys: string[] = [];
143 | const values: FirebaseDatabaseTypes.DataSnapshot[] = [];
144 | snapshots.forEach((snapshot) => {
145 | if (!snapshot.key) {
146 | return;
147 | }
148 | keys.push(snapshot.key);
149 | values.push(snapshot);
150 | });
151 |
152 | return {
153 | keys,
154 | values,
155 | };
156 | };
157 |
158 | const addChild = (
159 | currentState: KeyValueState,
160 | snapshot: FirebaseDatabaseTypes.DataSnapshot,
161 | previousKey?: string | null
162 | ): KeyValueState => {
163 | if (!snapshot.key) {
164 | return currentState;
165 | }
166 |
167 | const { keys, values } = currentState;
168 | if (!previousKey) {
169 | // The child has been added to the start of the list
170 | return {
171 | keys: keys ? [snapshot.key, ...keys] : [snapshot.key],
172 | values: values ? [snapshot, ...values] : [snapshot],
173 | };
174 | }
175 | // Establish the index for the previous child in the list
176 | const index = keys ? keys.indexOf(previousKey) : 0;
177 | // Insert the item after the previous child
178 | return {
179 | keys: keys
180 | ? [...keys.slice(0, index + 1), snapshot.key, ...keys.slice(index + 1)]
181 | : [snapshot.key],
182 | values: values
183 | ? [...values.slice(0, index + 1), snapshot, ...values.slice(index + 1)]
184 | : [snapshot],
185 | };
186 | };
187 |
188 | const changeChild = (
189 | currentState: KeyValueState,
190 | snapshot: FirebaseDatabaseTypes.DataSnapshot
191 | ): KeyValueState => {
192 | if (!snapshot.key) {
193 | return currentState;
194 | }
195 | const { keys, values } = currentState;
196 | const index = keys ? keys.indexOf(snapshot.key) : 0;
197 | return {
198 | ...currentState,
199 | values: values
200 | ? [...values.slice(0, index), snapshot, ...values.slice(index + 1)]
201 | : [snapshot],
202 | };
203 | };
204 |
205 | const removeChild = (
206 | currentState: KeyValueState,
207 | snapshot: FirebaseDatabaseTypes.DataSnapshot
208 | ): KeyValueState => {
209 | if (!snapshot.key) {
210 | return currentState;
211 | }
212 |
213 | const { keys, values } = currentState;
214 | const index = keys ? keys.indexOf(snapshot.key) : 0;
215 | return {
216 | keys: keys ? [...keys.slice(0, index), ...keys.slice(index + 1)] : [],
217 | values: values
218 | ? [...values.slice(0, index), ...values.slice(index + 1)]
219 | : [],
220 | };
221 | };
222 |
223 | const moveChild = (
224 | currentState: KeyValueState,
225 | snapshot: FirebaseDatabaseTypes.DataSnapshot,
226 | previousKey?: string | null
227 | ): KeyValueState => {
228 | // Remove the child from it's previous location
229 | const tempValue = removeChild(currentState, snapshot);
230 | // Add the child into it's new location
231 | return addChild(tempValue, snapshot, previousKey);
232 | };
233 |
234 | export default () => useReducer(listReducer, initialState);
235 |
--------------------------------------------------------------------------------
/example/ios/example/AppDelegate.mm:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 | #import
3 | #import
4 | #import
5 | #import
6 | #import
7 | #import
8 |
9 | #import
10 | #import
11 | #import
12 | #import
13 | #import
14 |
15 | #import
16 |
17 | #if RCT_NEW_ARCH_ENABLED
18 | #import
19 | #import
20 | #import
21 | #import
22 | #import
23 | #import
24 |
25 | #import
26 |
27 | @interface AppDelegate () {
28 | RCTTurboModuleManager *_turboModuleManager;
29 | RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
30 | std::shared_ptr _reactNativeConfig;
31 | facebook::react::ContextContainer::Shared _contextContainer;
32 | }
33 | @end
34 | #endif
35 |
36 | @implementation AppDelegate
37 |
38 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
39 | {
40 | RCTAppSetupPrepareApp(application);
41 |
42 | // @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda
43 | [FIRApp configure];
44 | // @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
45 | RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions];
46 |
47 | #if RCT_NEW_ARCH_ENABLED
48 | _contextContainer = std::make_shared();
49 | _reactNativeConfig = std::make_shared();
50 | _contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
51 | _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer];
52 | bridge.surfacePresenter = _bridgeAdapter.surfacePresenter;
53 | #endif
54 |
55 | UIView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil];
56 |
57 | rootView.backgroundColor = [UIColor whiteColor];
58 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
59 | UIViewController *rootViewController = [self.reactDelegate createRootViewController];
60 | rootViewController.view = rootView;
61 | self.window.rootViewController = rootViewController;
62 | [self.window makeKeyAndVisible];
63 |
64 | [super application:application didFinishLaunchingWithOptions:launchOptions];
65 |
66 | return YES;
67 | }
68 |
69 | - (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge
70 | {
71 | // If you'd like to export some custom RCTBridgeModules, add them here!
72 | return @[];
73 | }
74 |
75 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
76 | {
77 | #if DEBUG
78 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
79 | #else
80 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
81 | #endif
82 | }
83 |
84 | // Linking API
85 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options {
86 | return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options];
87 | }
88 |
89 | // Universal Links
90 | - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler {
91 | BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
92 | return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result;
93 | }
94 |
95 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
96 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
97 | {
98 | return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
99 | }
100 |
101 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
102 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
103 | {
104 | return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
105 | }
106 |
107 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
108 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
109 | {
110 | return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
111 | }
112 |
113 | #if RCT_NEW_ARCH_ENABLED
114 |
115 | #pragma mark - RCTCxxBridgeDelegate
116 |
117 | - (std::unique_ptr)jsExecutorFactoryForBridge:(RCTBridge *)bridge
118 | {
119 | _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
120 | delegate:self
121 | jsInvoker:bridge.jsCallInvoker];
122 | return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager);
123 | }
124 |
125 | #pragma mark RCTTurboModuleManagerDelegate
126 |
127 | - (Class)getModuleClassFromName:(const char *)name
128 | {
129 | return RCTCoreModulesClassProvider(name);
130 | }
131 |
132 | - (std::shared_ptr