From d94eb348cc7da0cefeb845b0dcb898629b288450 Mon Sep 17 00:00:00 2001 From: Sebastian Roth Date: Tue, 9 Apr 2024 09:26:56 +0100 Subject: [PATCH] chore: Android build (#545) * refactors * new build style for android * global melos * simplify * correct path --- .github/workflows/analysis.yml | 3 +- .github/workflows/examples.yml | 4 ++ .github/workflows/format.yml | 2 + ANDROID_SETUP.md | 14 ---- README.md | 16 ++--- example/android/app/build.gradle | 15 ++--- example/android/build.gradle | 13 ---- example/android/settings.gradle | 30 ++++++--- example/ios/Podfile.lock | 6 ++ example/ios/Runner.xcodeproj/project.pbxproj | 19 ++++++ example/lib/main.dart | 12 ++-- example/pubspec.yaml | 5 +- pubspec.yaml | 2 +- workmanager/android/build.gradle | 19 +----- workmanager/android/settings.gradle | 26 +++++++- .../CheckBackgroundRefreshPermission.swift | 66 ------------------- .../ios/Classes/SwiftWorkmanagerPlugin.swift | 7 -- workmanager/lib/src/options.dart | 20 ------ workmanager/lib/src/workmanager.dart | 36 ---------- 19 files changed, 103 insertions(+), 212 deletions(-) delete mode 100644 workmanager/ios/Classes/CheckBackgroundRefreshPermission.swift diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 3f6ac186..e4a6523d 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -9,4 +9,5 @@ jobs: - uses: axel-op/dart-package-analyzer@v3 with: # Required: - githubToken: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + githubToken: ${{ secrets.GITHUB_TOKEN }} + relativePath: workmanager/ \ No newline at end of file diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index d40d2266..66b4ef08 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -20,6 +20,8 @@ jobs: - name: build run: | + dart pub global activate melos + melos bootstrap cd example && flutter build apk --debug example_ios: @@ -32,4 +34,6 @@ jobs: - name: build run: | + dart pub global activate melos + melos bootstrap cd example && flutter build ios --debug --no-codesign diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 9f77fa51..04ca605b 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -47,6 +47,8 @@ jobs: - name: publish checks run: | + dart pub global activate melos + melos bootstrap cd workmanager flutter pub get flutter pub publish -n diff --git a/ANDROID_SETUP.md b/ANDROID_SETUP.md index bb90b0f6..6c519a66 100644 --- a/ANDROID_SETUP.md +++ b/ANDROID_SETUP.md @@ -1,17 +1,3 @@ -# Update build.gradle - -Make sure that your kotlin_version to `1.8.0` or greater: - -``` -buildscript { - ext.kotlin_version = '1.8.0+' - repositories { - google() - mavenCentral() - } - // ... -``` - # Check your AndroidManifest.xml Check if you have the following in your `AndroidManifest.xml` file. diff --git a/README.md b/README.md index d60b32ac..d600759b 100644 --- a/README.md +++ b/README.md @@ -176,16 +176,16 @@ Workmanager().registerProcessingTask( ``` ### Background App Refresh permission + On iOS user can disable `Background App Refresh` permission anytime, hence background tasks can only run if user has granted the permission. -With `Workmanager.checkBackgroundRefreshPermission` you can check whether background app refresh is enabled. If it is not enabled you might ask -the user to enable it in app settings. -```dart -if (Platform.isIOS) { - final hasPermission = await Workmanager().checkBackgroundRefreshPermission(); - if (hasPermission != BackgroundRefreshPermissionState.available){ - // Inform the user that background app refresh is disabled - } +Use `permision_handler` to check for the permission: + +``` dart +final status = await Permission.backgroundRefresh.status; +if (status != PermissionStatus.granted) { + _showNoPermission(context, status); + return; } ``` diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 769e3794..6c76adc8 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,10 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { @@ -21,10 +23,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -74,7 +72,6 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' diff --git a/example/android/build.gradle b/example/android/build.gradle index ce62641e..7777bae8 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.8.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.1.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - plugins { id "com.github.ben-manes.versions" version "0.41.0" } diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 5a2f14fb..009192ec 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,15 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } } -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.4" apply false + id "org.jetbrains.kotlin.android" version "1.9.23" apply false } + +include ":app" diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 8401fc3e..4edc23e4 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -5,6 +5,8 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - permission_handler_apple (9.3.0): + - Flutter - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -15,6 +17,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - workmanager (from `.symlinks/plugins/workmanager/ios`) @@ -25,6 +28,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/integration_test/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" workmanager: @@ -34,6 +39,7 @@ SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 integration_test: 13825b8a9334a850581300559b8839134b124670 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 398caff3..851fd5ef 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -190,6 +190,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, F4A106E3067F0564F4FF488C /* [CP] Embed Pods Frameworks */, + E672F4E929E3D3E957CCC34B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -353,6 +354,24 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + E672F4E929E3D3E957CCC34B /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/permission_handler_apple/permission_handler_apple_privacy.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/permission_handler_apple_privacy.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; F4A106E3067F0564F4FF488C /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/example/lib/main.dart b/example/lib/main.dart index b9153421..80021afa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:workmanager/workmanager.dart'; @@ -124,11 +125,9 @@ class _MyAppState extends State { child: Text("Start the Flutter background service"), onPressed: () async { if (Platform.isIOS) { - final hasPermission = await Workmanager() - .checkBackgroundRefreshPermission(); - if (hasPermission != - BackgroundRefreshPermissionState.available) { - _showNoPermission(context, hasPermission); + final status = await Permission.backgroundRefresh.status; + if (status != PermissionStatus.granted) { + _showNoPermission(context, status); return; } } @@ -352,8 +351,7 @@ class _MyAppState extends State { ); } - void _showNoPermission( - BuildContext context, BackgroundRefreshPermissionState hasPermission) { + void _showNoPermission(BuildContext context, PermissionStatus hasPermission) { showDialog( context: context, builder: (BuildContext context) { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 1b5ee845..e8f43730 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,8 +6,9 @@ environment: sdk: ">=2.18.0 <4.0.0" dependencies: - path_provider: ^2.0.11 - shared_preferences: ^2.2.1 + path_provider: + shared_preferences: + permission_handler: flutter: sdk: flutter workmanager: diff --git a/pubspec.yaml b/pubspec.yaml index 1cff8be6..d5b16648 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,4 +3,4 @@ name: workmanager_workspace environment: sdk: '>=2.17.0 <3.0.0' dev_dependencies: - melos: ^3.1.0 + melos: ^5.3.0 diff --git a/workmanager/android/build.gradle b/workmanager/android/build.gradle index 054d1af1..fed07446 100644 --- a/workmanager/android/build.gradle +++ b/workmanager/android/build.gradle @@ -1,19 +1,6 @@ group 'dev.fluttercommunity.workmanager' version '1.0-SNAPSHOT' -buildscript { - ext.kotlin_version = '1.8.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.0.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - rootProject.allprojects { repositories { mavenCentral() @@ -34,7 +21,7 @@ android { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { - compileSdk 33 + compileSdk 34 minSdkVersion 19 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -55,9 +42,7 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - def work_version = "2.8.1" + def work_version = "2.9.0" implementation "androidx.work:work-runtime:$work_version" implementation "androidx.concurrent:concurrent-futures:1.1.0" diff --git a/workmanager/android/settings.gradle b/workmanager/android/settings.gradle index 6968b519..aa90634c 100644 --- a/workmanager/android/settings.gradle +++ b/workmanager/android/settings.gradle @@ -1 +1,25 @@ -rootProject.name = 'workmanager' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.4" apply false + id "org.jetbrains.kotlin.android" version "1.9.23" apply false +} + +include ":app" \ No newline at end of file diff --git a/workmanager/ios/Classes/CheckBackgroundRefreshPermission.swift b/workmanager/ios/Classes/CheckBackgroundRefreshPermission.swift deleted file mode 100644 index 9f29f023..00000000 --- a/workmanager/ios/Classes/CheckBackgroundRefreshPermission.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// CheckBackgroundRefreshPermission.swift -// workmanager -// -// Created by Lars Huth on 03/11/2022. -// -import Foundation - -func checkBackgroundRefreshPermission(result: @escaping FlutterResult) -> BackgroundRefreshPermissionState { - switch UIApplication.shared.backgroundRefreshStatus { - case .available: - result(BackgroundRefreshPermissionState.available.rawValue) - return BackgroundRefreshPermissionState.available - case .denied: - result(BackgroundRefreshPermissionState.denied.rawValue) - return BackgroundRefreshPermissionState.denied - case .restricted: - result(BackgroundRefreshPermissionState.restricted.rawValue) - return BackgroundRefreshPermissionState.restricted - default: - result( - FlutterError( - code: "103", - message: "BGAppRefreshTask - Probably you have restricted background refresh permission. " + - "\n" + - "BackgroundRefreshStatus is unknown\n", - details: nil - ) - ) - return BackgroundRefreshPermissionState.unknown - } -} - -func requestBackgroundPermission() { - // Request for permission - UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) -} - -enum BackgroundRefreshPermissionState: String { - /// Background app refresh is enabled in iOS Setting - case available - - /// Background app refresh is disabled in iOS Setting. Permission should be requested from user - case denied - - /// iOS Setting is under parental control etc. Can't be changed by user - case restricted - - /// Unknown state - case unknown - - /// Convenience constructor to build a [BackgroundRefreshPermissionState] from a Dart enum. - init?(fromDart: String) { - self.init(rawValue: fromDart.camelCased(with: "_")) - } -} - -private extension String { - func camelCased(with separator: Character) -> String { - return self.lowercased() - .split(separator: separator) - .enumerated() - .map { $0.offset > 0 ? $0.element.capitalized : $0.element.lowercased() } - .joined() - } -} diff --git a/workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift b/workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift index 80b6de6c..c09d6ac0 100644 --- a/workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift +++ b/workmanager/ios/Classes/SwiftWorkmanagerPlugin.swift @@ -26,10 +26,6 @@ public class SwiftWorkmanagerPlugin: FlutterPluginAppLifeCycleDelegate { } } - struct CheckBackgroundRefreshPermission { - static let name = "\(CheckBackgroundRefreshPermission.self)".lowercasingFirst - } - struct RegisterOneOffTask { static let name = "\(RegisterOneOffTask.self)".lowercasingFirst enum Arguments: String { @@ -271,9 +267,6 @@ extension SwiftWorkmanagerPlugin: FlutterPlugin { case (ForegroundMethodChannel.Methods.Initialize.name, let .some(arguments)): initialize(arguments: arguments, result: result) return - case (ForegroundMethodChannel.Methods.CheckBackgroundRefreshPermission.name, .some): - _ = checkBackgroundRefreshPermission(result: result) - return case (ForegroundMethodChannel.Methods.RegisterOneOffTask.name, let .some(arguments)): registerOneOffTask(arguments: arguments, result: result) return diff --git a/workmanager/lib/src/options.dart b/workmanager/lib/src/options.dart index 1ce49686..34f27c31 100644 --- a/workmanager/lib/src/options.dart +++ b/workmanager/lib/src/options.dart @@ -101,23 +101,3 @@ class Constraints { this.requiresStorageNotLow, }); } - -/// Background App Refresh permission states. Currently only available in iOS. -/// -/// On iOS user can disable Background App Refresh permission anytime, hence -/// background tasks can only run if user has granted the permission. -/// [Workmanager().checkBackgroundRefreshPermission()] can be used to check the -/// permission. -enum BackgroundRefreshPermissionState { - /// Background app refresh is enabled in OS Setting - available, - - /// Background app refresh is disabled in OS Setting. Permission should be requested from user - denied, - - /// OS setting is under parental control etc. Can't be changed by user - restricted, - - /// Unknown state - unknown -} diff --git a/workmanager/lib/src/workmanager.dart b/workmanager/lib/src/workmanager.dart index 74951fdc..e52c0327 100644 --- a/workmanager/lib/src/workmanager.dart +++ b/workmanager/lib/src/workmanager.dart @@ -319,42 +319,6 @@ class Workmanager { ), ); - /// Check whether background app refresh is enabled. If it is not enabled you - /// might ask the user to enable it in app settings. - /// - /// On iOS user can disable Background App Refresh permission anytime, hence - /// background tasks can only run if user has granted the permission. Parental - /// controls can also restrict it. - /// - /// Only available on iOS. - Future - checkBackgroundRefreshPermission() async { - try { - var result = await _foregroundChannel.invokeMethod( - 'checkBackgroundRefreshPermission', - JsonMapperHelper.toInitializeMethodArgument( - isInDebugMode: _isInDebugMode, - callbackHandle: 0, - ), - ); - switch (result.toString()) { - case 'available': - return BackgroundRefreshPermissionState.available; - case 'denied': - return BackgroundRefreshPermissionState.denied; - case 'restricted': - return BackgroundRefreshPermissionState.restricted; - case 'unknown': - return BackgroundRefreshPermissionState.unknown; - } - } catch (e) { - // TODO not sure it's a good idea to handle and print a message - print("Could not retrieve BackgroundRefreshPermissionState " + - e.toString()); - } - return BackgroundRefreshPermissionState.unknown; - } - /// Cancels a task by its [uniqueName] Future cancelByUniqueName(final String uniqueName) async => await _foregroundChannel.invokeMethod(