【簡単!】React Native(Expo)でバックグラウンドで定期実行する方法

Featured image of the post

はじめに

Expoで「バックグラウンドで任意の処理を定期実行させる」のに苦労した💦

この記事では初心者向けに一から実装手順を解説する😊

作るもの

アプリを閉じていても、15分ごとに1回任意の処理を実行する機能を作る。

「アプリを閉じている」ってどんな状態?

この3パターンすべてが「アプリを閉じている状態」

  • バックグラウンド(アプリが最近使用したアプリ内にある間)
  • アプリが終了された後(最近使用したアプリから削除した後)
  • 端末を再起動した後(再起動後一度もアプリを起動していない間)

結論
  • どのパッケージを使えばいいか?

    結論:「react-native-background-fetch」を使う。

  • 制限事項はある?

    結論:実行間隔は15分以下にはできない。

    結論:正確に15分に1回実行されるという保証はない。

  • Managed workflowのままで実装できる?

    結論:できる。

  • 実装方法は?

    結論:基本はドキュメントのとおりだが、一部注意が必要。

なぜreact-native-background-fetchを使う?

✅他のパッケージも試したが、アプリを閉じると停止してしまったため。

(react-native-background-fetchだとアプリを閉じても動かせた)

不採用のパッケージ
expo-background-fetch
  • 実装は簡単だった。
  • しかしアプリを閉じると動かせない仕様のようだったので今回は不採用🚫

    バックグラウンド(アプリが最近使用したアプリ内にある間)だけ動作すればいい場合はexpo-background-fetchもあり!

実装方法

準備(使用する手法・技術)

事前に用意しておくもの

  • Expoのプロジェクト

今からインストールするもの

  • react-native-background-fetch

react-native-background-fetchをインストールする
npx expo install react-native-background-fetch

初期設定

✅app.jsonを修正するだけでOK。

app.json

2箇所追記する。

{
  "expo": {
    "name": "your-app-name",
    "plugins": [
	    // ✅追記
	    "react-native-background-fetch"
    ],
    "ios": {
	    // ✅追記
	    "infoPlist": {
	      "UIBackgroundModes": [
	        "fetch",
	        "processing"
	      ],
	      "BGTaskSchedulerPermittedIdentifiers": [
	        "com.transistorsoft.fetch"
	      ]
	    }
    }
  }
}

サンプルプログラムを作成

たった2ステップで作成できる!

1️⃣バックグラウンドの定期処理を登録

バックグラウンド(アプリが最近使用したアプリ内にある間)に定期的に実行したい処理を登録する。

BackgroundFetch.configure(…)で登録できる!

(一度登録すると別の画面にいっても定期実行が続く✨)

定期処理を登録したい任意の画面.tsx

(useEffectを丸ごとコピペでOK)

import BackgroundFetch from "react-native-background-fetch";

export default function ExampleScreen() {
  useEffect(() => {
    /**
     * バックグラウンドの定期処理を登録
     */
    const initBackgroundFetch = async () => {
      try {
        const status = await BackgroundFetch.configure(
          {
            minimumFetchInterval: 15, // 最小間隔(分)
				    enableHeadless: true, // ヘッドレスモードを有効にするか(アプリ終了後も定期実行するか)
				    stopOnTerminate: false, // アプリが終了したときにバックグラウンドフェッチを停止するか
				    startOnBoot: true, // デバイスの起動時にバックグラウンドフェッチを開始するか
				    forceAlarmManager: true, // AndroidのAlarmManagerを使用するか
          },
          // バックグラウンド処理
          async (taskId) => {
	          // ✅ここにバックグラウンド(アプリが最近使用したアプリ内にある間)に定期的に処理したい内容を書く
	          // ...
	          
            console.log("[BackgroundFetch] taskId: ", taskId);
            BackgroundFetch.finish(taskId);
          },
          // タイムアウト時の処理
          async (taskId) => {
            console.warn("[BackgroundFetch] TIMEOUT task: ", taskId);
            BackgroundFetch.finish(taskId);
          },
        );
        console.log("[BackgroundFetch] configure status: ", status);
      } catch (error) {
        console.error("[BackgroundFetch] Failed to configure: ", error);
      }
    };
    initBackgroundFetch();
  }, []);
}

2️⃣アプリが終了された後の定期処理を登録

アプリが終了された後(最近使用したアプリから削除した後)、端末を再起動した後(再起動後一度もアプリを起動していない間)に定期的に実行したい処理を登録する。

BackgroundFetch.registerHeadlessTask(…)で登録できる!

💡
必ずエントリーポイント(index.tsなど)に書く。

index.ts

(既存のコードはそのまま残して、以下をコピペで追記する)

import BackgroundFetch, { HeadlessEvent } from "react-native-background-fetch";

