diff --git a/packages/firebase_app_installations/firebase_app_installations/android/build.gradle b/packages/firebase_app_installations/firebase_app_installations/android/build.gradle index e4ad7c1cade5..86f1d0dc2ba2 100644 --- a/packages/firebase_app_installations/firebase_app_installations/android/build.gradle +++ b/packages/firebase_app_installations/firebase_app_installations/android/build.gradle @@ -5,10 +5,14 @@ apply plugin: 'com.android.library' apply from: file("local-config.gradle") buildscript { + ext.kotlin_version = "2.0.0" repositories { google() mavenCentral() } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } rootProject.allprojects { @@ -18,6 +22,16 @@ rootProject.allprojects { } } +// AGP 9+ has built-in Kotlin support unless Flutter opts out via android.builtInKotlin=false. +def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int +def builtInKotlin = providers.gradleProperty("android.builtInKotlin") + .map { it.toBoolean() } + .orElse(agpMajor >= 9) + .get() +if (agpMajor < 9 || !builtInKotlin) { + apply plugin: 'kotlin-android' +} + def firebaseCoreProject = rootProject.subprojects.find { it.name == "firebase_core" } if (firebaseCoreProject == null) { throw new GradleException('Could not find the firebase_core FlutterFire plugin, have you added it as a dependency in your pubspec?') @@ -49,6 +63,11 @@ android { targetCompatibility project.ext.javaVersion } + sourceSets { + main.java.srcDirs += "src/main/kotlin" + test.java.srcDirs += "src/test/kotlin" + } + buildFeatures { buildConfig true } @@ -64,4 +83,12 @@ dependencies { implementation 'androidx.annotation:annotation:1.7.0' } +plugins.withId("org.jetbrains.kotlin.android") { + kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(project.ext.javaVersion.toString()) + } + } +} + apply from: file("./user-agent.gradle") diff --git a/packages/firebase_app_installations/firebase_app_installations/android/src/main/java/io/flutter/plugins/firebase/installations/firebase_app_installations/FirebaseInstallationsPlugin.java b/packages/firebase_app_installations/firebase_app_installations/android/src/main/java/io/flutter/plugins/firebase/installations/firebase_app_installations/FirebaseInstallationsPlugin.java deleted file mode 100644 index 2ddb5d1e54fd..000000000000 --- a/packages/firebase_app_installations/firebase_app_installations/android/src/main/java/io/flutter/plugins/firebase/installations/firebase_app_installations/FirebaseInstallationsPlugin.java +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebase.installations.firebase_app_installations; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.gms.tasks.Task; -import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.android.gms.tasks.Tasks; -import com.google.firebase.FirebaseApp; -import com.google.firebase.installations.FirebaseInstallations; -import com.google.firebase.installations.InstallationTokenResult; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugins.firebase.core.FlutterFirebasePlugin; -import io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** FirebaseInstallationsPlugin */ -public class FirebaseInstallationsPlugin - implements FlutterFirebasePlugin, FlutterPlugin, MethodCallHandler { - private MethodChannel channel; - private static final String METHOD_CHANNEL_NAME = "plugins.flutter.io/firebase_app_installations"; - private final Map streamHandlers = new HashMap<>(); - - @Nullable private BinaryMessenger messenger; - - private MethodChannel setup(BinaryMessenger binaryMessenger) { - final MethodChannel channel = new MethodChannel(binaryMessenger, METHOD_CHANNEL_NAME); - channel.setMethodCallHandler(this); - this.messenger = binaryMessenger; - return channel; - } - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { - BinaryMessenger binaryMessenger = flutterPluginBinding.getBinaryMessenger(); - channel = setup(binaryMessenger); - - FlutterFirebasePluginRegistry.registerPlugin(METHOD_CHANNEL_NAME, this); - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - channel.setMethodCallHandler(null); - channel = null; - messenger = null; - - removeEventListeners(); - } - - private FirebaseInstallations getInstallations(Map arguments) { - @NonNull String appName = (String) Objects.requireNonNull(arguments.get("appName")); - FirebaseApp app = FirebaseApp.getInstance(appName); - return FirebaseInstallations.getInstance(app); - } - - private Task getId(Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - taskCompletionSource.setResult(Tasks.await(getInstallations(arguments).getId())); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task getToken(Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - FirebaseInstallations firebaseInstallations = getInstallations(arguments); - Boolean forceRefresh = (Boolean) Objects.requireNonNull(arguments.get("forceRefresh")); - InstallationTokenResult tokenResult = - Tasks.await(firebaseInstallations.getToken(forceRefresh)); - - taskCompletionSource.setResult(tokenResult.getToken()); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task registerIdChangeListener(Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - String appName = (String) Objects.requireNonNull(arguments.get("appName")); - FirebaseInstallations firebaseInstallations = getInstallations(arguments); - - io.flutter.plugins.firebase.installations.firebase_app_installations - .TokenChannelStreamHandler - handler = - new io.flutter.plugins.firebase.installations.firebase_app_installations - .TokenChannelStreamHandler(firebaseInstallations); - - final String name = METHOD_CHANNEL_NAME + "/token/" + appName; - final EventChannel channel = new EventChannel(messenger, name); - channel.setStreamHandler(handler); - streamHandlers.put(channel, handler); - - taskCompletionSource.setResult(name); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task deleteId(Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - Tasks.await(getInstallations(arguments).delete()); - - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { - Task methodCallTask; - - switch (call.method) { - case "FirebaseInstallations#getId": - methodCallTask = getId(call.arguments()); - break; - case "FirebaseInstallations#getToken": - methodCallTask = getToken(call.arguments()); - break; - case "FirebaseInstallations#delete": - methodCallTask = deleteId(call.arguments()); - break; - case "FirebaseInstallations#registerIdChangeListener": - methodCallTask = registerIdChangeListener(call.arguments()); - break; - - default: - result.notImplemented(); - return; - } - - methodCallTask.addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - result.success(task.getResult()); - } else { - Exception exception = task.getException(); - result.error( - "firebase_app_installations", - exception != null ? exception.getMessage() : null, - getExceptionDetails(exception)); - } - }); - } - - private Map getExceptionDetails(@Nullable Exception exception) { - Map details = new HashMap<>(); - details.put("code", "unknown"); - if (exception != null) { - details.put("message", exception.getMessage()); - } else { - details.put("message", "An unknown error has occurred."); - } - return details; - } - - @Override - public Task> getPluginConstantsForFirebaseApp(FirebaseApp firebaseApp) { - TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - taskCompletionSource.setResult(new HashMap() {}); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - @Override - public Task didReinitializeFirebaseCore() { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private void removeEventListeners() { - for (EventChannel eventChannel : streamHandlers.keySet()) { - EventChannel.StreamHandler streamHandler = streamHandlers.get(eventChannel); - streamHandler.onCancel(null); - eventChannel.setStreamHandler(null); - } - streamHandlers.clear(); - } -} diff --git a/packages/firebase_app_installations/firebase_app_installations/android/src/main/java/io/flutter/plugins/firebase/installations/firebase_app_installations/FlutterFirebaseAppRegistrar.java b/packages/firebase_app_installations/firebase_app_installations/android/src/main/java/io/flutter/plugins/firebase/installations/firebase_app_installations/FlutterFirebaseAppRegistrar.java deleted file mode 100644 index 688f5c9a0f68..000000000000 --- a/packages/firebase_app_installations/firebase_app_installations/android/src/main/java/io/flutter/plugins/firebase/installations/firebase_app_installations/FlutterFirebaseAppRegistrar.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebase.installations.firebase_app_installations; - -import androidx.annotation.Keep; -import com.google.firebase.components.Component; -import com.google.firebase.components.ComponentRegistrar; -import com.google.firebase.platforminfo.LibraryVersionComponent; -import java.util.Collections; -import java.util.List; - -@Keep -public class FlutterFirebaseAppRegistrar implements ComponentRegistrar { - @Override - public List> getComponents() { - return Collections.>singletonList( - LibraryVersionComponent.create(BuildConfig.LIBRARY_NAME, BuildConfig.LIBRARY_VERSION)); - } -} diff --git a/packages/firebase_app_installations/firebase_app_installations/android/src/main/java/io/flutter/plugins/firebase/installations/firebase_app_installations/TokenChannelStreamHandler.java b/packages/firebase_app_installations/firebase_app_installations/android/src/main/java/io/flutter/plugins/firebase/installations/firebase_app_installations/TokenChannelStreamHandler.java deleted file mode 100644 index ee727996cb76..000000000000 --- a/packages/firebase_app_installations/firebase_app_installations/android/src/main/java/io/flutter/plugins/firebase/installations/firebase_app_installations/TokenChannelStreamHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebase.installations.firebase_app_installations; - -import com.google.firebase.installations.FirebaseInstallations; -import com.google.firebase.installations.internal.FidListener; -import io.flutter.plugin.common.EventChannel; -import java.util.HashMap; -import java.util.Map; - -public class TokenChannelStreamHandler implements EventChannel.StreamHandler { - - private final FirebaseInstallations firebaseInstallations; - private FidListener listener; - - public TokenChannelStreamHandler(FirebaseInstallations firebaseInstallations) { - this.firebaseInstallations = firebaseInstallations; - } - - @Override - public void onListen(Object arguments, EventChannel.EventSink events) { - - listener = createTokenEventListener(events); - - firebaseInstallations.registerFidListener(listener); - } - - @Override - public void onCancel(Object arguments) { - if (listener != null) { - listener = null; - } - } - - FidListener createTokenEventListener(final EventChannel.EventSink events) { - return token -> { - Map event = new HashMap<>(); - - event.put("token", token); - - events.success(event); - }; - } -} diff --git a/packages/firebase_app_installations/firebase_app_installations/android/src/main/kotlin/io/flutter/plugins/firebase/installations/firebase_app_installations/FirebaseInstallationsPlugin.kt b/packages/firebase_app_installations/firebase_app_installations/android/src/main/kotlin/io/flutter/plugins/firebase/installations/firebase_app_installations/FirebaseInstallationsPlugin.kt new file mode 100644 index 000000000000..0b55bf25178e --- /dev/null +++ b/packages/firebase_app_installations/firebase_app_installations/android/src/main/kotlin/io/flutter/plugins/firebase/installations/firebase_app_installations/FirebaseInstallationsPlugin.kt @@ -0,0 +1,186 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package io.flutter.plugins.firebase.installations.firebase_app_installations + +import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.TaskCompletionSource +import com.google.android.gms.tasks.Tasks +import com.google.firebase.FirebaseApp +import com.google.firebase.installations.FirebaseInstallations +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugins.firebase.core.FlutterFirebasePlugin +import io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry + +/** FirebaseInstallationsPlugin */ +class FirebaseInstallationsPlugin : FlutterFirebasePlugin, FlutterPlugin, MethodCallHandler { + private var channel: MethodChannel? = null + private var messenger: BinaryMessenger? = null + private val streamHandlers = mutableMapOf() + + override fun onAttachedToEngine(binding: FlutterPluginBinding) { + messenger = binding.binaryMessenger + channel = + MethodChannel(binding.binaryMessenger, METHOD_CHANNEL_NAME).also { + it.setMethodCallHandler(this) + } + + FlutterFirebasePluginRegistry.registerPlugin(METHOD_CHANNEL_NAME, this) + } + + override fun onDetachedFromEngine(binding: FlutterPluginBinding) { + channel?.setMethodCallHandler(null) + channel = null + messenger = null + removeEventListeners() + } + + private fun getInstallations(arguments: Map): FirebaseInstallations { + val appName = requireNotNull(arguments["appName"] as? String) + return FirebaseInstallations.getInstance(FirebaseApp.getInstance(appName)) + } + + private fun getId(arguments: Map): Task { + val taskCompletionSource = TaskCompletionSource() + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + taskCompletionSource.setResult(Tasks.await(getInstallations(arguments).id)) + } catch (exception: Exception) { + taskCompletionSource.setException(exception) + } + } + + return taskCompletionSource.task + } + + private fun getToken(arguments: Map): Task { + val taskCompletionSource = TaskCompletionSource() + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val forceRefresh = requireNotNull(arguments["forceRefresh"] as? Boolean) + val tokenResult = Tasks.await(getInstallations(arguments).getToken(forceRefresh)) + taskCompletionSource.setResult(tokenResult.token) + } catch (exception: Exception) { + taskCompletionSource.setException(exception) + } + } + + return taskCompletionSource.task + } + + private fun registerIdChangeListener(arguments: Map): Task { + val taskCompletionSource = TaskCompletionSource() + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val appName = requireNotNull(arguments["appName"] as? String) + val handler = TokenChannelStreamHandler(getInstallations(arguments)) + val name = "$METHOD_CHANNEL_NAME/token/$appName" + val eventChannel = EventChannel(requireNotNull(messenger), name) + eventChannel.setStreamHandler(handler) + streamHandlers[eventChannel] = handler + taskCompletionSource.setResult(name) + } catch (exception: Exception) { + taskCompletionSource.setException(exception) + } + } + + return taskCompletionSource.task + } + + private fun deleteId(arguments: Map): Task { + val taskCompletionSource = TaskCompletionSource() + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + Tasks.await(getInstallations(arguments).delete()) + taskCompletionSource.setResult(null) + } catch (exception: Exception) { + taskCompletionSource.setException(exception) + } + } + + return taskCompletionSource.task + } + + override fun onMethodCall(call: MethodCall, result: Result) { + val arguments = requireNotNull(call.arguments>()) + val methodCallTask = + when (call.method) { + "FirebaseInstallations#getId" -> getId(arguments) + "FirebaseInstallations#getToken" -> getToken(arguments) + "FirebaseInstallations#delete" -> deleteId(arguments) + "FirebaseInstallations#registerIdChangeListener" -> registerIdChangeListener(arguments) + else -> { + result.notImplemented() + return + } + } + + methodCallTask.addOnCompleteListener { task -> + if (task.isSuccessful) { + result.success(task.result) + } else { + val exception = task.exception + result.error( + "firebase_app_installations", exception?.message, getExceptionDetails(exception)) + } + } + } + + private fun getExceptionDetails(exception: Exception?): Map { + return mapOf( + "code" to "unknown", + "message" to if (exception != null) exception.message else "An unknown error has occurred.", + ) + } + + override fun getPluginConstantsForFirebaseApp(firebaseApp: FirebaseApp): Task> { + val taskCompletionSource = TaskCompletionSource>() + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + taskCompletionSource.setResult(emptyMap()) + } catch (exception: Exception) { + taskCompletionSource.setException(exception) + } + } + + return taskCompletionSource.task + } + + override fun didReinitializeFirebaseCore(): Task { + val taskCompletionSource = TaskCompletionSource() + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + taskCompletionSource.setResult(null) + } catch (exception: Exception) { + taskCompletionSource.setException(exception) + } + } + + return taskCompletionSource.task + } + + private fun removeEventListeners() { + for ((eventChannel, streamHandler) in streamHandlers) { + streamHandler.onCancel(null) + eventChannel.setStreamHandler(null) + } + streamHandlers.clear() + } + + companion object { + private const val METHOD_CHANNEL_NAME = "plugins.flutter.io/firebase_app_installations" + } +} diff --git a/packages/firebase_app_installations/firebase_app_installations/android/src/main/kotlin/io/flutter/plugins/firebase/installations/firebase_app_installations/FlutterFirebaseAppRegistrar.kt b/packages/firebase_app_installations/firebase_app_installations/android/src/main/kotlin/io/flutter/plugins/firebase/installations/firebase_app_installations/FlutterFirebaseAppRegistrar.kt new file mode 100644 index 000000000000..219850d4274e --- /dev/null +++ b/packages/firebase_app_installations/firebase_app_installations/android/src/main/kotlin/io/flutter/plugins/firebase/installations/firebase_app_installations/FlutterFirebaseAppRegistrar.kt @@ -0,0 +1,17 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package io.flutter.plugins.firebase.installations.firebase_app_installations + +import androidx.annotation.Keep +import com.google.firebase.components.Component +import com.google.firebase.components.ComponentRegistrar +import com.google.firebase.platforminfo.LibraryVersionComponent + +@Keep +class FlutterFirebaseAppRegistrar : ComponentRegistrar { + override fun getComponents(): List> { + return listOf( + LibraryVersionComponent.create(BuildConfig.LIBRARY_NAME, BuildConfig.LIBRARY_VERSION)) + } +} diff --git a/packages/firebase_app_installations/firebase_app_installations/android/src/main/kotlin/io/flutter/plugins/firebase/installations/firebase_app_installations/TokenChannelStreamHandler.kt b/packages/firebase_app_installations/firebase_app_installations/android/src/main/kotlin/io/flutter/plugins/firebase/installations/firebase_app_installations/TokenChannelStreamHandler.kt new file mode 100644 index 000000000000..62c9a49ccc1c --- /dev/null +++ b/packages/firebase_app_installations/firebase_app_installations/android/src/main/kotlin/io/flutter/plugins/firebase/installations/firebase_app_installations/TokenChannelStreamHandler.kt @@ -0,0 +1,27 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package io.flutter.plugins.firebase.installations.firebase_app_installations + +import com.google.firebase.installations.FirebaseInstallations +import com.google.firebase.installations.internal.FidListener +import io.flutter.plugin.common.EventChannel + +class TokenChannelStreamHandler(private val firebaseInstallations: FirebaseInstallations) : + EventChannel.StreamHandler { + + private var listener: FidListener? = null + + override fun onListen(arguments: Any?, events: EventChannel.EventSink) { + listener = createTokenEventListener(events) + firebaseInstallations.registerFidListener(listener!!) + } + + override fun onCancel(arguments: Any?) { + listener = null + } + + internal fun createTokenEventListener(events: EventChannel.EventSink): FidListener { + return FidListener { token -> events.success(mapOf("token" to token)) } + } +}