バックエンド

【Java】Firebase Admin SDKのリロード時にエラーになる問題を解決!

firebase-admin-sdk-reload
野田竣介

プッシュ通知を送信するためにFirebase Admin SDKを導入しました。

しばらく動作確認をしているとアプリケーションのリロードで以下のエラーが発生することが発覚!

java.lang.IllegalStateException: FirebaseApp name [DEFAULT] already exists!
     at com.google.common.base.Preconditions.checkState(Preconditions.java:510)
     at com.google.firebase.FirebaseApp.initializeApp(FirebaseApp.java:227)
     at com.google.firebase.FirebaseApp.initializeApp(FirebaseApp.java:218)
     at com.google.firebase.FirebaseApp.initializeApp(FirebaseApp.java:205)
     at utils.FirebaseUtils.(FirebaseUtils.java:47)
     at utils.FirebaseUtils$$FastClassByGuice$$e54a4c4d.newInstance()
     at com.google.inject.internal.DefaultConstructionProxyFactory$FastClassProxy.newInstance(DefaultConstructionProxyFactory.java:89)
     at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:114)
     at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:91)
     at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:306)
     at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
     at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:168)
     at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:39)
     at com.google.inject.internal.InternalInjectorCreator.loadEagerSingletons(InternalInjectorCreator.java:211)
     at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:182)
     at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:109)
     at com.google.inject.Guice.createInjector(Guice.java:87)
     at com.google.inject.Guice.createInjector(Guice.java:78)
     at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:185)
     at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:137)
     at play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21)
     at play.core.server.DevServerStart$$anon$1.$anonfun$reload$3(DevServerStart.scala:174)
     at play.utils.Threads$.withContextClassLoader(Threads.scala:21)
     at play.core.server.DevServerStart$$anon$1.reload(DevServerStart.scala:171)
     at play.core.server.DevServerStart$$anon$1.get(DevServerStart.scala:124)
     at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:241)
     at play.core.server.AkkaHttpServer.$anonfun$createServerBinding$1(AkkaHttpServer.scala:138)
     at akka.stream.impl.fusing.MapAsyncUnordered$$anon$26.onPush(Ops.scala:1304)
     at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:519)
     at akka.stream.impl.fusing.GraphInterpreter.processEvent(GraphInterpreter.scala:482)
     at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:378)
     at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:588)
     at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:472)
     at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:563)
     at akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$processEvent(ActorGraphInterpreter.scala:745)
     at akka.stream.impl.fusing.ActorGraphInterpreter$$anonfun$receive$1.applyOrElse(ActorGraphInterpreter.scala:760)
     at akka.actor.Actor.aroundReceive(Actor.scala:517)
     at akka.actor.Actor.aroundReceive$(Actor.scala:515)
     at akka.stream.impl.fusing.ActorGraphInterpreter.aroundReceive(ActorGraphInterpreter.scala:670)
     at akka.actor.ActorCell.receiveMessage(ActorCell.scala:588)
     at akka.actor.ActorCell.invoke(ActorCell.scala:557)
     at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:258)
     at akka.dispatch.Mailbox.run(Mailbox.scala:225)
     at akka.dispatch.Mailbox.exec(Mailbox.scala:235)
     at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
     at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
     at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
     at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

そこで、このエラーを解消します!

環境
  • Java 11
  • Play Framework 2.6

原因:FirebaseAppの多重登録

FirebaseAppに同じ名前のAppを登録しようとしてエラーになっているみたいです。

Firebaseのコンソール上に表示されているサンプルコードを利用している場合、初期化はこのように行っていると思います。

FileInputStream serviceAccount =
  new FileInputStream("path/to/serviceAccountKey.json");
FirebaseOptions options = new FirebaseOptions.Builder()
  .setCredentials(GoogleCredentials.fromStream(serviceAccount))
  .setDatabaseUrl("https://project-name.firebaseio.com")
  .build();
FirebaseApp.initializeApp(options);

最後の行の

FirebaseApp.initializeApp(options);

ここでエラーになっていました。

解決:初期化済みであればそのAppを返す

最後の行をこのように変更しました。

FirebaseApp firebaseApp;
List<FirebaseApp> apps = FirebaseApp.getApps();
if (apps.size() == 0) {
  firebaseApp = FirebaseApp.initializeApp(options);
} else {
   firebaseApp = apps.get(0);
}

次のような流れで処理するように変更しました。

  1. 起動しているAppを取得する
  2. 初期化済みのものは存在する?
    • 存在する:それをそのまま返す
    • 存在しない:新たに初期化する

ありがたいことに起動しているAppを取得するメソッドがあったのであっさり解決しました。

これでリロードしたときは初期化済みのAppを返してくれます。

あとがき

起動しているAppを取得することができなかったら、面倒なことになっていましたね。

わざわざ判定するのも面倒なので、どうにかしてSDK側で吸収してほしいものですが…

しばらくこのコードで利用していますが、特にバグはないので大丈夫だと思います。

今は簡単にプッシュ通知を飛ばせて良いですね!

記事URLをコピーしました