// アプリ終了時のバックグラウンドタスク
let WidgetUpdateHeadlessTask = async (event: HeadlessEvent) => {
  let taskId = event.taskId;
  let isTimeout = event.timeout;
  if (isTimeout) {
    console.log("[BackgroundFetch] Headless TIMEOUT:", taskId);
    BackgroundFetch.finish(taskId);
    return;
  }
  console.log("[BackgroundFetch HeadlessTask] start: ", taskId);

	// ✅ここにアプリが終了された後に定期的に処理したい内容を書く
	// ...
	
  BackgroundFetch.finish(taskId);
};

// Register your BackgroundFetch HeadlessTask
BackgroundFetch.registerHeadlessTask(WidgetUpdateHeadlessTask);

サンプルプログラムを実行

✅実際に15分に1回定期実行されるか確認する。

  1. ビルドし直す

    (developmentビルドだとバックグラウンド処理が動作確認できなかったのでpreviewビルドにする)

    # 【パターン1】Androidの場合
    eas build --profile preview --platform android
    
    # 【パターン2】iOSの場合
    eas build --profile preview --platform ios
    
    # 【パターン3】両方の場合
    eas build --profile preview

    エラー'no versions of com.transistorsoft:tsbackgroundfetch are available.'が出る場合
    原因

    android/build.gradle に足りていない部分がある。

    allprojects {
        repositories {
            maven {
                // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
                url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android'))
            }
            maven {
                // Android JSC is installed from npm
                url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist'))
            }
            // 🚫これが必要
            maven {
                url("${project(':react-native-background-fetch').projectDir}/libs")
            }
    
            google()
            mavenCentral()
            maven { url 'https://www.jitpack.io' }
        }
    }

    問題点

    Managed Workflowでは直接 android/build.gradle を編集できない🚫

    (Bare workflowならandroid/build.gradleに直接追記すればOK。)

    解決策

    Config Pluginを作成する✅

    (ビルド時にandroid/build.gradleを書き換えるように設定をする)

    1️⃣Config Pluginを作成

    plugin/react-native-background-fetch.config.js(新規作成)

    /**
     * react-native-background-fetchの設定を行うConfig Plugin
     */
    const { withProjectBuildGradle } = require("@expo/config-plugins");
    
    module.exports = (config) => {
      console.log("[カスタムプラグイン react-native-background-fetch]開始");
      config = setBuildGradle(config);
      return config;
    };
    
    /**
     * build.gradleの設定を行う
     */
    function setBuildGradle(config) {
      return withProjectBuildGradle(config, (config) => {
        // react-native-background-fetchで必要なmavenリポジトリを追加
        // 参考:https://github.com/transistorsoft/react-native-background-fetch/issues/440#issuecomment-1484168664
        config.modResults.contents = replace(
          config.modResults.contents,
          "maven { url 'https://www.jitpack.io' }",
          `maven { url 'https://www.jitpack.io' }
          
            maven {
                // Required for react-native-background-fetch
                url("$\{project(':react-native-background-fetch').projectDir}/libs")
            }`,
        );
    
        return config;
      });
    }
    
    function replace(contents, match, replace) {
      if (!(match.test ? RegExp(match).test(contents) : contents.includes(match)))
        throw new Error("Invalid text replace in config");
    
      return contents.replace(match, replace);
    }
    
    2️⃣Config Pluginを適用

    app.config.ts

    const withReactNativeBackGroundConfig = require("./plugin/react-native-background-fetch.config");
    
    // 既存の設定に対して、withReactNativeBackGroundConfigを実行する
    export default ({ config }: ConfigContext): ExpoConfig =>
      withReactNativeBackGroundConfig(config);
    

    →これでビルド時にandroid/build.gradleが正しい内容に書き換えられてビルドが成功する。

  2. ビルドしたアプリをインストールする
  3. アプリを開く
  4. 「1️⃣バックグラウンドの定期処理を登録」を書いた画面を開く

    (開くとバックグラウンドの定期処理が登録される)

  5. バックグラウンド(アプリが最近使用したアプリ内にある状態)で15分待つ
  6. 15分後、定期処理が実行されたか確認する

【補足】デバッグする方法

【トラブルシューティング】Androidでデバイスを再起動すると定期実行が停止してしまう場合の対処方法
💡
問題ない場合はここは読み飛ばしてOK。

1️⃣のコードで再起動しても定期処理を続ける設定(startOnBoot: trueを書いていた。

にもかかわらず再起動すると定期実行が止まってしまうことがあるみたい💦

原因

「android.permission.RECEIVE_BOOT_COMPLETED」のパーミッションが足りていないことが原因。

対処方法

app.jsonで「android.permission.RECEIVE_BOOT_COMPLETED」のパーミッションを追加する。

app.json

{
  "android": {
	  // 追加
    "permissions": [
      "RECEIVE_BOOT_COMPLETED"
    ]
  },
}

まとめ

  • 「react-native-background-fetch」を使えば、アプリを終了後も定期的に処理を実行できる。

  • 初期設定後はビルドし直す必要がある。

    (開発ビルドを作り直す)

  • 2つのファイルを編集するだけで実装できる。
    • 任意の画面:バックグラウンドの定期処理を登録する。
    • index.ts:アプリ終了後の定期処理を登録する。

参考サイト