From dd2d1164d0a10b84a912124e7c53bd5b5f6ee866 Mon Sep 17 00:00:00 2001 From: Anton Borries Date: Sun, 24 Mar 2024 23:01:45 +0100 Subject: [PATCH] feat: finish glance support (#239) --- README.md | 253 ++++++++++++------ android/build.gradle | 1 + .../home_widget/HomeWidgetGlanceState.kt | 32 +++ .../HomeWidgetGlanceWidgetReceiver.kt | 29 ++ .../home_widget/HomeWidgetIntent.kt | 10 + .../home_widget/HomeWidgetPlugin.kt | 2 +- example/android/app/build.gradle | 17 +- .../android/app/src/debug/AndroidManifest.xml | 3 +- .../android/app/src/main/AndroidManifest.xml | 14 +- .../glance/HomeWidgetGlanceAppWidget.kt | 93 +++++++ .../glance/HomeWidgetReceiver.kt | 7 + .../res/xml/home_widget_glance_example.xml | 7 + .../app/src/profile/AndroidManifest.xml | 3 +- example/android/build.gradle | 2 +- example/lib/main.dart | 61 ++++- example/pubspec.yaml | 2 +- pubspec.yaml | 6 +- 17 files changed, 431 insertions(+), 111 deletions(-) create mode 100644 android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceState.kt create mode 100644 android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceWidgetReceiver.kt create mode 100644 example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetGlanceAppWidget.kt create mode 100644 example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetReceiver.kt create mode 100644 example/android/app/src/main/res/xml/home_widget_glance_example.xml diff --git a/README.md b/README.md index f7d17ed9..c2213031 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,86 @@ let data = UserDefaults.init(suiteName:"YOUR_GROUP_ID") ``` -
Android +
Android (Jetpack Glance) + +### Add Jetpack Glance as a dependency to you app's Gradle File +```groovy +implementation 'androidx.glance:glance-appwidget:LATEST-VERSION' +``` + +### Create Widget Configuration into `android/app/src/main/res/xml` +```xml + + +``` + +### Add WidgetReceiver to AndroidManifest +```xml + + + + + + +``` + +### Create WidgetReceiver + +To get automatic Updates you should extend from [HomeWidgetGlanceWidgetReceiver](android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceWidgetReceiver.kt) + +Your Receiver should then look like this + +```kotlin +package es.antonborri.home_widget_example.glance + +import HomeWidgetGlanceWidgetReceiver + +class HomeWidgetReceiver : HomeWidgetGlanceWidgetReceiver() { + override val glanceAppWidget = HomeWidgetGlanceAppWidget() +} +``` + +### Build Your AppWidget + +```kotlin + +class HomeWidgetGlanceAppWidget : GlanceAppWidget() { + + /** + * Needed for Updating + */ + override val stateDefinition = HomeWidgetGlanceStateDefinition() + + override suspend fun provideGlance(context: Context, id: GlanceId) { + provideContent { + GlanceContent(context, currentState()) + } + } + + @Composable + private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) { + // Use data to access the data you save with + val data = currentState.preferences + + + // Build Your Composable Widget + Column( + ... + } + +``` + +
+ +
Android (XML) ### Create Widget Layout inside `android/app/src/main/res/layout` @@ -103,22 +182,6 @@ which will give you access to the same SharedPreferences ### More Information For more Information on how to create and configure Android Widgets, check out [this guide](https://developer.android.com/develop/ui/views/appwidgets) on the Android Developers Page. -### Jetpack Glance -In Jetpack Glance, you have to write your receiver (== provider), that returns a widget. -Add it to AndroidManifest the same way as written above for android widgets. - -```kotlin -class MyReceiver : GlanceAppWidgetReceiver() { - override val glanceAppWidget: GlanceAppWidget get() = MyWidget() -} -``` - -If you need to access HomeWidget shared preferences, use this: - -```kotlin -HomeWidgetPlugin.getData(context) -``` -
## Usage @@ -145,14 +208,17 @@ HomeWidget.updateWidget( ); ``` -The name for Android will be chosen by checking `qualifiedAndroidName`, falling back to `.androidName` and if that was not provided it -will fallback to `.name`. +The name for Android will be chosen by checking `qualifiedAndroidName`, falling back to `.androidName` and if that was not provided it will fallback to `.name`. This Name needs to be equal to the Classname of the [WidgetProvider](#Write-your-Widget) The name for iOS will be chosen by checking `iOSName` if that was not provided it will fallback to `name`. This name needs to be equal to the Kind specified in you Widget -#### Android +#### Android (Jetpack Glance) + +If you followed the guide and use `HomeWidgetGlanceWidgetReceiver` as your Receiver, `HomeWidgetGlanceStateDefinition` as the AppWidgetStateDefinition, `currentState()` in the composable view and `currentState.preferences` for data access. No further work is necessary. + +#### Android (XML) Calling `HomeWidget.updateWidget` only notifies the specified provider. To update widgets using this provider, update them from the provider like this: @@ -173,36 +239,6 @@ class HomeWidgetExampleProvider : HomeWidgetProvider() { } ``` -#### Jetpack Glance -Updating widgets in Jetpack Glance is a bit more tricky, -widgets are only updated when their state changes, -therefore simple update will not refresh them. -To update them, you have to fake state update like this: - -```kotlin -class MyWidgetReceiver : GlanceAppWidgetReceiver() { - override val glanceAppWidget: GlanceAppWidget get() = MyWidget() - - override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { - super.onUpdate(context, appWidgetManager, appWidgetIds) - - runBlocking { - appWidgetIds.forEach { - val glanceId = GlanceAppWidgetManager(context).getGlanceIdBy(it) - MyWidget().apply { - // Must update widget state otherwise it update has no effect for some reason. - updateAppWidgetState(context, glanceId) { prefs -> - prefs[stringPreferencesKey("___FAKE_UPDATE___")] = Random.nextULong().toString() - } - - // Update widget. - update(context, glanceId) - } - } - } - } -} -``` ### Retrieve Data To retrieve the current Data saved in the Widget call `HomeWidget.getWidgetData('id', defaultValue: data)` @@ -301,7 +337,40 @@ Android and iOS (starting with iOS 17) allow widgets to have interactive Element This code tells the system to always perform the Intent in the App and not in a process attached to the Widget. Note however that this will start your Flutter App using the normal main entrypoint meaning your full app might be run in the background. To counter this you should add checks in the very first Widget you build inside `runApp` to only perform necessary calls/setups while the App is launched in the background
-
Android + +
Android Jetpack Glance + +1. Add the necessary Receiver and Service to your `AndroidManifest.xml` file + ``` + + + + + + + ``` +2. Create a custom Action + ```kotlin + class InteractiveAction : ActionCallback { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context, Uri.parse("homeWidgetExample://titleClicked")) + backgroundIntent.send() + } + } + ``` +3. Add the Action as a modifier to a view + ```kotlin + Text( + title, + style = TextStyle(fontSize = 36.sp, fontWeight = FontWeight.Bold), + modifier = GlanceModifier.clickable(onClick = actionRunCallback()), + ) + ``` + +
+ +
Android XML 1. Add the necessary Receiver and Service to your `AndroidManifest.xml` file ``` @@ -413,7 +482,25 @@ To retrieve the image and display it in a widget, you can use the following Swif Screenshot 2023-06-07 at 12 57 28 PM
-
Android +
Android (Jetpack Glance) + +```kotlin +// Access data +val data = currentState.preferences + +// Get Path +val imagePath = data.getString("lineChart", null) + +// Add Image to Compose Tree +imagePath?.let { + val bitmap = BitmapFactory.decodeFile(it) + Image(androidx.glance.ImageProvider(bitmap), null) +} +``` + +
+ +
Android (XML) 1. Add an image UI element to your xml file: ```xml @@ -484,7 +571,8 @@ Text(entry.message) In order to only detect Widget Links you need to add the queryParameter`homeWidget` to the URL
-
Android +
Android Jetpack Glance + Add an `IntentFilter` to the `Activity` Section in your `AndroidManifest` ``` @@ -492,49 +580,38 @@ Add an `IntentFilter` to the `Activity` Section in your `AndroidManifest` ``` -In your WidgetProvider add a PendingIntent to your View using `HomeWidgetLaunchIntent.getActivity` +Add the following modifier to your Widget (import from HomeWidget) ```kotlin -val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity( - context, - MainActivity::class.java, - Uri.parse("homeWidgetExample://message?message=$message")) -setOnClickPendingIntent(R.id.widget_message, pendingIntentWithData) +Text( + message, + style = TextStyle(fontSize = 18.sp), + modifier = GlanceModifier.clickable( + onClick = actionStartActivity( + context, + Uri.parse("homeWidgetExample://message?message=$message") + ) + ) +) ``` -#### Jetpack Glance -Create an `ActionCallback`: - -```kotlin -class OpenAppAction : ActionCallback { - companion object { - const val MESSAGE_KEY = "OpenAppActionMessageKey" - } - - override suspend fun onAction( - context: Context, glanceId: GlanceId, parameters: ActionParameters - ) { - val message = parameters[ActionParameters.Key(MESSAGE_KEY)] +
- val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity( - context, MainActivity::class.java, Uri.parse("homeWidgetExample://message?message=$message") - ) +
Android XML - pendingIntentWithData.send() - } -} +Add an `IntentFilter` to the `Activity` Section in your `AndroidManifest` +``` + + + ``` -and use it like this: - +In your WidgetProvider add a PendingIntent to your View using `HomeWidgetLaunchIntent.getActivity` ```kotlin -Button( - text = "Open App", - onClick = actionRunCallback( - actionParametersOf( - ActionParameters.Key(OpenAppAction.MESSAGE_KEY) to "your message" - ) - ) -) +val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity( + context, + MainActivity::class.java, + Uri.parse("homeWidgetExample://message?message=$message")) +setOnClickPendingIntent(R.id.widget_message, pendingIntentWithData) ```
diff --git a/android/build.gradle b/android/build.gradle index 48f943af..f5dc92a8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -51,4 +51,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation 'androidx.glance:glance-appwidget:1.0.0' } diff --git a/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceState.kt b/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceState.kt new file mode 100644 index 00000000..31e6595a --- /dev/null +++ b/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceState.kt @@ -0,0 +1,32 @@ +import android.content.Context +import android.content.SharedPreferences +import android.os.Environment +import androidx.datastore.core.DataStore +import androidx.glance.state.GlanceStateDefinition +import es.antonborri.home_widget.HomeWidgetPlugin +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import java.io.File + +class HomeWidgetGlanceState(val preferences: SharedPreferences) + +class HomeWidgetGlanceStateDefinition : GlanceStateDefinition { + override suspend fun getDataStore(context: Context, fileKey: String): DataStore { + val preferences = context.getSharedPreferences(HomeWidgetPlugin.PREFERENCES, Context.MODE_PRIVATE) + return HomeWidgetGlanceDataStore(preferences) + } + + override fun getLocation(context: Context, fileKey: String): File { + return Environment.getDataDirectory() + } + +} + +private class HomeWidgetGlanceDataStore(private val preferences: SharedPreferences) : DataStore { + override val data: Flow + get() = flow { emit(HomeWidgetGlanceState(preferences)) } + + override suspend fun updateData(transform: suspend (t: HomeWidgetGlanceState) -> HomeWidgetGlanceState): HomeWidgetGlanceState { + return transform(HomeWidgetGlanceState(preferences)) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceWidgetReceiver.kt b/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceWidgetReceiver.kt new file mode 100644 index 00000000..c4ee3413 --- /dev/null +++ b/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceWidgetReceiver.kt @@ -0,0 +1,29 @@ +import android.appwidget.AppWidgetManager +import android.content.Context +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetManager +import androidx.glance.appwidget.GlanceAppWidgetReceiver +import androidx.glance.appwidget.state.updateAppWidgetState +import kotlinx.coroutines.runBlocking + +abstract class HomeWidgetGlanceWidgetReceiver : GlanceAppWidgetReceiver() { + + abstract override val glanceAppWidget: T + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + super.onUpdate(context, appWidgetManager, appWidgetIds) + runBlocking { + appWidgetIds.forEach { + val glanceId = GlanceAppWidgetManager(context).getGlanceIdBy(it) + glanceAppWidget.apply { + if (this.stateDefinition is HomeWidgetGlanceStateDefinition) { + // Must Update State + updateAppWidgetState(context = context, this.stateDefinition as HomeWidgetGlanceStateDefinition, glanceId) { currentState -> currentState } + } + // Update widget. + update(context, glanceId) + } + } + } + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetIntent.kt b/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetIntent.kt index e4233800..770a1d3f 100644 --- a/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetIntent.kt +++ b/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetIntent.kt @@ -7,6 +7,8 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build +import androidx.glance.action.Action +import androidx.glance.appwidget.action.actionStartActivity object HomeWidgetLaunchIntent { @@ -33,6 +35,14 @@ object HomeWidgetLaunchIntent { } } +inline fun actionStartActivity(context: Context, uri: Uri? = null): Action { + val intent = Intent(context, T::class.java) + intent.data = uri + intent.action = HomeWidgetLaunchIntent.HOME_WIDGET_LAUNCH_ACTION + + return actionStartActivity(intent) +} + object HomeWidgetBackgroundIntent { private const val HOME_WIDGET_BACKGROUND_ACTION = "es.antonborri.home_widget.action.BACKGROUND" diff --git a/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetPlugin.kt b/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetPlugin.kt index 044c9a83..b162d417 100644 --- a/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetPlugin.kt +++ b/android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetPlugin.kt @@ -195,7 +195,7 @@ class HomeWidgetPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } companion object { - private const val PREFERENCES = "HomeWidgetPreferences" + internal const val PREFERENCES = "HomeWidgetPreferences" private const val INTERNAL_PREFERENCES = "InternalHomeWidgetPreferences" private const val CALLBACK_DISPATCHER_HANDLE = "callbackDispatcherHandle" diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 6100d5a2..062e0ba1 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -37,21 +37,33 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "es.antonborri.home_widget_example" minSdkVersion 23 targetSdkVersion 34 + multiDexEnabled true versionCode flutterVersionCode.toInteger() versionName flutterVersionName } + buildFeatures { + compose true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.5.4" + } + buildTypes { + debug { + minifyEnabled true + } release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } + namespace 'es.antonborri.home_widget_example' } flutter { @@ -60,4 +72,7 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.glance:glance-appwidget:1.0.0' + implementation "androidx.work:work-runtime-ktx:2.8.1" + implementation "androidx.multidex:multidex:2.0.1" } diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 0d6f66ae..f880684a 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 0358a5f7..17e4c2aa 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetGlanceAppWidget.kt b/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetGlanceAppWidget.kt new file mode 100644 index 00000000..ce8349bd --- /dev/null +++ b/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetGlanceAppWidget.kt @@ -0,0 +1,93 @@ +package es.antonborri.home_widget_example.glance + +import HomeWidgetGlanceState +import HomeWidgetGlanceStateDefinition +import android.content.Context +import android.graphics.BitmapFactory +import android.net.Uri +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.glance.GlanceId +import androidx.glance.GlanceModifier +import androidx.glance.Image +import androidx.glance.action.ActionParameters +import androidx.glance.action.clickable +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.action.ActionCallback +import androidx.glance.appwidget.action.actionRunCallback +import androidx.glance.appwidget.provideContent +import androidx.glance.background +import androidx.glance.currentState +import androidx.glance.layout.Alignment +import androidx.glance.layout.Box +import androidx.glance.layout.Column +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.padding +import androidx.glance.text.FontWeight +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import es.antonborri.home_widget.HomeWidgetBackgroundIntent +import es.antonborri.home_widget.actionStartActivity +import es.antonborri.home_widget_example.MainActivity + +class HomeWidgetGlanceAppWidget : GlanceAppWidget() { + + /** + * Needed for Updating + */ + override val stateDefinition = HomeWidgetGlanceStateDefinition() + + override suspend fun provideGlance(context: Context, id: GlanceId) { + provideContent { + GlanceContent(context, currentState()) + } + } + + @Composable + private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) { + val data = currentState.preferences + val imagePath = data.getString("dashIcon", null) + + val title = data.getString("title", "")!! + val message = data.getString("message", "")!! + + Box(modifier = GlanceModifier.background(Color.White).padding(16.dp).clickable(onClick = actionStartActivity(context))) { + Column( + modifier = GlanceModifier.fillMaxSize(), + verticalAlignment = Alignment.Vertical.Top, + horizontalAlignment = Alignment.Horizontal.Start, + ) { + Text("Glance") + Text( + title, + style = TextStyle(fontSize = 36.sp, fontWeight = FontWeight.Bold), + modifier = GlanceModifier.clickable(onClick = actionRunCallback()), + ) + Text( + message, + style = TextStyle(fontSize = 18.sp), + modifier = GlanceModifier.clickable( + onClick = actionStartActivity( + context, + Uri.parse("homeWidgetExample://message?message=$message") + ) + ) + ) + imagePath?.let { + val bitmap = BitmapFactory.decodeFile(it) + Image(androidx.glance.ImageProvider(bitmap), null) + } + } + } + } +} + +class InteractiveAction : ActionCallback { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context, Uri.parse("homeWidgetExample://titleClicked")) + backgroundIntent.send() + } +} + diff --git a/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetReceiver.kt b/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetReceiver.kt new file mode 100644 index 00000000..b9eb5ba9 --- /dev/null +++ b/example/android/app/src/main/kotlin/es/antonborri/home_widget_example/glance/HomeWidgetReceiver.kt @@ -0,0 +1,7 @@ +package es.antonborri.home_widget_example.glance + +import HomeWidgetGlanceWidgetReceiver + +class HomeWidgetReceiver : HomeWidgetGlanceWidgetReceiver() { + override val glanceAppWidget = HomeWidgetGlanceAppWidget() +} \ No newline at end of file diff --git a/example/android/app/src/main/res/xml/home_widget_glance_example.xml b/example/android/app/src/main/res/xml/home_widget_glance_example.xml new file mode 100644 index 00000000..12d19480 --- /dev/null +++ b/example/android/app/src/main/res/xml/home_widget_glance_example.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index 0d6f66ae..f880684a 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/example/android/build.gradle b/example/android/build.gradle index e269d914..c50bc8ec 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.8.0' + ext.kotlin_version = '1.9.20' repositories { google() mavenCentral() diff --git a/example/lib/main.dart b/example/lib/main.dart index 7abd4464..8b994dfc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -10,7 +10,7 @@ import 'package:workmanager/workmanager.dart'; /// Used for Background Updates using Workmanager Plugin @pragma("vm:entry-point") -void callbackDispatcher() { +void callbackDispatcher() async { Workmanager().executeTask((taskName, inputData) { final now = DateTime.now(); return Future.wait([ @@ -22,11 +22,18 @@ void callbackDispatcher() { 'message', '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}', ), - HomeWidget.updateWidget( - name: 'HomeWidgetExampleProvider', - iOSName: 'HomeWidgetExample', - ), - ]).then((value) { + ]).then((value) async { + Future.wait([ + HomeWidget.updateWidget( + name: 'HomeWidgetExampleProvider', + iOSName: 'HomeWidgetExample', + ), + if (Platform.isAndroid) + HomeWidget.updateWidget( + qualifiedAndroidName: + 'es.antonborri.home_widget_example.glance.HomeWidgetReceiver', + ), + ]); return !value.contains(false); }); }); @@ -53,6 +60,12 @@ Future interactiveCallback(Uri? data) async { name: 'HomeWidgetExampleProvider', iOSName: 'HomeWidgetExample', ); + if (Platform.isAndroid) { + await HomeWidget.updateWidget( + qualifiedAndroidName: + 'es.antonborri.home_widget_example.glance.HomeWidgetReceiver', + ); + } } } @@ -73,11 +86,14 @@ class _MyAppState extends State { final TextEditingController _titleController = TextEditingController(); final TextEditingController _messageController = TextEditingController(); + bool _isRequestPinWidgetSupported = false; + @override void initState() { super.initState(); HomeWidget.setAppGroupId('YOUR_GROUP_ID'); HomeWidget.registerInteractivityCallback(interactiveCallback); + _checkPinability(); } @override @@ -115,10 +131,17 @@ class _MyAppState extends State { Future _updateWidget() async { try { - return HomeWidget.updateWidget( - name: 'HomeWidgetExampleProvider', - iOSName: 'HomeWidgetExample', - ); + return Future.wait([ + HomeWidget.updateWidget( + name: 'HomeWidgetExampleProvider', + iOSName: 'HomeWidgetExample', + ), + if (Platform.isAndroid) + HomeWidget.updateWidget( + qualifiedAndroidName: + 'es.antonborri.home_widget_example.glance.HomeWidgetReceiver', + ), + ]); } on PlatformException catch (exception) { debugPrint('Error Updating Widget. $exception'); } @@ -209,6 +232,16 @@ class _MyAppState extends State { } } + Future _checkPinability() async { + final isRequestPinWidgetSupported = + await HomeWidget.isRequestPinWidgetSupported(); + if (mounted) { + setState(() { + _isRequestPinWidgetSupported = isRequestPinWidgetSupported ?? false; + }); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -258,6 +291,14 @@ class _MyAppState extends State { onPressed: _getInstalledWidgets, child: const Text('Get Installed Widgets'), ), + if (_isRequestPinWidgetSupported) + ElevatedButton( + onPressed: () => HomeWidget.requestPinWidget( + qualifiedAndroidName: + 'es.antonborri.home_widget_example.glance.HomeWidgetReceiver', + ), + child: const Text('Pin Widget'), + ), ], ), ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 7f757721..1ec29938 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - workmanager: ^0.5.1 + workmanager: ^0.5.2 home_widget: path: ../ diff --git a/pubspec.yaml b/pubspec.yaml index 80da4e5a..cb619eff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,15 +10,15 @@ environment: dependencies: flutter: sdk: flutter - path_provider: ^2.1.1 - path_provider_foundation: ^2.3.1 + path_provider: ^2.1.2 + path_provider_foundation: ^2.3.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.1 golden_toolkit: ^0.15.0 - mocktail: ^1.0.1 + mocktail: ^1.0.3 path_provider_platform_interface: plugin_platform_interface: