Coder Social home page Coder Social logo

abausg / home_widget Goto Github PK

View Code? Open in Web Editor NEW
719.0 7.0 188.0 3.68 MB

Flutter Package for Easier Creation of Home Screen Widgets

License: BSD 3-Clause "New" or "Revised" License

Kotlin 30.02% Ruby 2.73% Swift 17.24% Objective-C 0.75% Dart 47.98% C++ 0.17% C 0.32% CMake 0.79%

home_widget's Introduction

Home Widget

Pub likes popularity pub points Build codecov

HomeWidget is a Plugin to make it easier to create HomeScreen Widgets on Android and iOS. HomeWidget does not allow writing Widgets with Flutter itself. It still requires writing the Widgets with native code. However, it provides a unified Interface for sending data, retrieving data and updating the Widgets

iOS  Android

Platform Setup

In order to work correctly there needs to be some platform specific setup. Check below on how to add support for Android and iOS

iOS

Add a Widget to your App in Xcode

Add a widget extension by going File > New > Target > Widget Extension

Widget Extension

Add GroupId

You need to add a groupId to the App and the Widget Extension

Note: in order to add groupIds you need a paid Apple Developer Account

Go to your Apple Developer Account and add a new group. Add this group to your Runner and the Widget Extension inside XCode: Signing & Capabilities > App Groups > +. (To swap between your App, and the Extension change the Target)

Build Targets

Sync CFBundleVersion (optional)

This step is optional, this will sync the widget extension build version with your app version, so you don't get warnings of mismatch version from App Store Connect when uploading your app.

Build Phases

In your Runner (app) target go to Build Phases > + > New Run Script Phase and add the following script:

generatedPath="$SRCROOT/Flutter/Generated.xcconfig"
versionNumber=$(grep FLUTTER_BUILD_NAME $generatedPath | cut -d '=' -f2)
buildNumber=$(grep FLUTTER_BUILD_NUMBER $generatedPath | cut -d '=' -f2)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/HomeExampleWidget/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $versionNumber" "$SRCROOT/HomeExampleWidget/Info.plist"

Replace HomeExampleWidget with the name of the widget extension folder that you have created.

Write your Widget

Check the Example App for an Implementation of a Widget. A more detailed overview on how to write Widgets for iOS 14 can be found on the Apple Developer documentation. In order to access the Data send with Flutter can be access with

let data = UserDefaults.init(suiteName:"YOUR_GROUP_ID")
Android (Jetpack Glance)

Add Jetpack Glance as a dependency to you app's Gradle File

implementation 'androidx.glance:glance-appwidget:LATEST-VERSION'

Create Widget Configuration into android/app/src/main/res/xml

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="10000">
</appwidget-provider>

Add WidgetReceiver to AndroidManifest

<receiver android:name=".glance.HomeWidgetReceiver"
          android:exported="true">
   <intent-filter>
      <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
   </intent-filter>
   <meta-data
           android:name="android.appwidget.provider"
           android:resource="@xml/home_widget_glance_example" />
</receiver>

Create WidgetReceiver

To get automatic Updates you should extend from HomeWidgetGlanceWidgetReceiver

Your Receiver should then look like this

package es.antonborri.home_widget_example.glance

import HomeWidgetGlanceWidgetReceiver

class HomeWidgetReceiver : HomeWidgetGlanceWidgetReceiver<HomeWidgetGlanceAppWidget>() {
    override val glanceAppWidget = HomeWidgetGlanceAppWidget()
}

Build Your AppWidget

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

Create Widget Configuration into android/app/src/main/res/xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:updatePeriodMillis="86400000"
    android:initialLayout="@layout/example_layout"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen">
</appwidget-provider>

Add WidgetReceiver to AndroidManifest

<receiver android:name="HomeWidgetExampleProvider" android:exported="true">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
        android:resource="@xml/home_widget_example" />
</receiver>

Write your WidgetProvider

For convenience, you can extend from HomeWidgetProvider which gives you access to a SharedPreferences Object with the Data in the onUpdate method. In case you don't want to use the convenience Method you can access the Data using

import es.antonborri.home_widget.HomeWidgetPlugin
...
HomeWidgetPlugin.getData(context)

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 on the Android Developers Page.

Usage

Setup

iOS

For iOS, you need to call HomeWidget.setAppGroupId('YOUR_GROUP_ID'); Without this you won't be able to share data between your App and the Widget and calls to saveWidgetData and getWidgetData will return an error

Save Data

In order to save Data call HomeWidget.saveWidgetData<String>('id', data)

Update a Widget

In order to force a reload of the HomeScreenWidget you need to call

HomeWidget.updateWidget(
    name: 'HomeWidgetExampleProvider',
    androidName: 'HomeWidgetExampleProvider',
    iOSName: 'HomeWidgetExample',
    qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider',
);

The name for Android will be chosen by checking qualifiedAndroidName, falling back to <packageName>.androidName and if that was not provided it will fallback to <packageName>.name. This Name needs to be equal to the Classname of the WidgetProvider

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 (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:

class HomeWidgetExampleProvider : HomeWidgetProvider() {

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
        appWidgetIds.forEach { widgetId ->
            val views = RemoteViews(context.packageName, R.layout.example_layout).apply {
                // ...
            }

            // Update widget.
            appWidgetManager.updateAppWidget(widgetId, views)
        }
    }
}

Retrieve Data

To retrieve the current Data saved in the Widget call HomeWidget.getWidgetData<String>('id', defaultValue: data)

Interactive Widgets

Android and iOS (starting with iOS 17) allow widgets to have interactive Elements like Buttons

Dart
  1. Write a static function that takes a Uri as an argument. This will get called when a user clicks on the View

    @pragma("vm:entry-point")
    FutureOr<void> backgroundCallback(Uri data) async {
      // do something with data
      ...
    }

    @pragma('vm:entry-point') must be placed above the callback function to avoid tree shaking in release mode.

  2. Register the callback function by calling

    HomeWidget.registerInteractivityCallback(backgroundCallback);
iOS
  1. Adjust your Podfile to add home_widget as a dependency to your WidgetExtension

    target 'YourWidgetExtension' do
       use_frameworks!
       use_modular_headers!
    
       pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios'
    end
  2. To be able to use plugins with the Background Callback add this to your AppDelegate's application function

    if #available(iOS 17, *) {
     HomeWidgetBackgroundWorker.setPluginRegistrantCallback { registry in
         GeneratedPluginRegistrant.register(with: registry)
     }
    }
  3. Create a custom AppIntent in your App Target (Runner) and make sure to select both your App and your WidgetExtension in the Target Membership panel

    Target Membership

    In this Intent you should import home_widget and call HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!) in the perform method. url and appGroup can be either hardcoded or set as parameters from the Widget

    import AppIntents
    import Flutter
    import Foundation
    import home_widget
    
    @available(iOS 16, *)
    public struct BackgroundIntent: AppIntent {
       static public var title: LocalizedStringResource = "HomeWidget Background Intent"
       
       @Parameter(title: "Widget URI")
       var url: URL?
       
       @Parameter(title: "AppGroup")
       var appGroup: String?
       
       public init() {}
       
       public init(url: URL?, appGroup: String?) {
          self.url = url
          self.appGroup = appGroup
       }
       
       public func perform() async throws -> some IntentResult {
          await HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!)
       
          return .result()
       }
    }   
  4. Add a Button to your Widget. This Button might be encapsulated by a Version check. Pass in an instance of the AppIntent created in the previous step

    Button(
       intent: BackgroundIntent(
         url: URL(string: "homeWidgetExample://titleClicked"), appGroup: widgetGroupId)
     ) {
       Text(entry.title).bold().font( /*@START_MENU_TOKEN@*/.title /*@END_MENU_TOKEN@*/)
     }.buttonStyle(.plain)
  5. With the current setup the Widget is now Interactive as long as the App is still in the background. If you want to have the Widget be able to wake the App up you need to add the following to your AppIntent file

    @available(iOS 16, *)
    @available(iOSApplicationExtension, unavailable)
    extension BackgroundIntent: ForegroundContinuableIntent {}

    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 Jetpack Glance
  1. Add the necessary Receiver and Service to your AndroidManifest.xml file
    <receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"  android:exported="true">
        <intent-filter>
            <action android:name="es.antonborri.home_widget.action.BACKGROUND" />
        </intent-filter>
    </receiver>
    <service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
        android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/>
    
  2. Create a custom Action
    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
    Text(
         title,
         style = TextStyle(fontSize = 36.sp, fontWeight = FontWeight.Bold),
         modifier = GlanceModifier.clickable(onClick = actionRunCallback<InteractiveAction>()),
    )
Android XML
  1. Add the necessary Receiver and Service to your AndroidManifest.xml file
    <receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"  android:exported="true">
        <intent-filter>
            <action android:name="es.antonborri.home_widget.action.BACKGROUND" />
        </intent-filter>
    </receiver>
    <service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
        android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/>
    
  2. Add a HomeWidgetBackgroundIntent.getBroadcast PendingIntent to the View you want to add a click listener to
    val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(
        context,
        Uri.parse("homeWidgetExample://titleClicked")
    )
    setOnClickPendingIntent(R.id.widget_title, backgroundIntent)

Using images of Flutter widgets

In some cases, you may not want to rewrite UI code in the native frameworks for your widgets.

Dart For example, say you have a chart in your Flutter app configured with `CustomPaint`:
class LineChart extends StatelessWidget {
  const LineChart({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: LineChartPainter(),
      child: const SizedBox(
        height: 200,
        width: 200,
      ),
    );
  }
}
Screenshot 2023-06-07 at 12 33 44 PM

Rewriting the code to create this chart on both Android and iOS might be time consuming. Instead, you can generate a png file of the Flutter widget and save it to a shared container between your Flutter app and the home screen widget.

var path = await HomeWidget.renderFlutterWidget(
  const LineChart(),
  key: 'lineChart',
  logicalSize: const Size(400, 400),
);
  • LineChart() is the widget that will be rendered as an image.
  • key is the key in the key/value storage on the device that stores the path of the file for easy retrieval on the native side
iOS To retrieve the image and display it in a widget, you can use the following SwiftUI code:
  1. In your TimelineEntry struct add a property to retrieve the path:

    struct MyEntry: TimelineEntry {
        
        let lineChartPath: String
    }
  2. Get the path from the UserDefaults in getSnapshot:

    func getSnapshot(
        ...
        let lineChartPath = userDefaults?.string(forKey: "lineChart") ?? "No screenshot available"
  3. Create a View to display the chart and resize the image based on the displaySize of the widget:

    struct WidgetEntryView : View {
      
       var ChartImage: some View {
            if let uiImage = UIImage(contentsOfFile: entry.lineChartPath) {
                let image = Image(uiImage: uiImage)
                    .resizable()
                    .frame(width: entry.displaySize.height*0.5, height: entry.displaySize.height*0.5, alignment: .center)
                return AnyView(image)
            }
            print("The image file could not be loaded")
            return AnyView(EmptyView())
        }
    
    }
  4. Display the chart in the body of the widget's View:

    VStack {
            Text(entry.title)
            Text(entry.description)
            ChartImage
        }
Screenshot 2023-06-07 at 12 57 28 PM
Android (Jetpack Glance)
// 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:
    <ImageView
           android:id="@+id/widget_image"
           android:layout_width="200dp"
           android:layout_height="200dp"
           android:layout_below="@+id/headline_description"
           android:layout_alignBottom="@+id/headline_title"
           android:layout_alignParentStart="true"
           android:layout_alignParentLeft="true"
           android:layout_marginStart="8dp"
           android:layout_marginLeft="8dp"
           android:layout_marginTop="6dp"
           android:layout_marginBottom="-134dp"
           android:layout_weight="1"
           android:adjustViewBounds="true"
           android:background="@android:color/white"
           android:scaleType="fitCenter"
           android:src="@android:drawable/star_big_on"
           android:visibility="visible"
           tools:visibility="visible" />
  2. Update your Kotlin code to get the chart image and put it into the widget, if it exists.
    class NewsWidget : AppWidgetProvider() {
       override fun onUpdate(
           context: Context,
           appWidgetManager: AppWidgetManager,
           appWidgetIds: IntArray,
       ) {
           for (appWidgetId in appWidgetIds) {
               // Get reference to SharedPreferences
               val widgetData = HomeWidgetPlugin.getData(context)
               val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
                   // Get chart image and put it in the widget, if it exists
                   val imagePath = widgetData.getString("lineChart", null)
                   val imageFile = File(imagePath)
                   val imageExists = imageFile.exists()
                   if (imageExists) {
                      val myBitmap: Bitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
                      setImageViewBitmap(R.id.widget_image, myBitmap)
                   } else {
                      println("image not found!, looked @: $imagePath")
                   }
                   // End new code
               }
               appWidgetManager.updateAppWidget(appWidgetId, views)
           }
       }
    }

Launch App and Detect which Widget was clicked

To detect if the App has been initially started by clicking the Widget you can call HomeWidget.initiallyLaunchedFromHomeWidget() if the App was already running in the Background you can receive these Events by listening to HomeWidget.widgetClicked. Both methods will provide Uris, so you can easily send back data from the Widget to the App to for example navigate to a content page.

In order for these methods to work you need to follow these steps:

iOS

Add .widgetUrl to your WidgetComponent

Text(entry.message)
    .font(.body)
    .widgetURL(URL(string: "homeWidgetExample://message?message=\(entry.message)&homeWidget"))

In order to only detect Widget Links you need to add the queryParameterhomeWidget to the URL

Android Jetpack Glance

Add an IntentFilter to the Activity Section in your AndroidManifest

<intent-filter>
    <action android:name="es.antonborri.home_widget.action.LAUNCH" />
</intent-filter>

Add the following modifier to your Widget (import from HomeWidget)

Text(
   message,
   style = TextStyle(fontSize = 18.sp),
   modifier = GlanceModifier.clickable(
     onClick = actionStartActivity<MainActivity>(
       context,
       Uri.parse("homeWidgetExample://message?message=$message")
     )
   )
)
Android XML

Add an IntentFilter to the Activity Section in your AndroidManifest

<intent-filter>
    <action android:name="es.antonborri.home_widget.action.LAUNCH" />
</intent-filter>

In your WidgetProvider add a PendingIntent to your View using HomeWidgetLaunchIntent.getActivity

val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity(
        context,
        MainActivity::class.java,
        Uri.parse("homeWidgetExample://message?message=$message"))
setOnClickPendingIntent(R.id.widget_message, pendingIntentWithData)

Background Update

As the methods of HomeWidget are static it is possible to use HomeWidget in the background to update the Widget even when the App is in the background.

The example App is using the flutter_workmanager plugin to achieve this. Please follow the Setup Instructions for flutter_workmanager (or your preferred background code execution plugin). Most notably make sure that Plugins get registered in iOS in order to be able to communicate with the HomeWidget Plugin. In case of flutter_workmanager this achieved by adding:

WorkmanagerPlugin.setPluginRegistrantCallback { registry in
    GeneratedPluginRegistrant.register(with: registry)
}

to AppDelegate.swift

Request Pin Widget

Requests to Pin (Add) the Widget to the users HomeScreen by pinning it to the users HomeScreen.

HomeWidget.requestPinWidget(
    name: 'HomeWidgetExampleProvider',
    androidName: 'HomeWidgetExampleProvider',
    qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider',
);

This method is only supported on Android, API 26+. If you want to check whether it is supported on current device, use:

HomeWidget.isRequestPinWidgetSupported();

Resources, Articles, Talks

Please add to this list if you have interesting and helpful resources

home_widget's People

Contributors

aaronkelton avatar abausg avatar aljkor avatar andyrusso avatar armandsla avatar bierbaumtim avatar bradrushworth avatar colinschmale avatar eliasto avatar hadysata avatar josepedromonteiro avatar kreazyme avatar leonardocustodio avatar linziyou0601 avatar mattrltrent avatar mchudy avatar mgonzalezc avatar milindgoel15 avatar nicolaverbeeck avatar nilsreichardt avatar qwadrox avatar roly151 avatar ronnieeeeee avatar tenhobi avatar ueman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

home_widget's Issues

Target of URI doesn't exist: package

import 'package:home_widget/home_widget_callback_dispatcher.dart';
Show error:
Target of URI doesn't exist: 'package:home_widget/home_widget_callback_dispatcher.dart'.

Undefined name 'HomeWidget'.

But plugin was added, flutter get was called many times.

home_widget: ^0.1.5

Unresolved reference: R

I copied files in kotlin/es/antonborri/home_widget_example and launched main.dart but it says Unresolved reference: R

I think that this line makes error.

val views = RemoteViews(context.packageName, R.layout.example_layout).apply {

Do I have to import R to my project?

How to debug the widget?

Hey!

Sorry for the question, but how can I debug or test this? For example, I am writing code on the flutter side and run the app, but I want to debug the widget and see the information coming to the widget and debug it. How can I achieve this? I have tried running the project on XCode but nothing happens

UIApplication:open handler is conflicting with uni_links plugin

As per Flutter engine implementation UIApplication:open is suppose to return false if link is not handled by the plugin.

https://github.com/flutter/engine/blob/138c91c614d742c52aa5432b4cb921f0ff9fdee2/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm#L313-L327

But in home_widget plugin it is always returning true and this blocks plugins like uni_links to get the URL.

public func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if(isWidgetUrl(url: url)) {
latestUrl = url
}
return true
}

I have a fix in #46 that properly returns the flag.

Possibility to update style of widget

We have a widget that shows the current state, filllevel, ... of an energy storage Unit, and we would like to show either Status: OK in black or Status: ERROR in red?

The data for this widget comes from a rest api, is there any way to programmatically update the textfield to red background as soon as the api returns an error? As far as I could see there is only support for updateWidget and set the text and nothing else?

Unable to instantiate receiver

Hello! I am having problems with the library. I tried to integrate the example from the library into my project, but I got the following problem:

E/AndroidRuntime(27577): java.lang.RuntimeException: Unable to instantiate receiver com.vadimrm.clastere.HomeWidgetProvider: java.lang.ClassNotFoundException: Didn't find class "com.vadimrm.clastere.HomeWidgetProvider" on path: DexPathList[[zip file "/data/app/~~88u_Wa5GQ7Svqajbbr4qdw==/com.vadimrm.clastere-0pEzhjkNxr5w8bTWaCZNsg==/base.apk"],nativeLibraryDirectories=[/data/app/~~88u_Wa5GQ7Svqajbbr4qdw==/com.vadimrm.clastere-0pEzhjkNxr5w8bTWaCZNsg==/lib/x86, /data/app/~~88u_Wa5GQ7Svqajbbr4qdw==/com.vadimrm.clastere-0pEzhjkNxr5w8bTWaCZNsg==/base.apk!/lib/x86, /system/lib, /system_ext/lib]]
E/AndroidRuntime(27577): 	at android.app.ActivityThread.handleReceiver(ActivityThread.java:4018)
E/AndroidRuntime(27577): 	at android.app.ActivityThread.access$1400(ActivityThread.java:237)
E/AndroidRuntime(27577): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1924)
E/AndroidRuntime(27577): 	at android.os.Handler.dispatchMessage(Handler.java:106)
E/AndroidRuntime(27577): 	at android.os.Looper.loop(Looper.java:223)
E/AndroidRuntime(27577): 	at android.app.ActivityThread.main(ActivityThread.java:7656)
E/AndroidRuntime(27577): 	at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(27577): 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
E/AndroidRuntime(27577): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
E/AndroidRuntime(27577): Caused by: java.lang.ClassNotFoundException: Didn't find class "com.vadimrm.clastere.HomeWidgetProvider" on path: DexPathList[[zip file "/data/app/~~88u_Wa5GQ7Svqajbbr4qdw==/com.vadimrm.clastere-0pEzhjkNxr5w8bTWaCZNsg==/base.apk"],nativeLibraryDirectories=[/data/app/~~88u_Wa5GQ7Svqajbbr4qdw==/com.vadimrm.clastere-0pEzhjkNxr5w8bTWaCZNsg==/lib/x86, /data/app/~~88u_Wa5GQ7Svqajbbr4qdw==/com.vadimrm.clastere-0pEzhjkNxr5w8bTWaCZNsg==/base.apk!/lib/x86, /system/lib, /system_ext/lib]]
E/AndroidRuntime(27577): 	at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:207)
E/AndroidRuntime(27577): 	at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
E/AndroidRuntime(27577): 	at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
E/AndroidRuntime(27577): 	at android.app.AppComponentFactory.instantiateReceiver(AppComponentFactory.java:110)
E/AndroidRuntime(27577): 	at androidx.core.app.CoreComponentFactory.instantiateReceiver(CoreComponentFactory.java:60)
E/AndroidRuntime(27577): 	at android.app.ActivityThread.handleReceiver(ActivityThread.java:4011)
E/AndroidRuntime(27577): 	... 8 more
I/Process (27577): Sending signal. PID: 27577 SIG: 9

android/build.gradle

buildscript {
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.0'
        classpath 'com.google.gms:google-services:4.3.5'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

rootProject.buildDir = '../build'
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
    project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app/build.gradle

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

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) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
    compileSdkVersion 29

    lintOptions {
        disable 'InvalidPackage'
    }

    defaultConfig {
        applicationId "com.vadimrm.clastere"
        minSdkVersion 23
        targetSdkVersion 29
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
    buildTypes {
        release {
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }
}

flutter {
    source '../..'
}

project structure:
image
Android manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.vadimrm.clastere">
    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
         calls FlutterMain.startInitialization(this); in its onCreate method.
         In most cases you can leave this as-is, but you if you want to provide
         additional functionality it is fine to subclass or reimplement
         FlutterApplication and put your custom class here. -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-sdk
        android:minSdkVersion="8" />
    <application
        android:label="Clastere"
        android:icon="@mipmap/ic_launcher"
        android:networkSecurityConfig="@xml/network_security_config">
        <provider
            android:name="sk.fourq.otaupdate.OtaUpdateFileProvider"
            android:authorities="${applicationId}.ota_update_provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="secret"/>
        <meta-data
            android:name="com.google.android.gms.ads.AD_MANAGER_APP"
            android:value="true"/>
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <!-- Displays an Android View that continues showing the launch screen
                 Drawable until Flutter paints its first frame, then this splash
                 screen fades out. A splash screen is useful to avoid any visual
                 gap between the end of Android's launch screen and the painting of
                 Flutter's first frame. -->
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <receiver android:name="HomeWidgetProvider" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/widget" />
        </receiver>

        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

HomeWidgetProvider.kt

package com.vadimrm.clastere

import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider

class HomeWidgetExampleProvider : HomeWidgetProvider() {

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
        appWidgetIds.forEach { widgetId ->
            val views = RemoteViews(context.packageName, R.layout.widget).apply {
                // Open App on Widget Click
                val pendingIntent = HomeWidgetLaunchIntent.getActivity(
                        context,
                        MainActivity::class.java)
                setOnClickPendingIntent(R.id.widget_container, pendingIntent)

                // Swap Title Text by calling Dart Code in the Background
                setTextViewText(R.id.widget_title, widgetData.getString("title", null)
                        ?: "No Title Set")
                val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(
                        context,
                        Uri.parse("homeWidgetExample://titleClicked")
                )
                setOnClickPendingIntent(R.id.widget_title, backgroundIntent)

                val message = widgetData.getString("message", null)
                setTextViewText(R.id.widget_message, message
                        ?: "No Message Set")
                // Detect App opened via Click inside Flutter
                val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity(
                        context,
                        MainActivity::class.java,
                        Uri.parse("homeWidgetExample://message?message=$message"))
                setOnClickPendingIntent(R.id.widget_message, pendingIntentWithData)
            }

            appWidgetManager.updateAppWidget(widgetId, views)
        }
    }
}

I am new to android development. I would really appreciate your help

MissingPluginException(No implementation found for method registerBackgroundCallback on channel home_widget)

Fairly new to all of this. I had Android working perfectly and tried to add an ios widget. I'm using workmanager to try to update the widget in the back ground. However I am getting this error.

The error also occurs on the example app.

For me this error occurs when I try to get data from an api using the http package.

[VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: MissingPluginException(No implementation found for method registerBackgroundCallback on channel home_widget)
#0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:156:7)

[VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: PlatformException(Error 8, kCLErrorDomain, The operation couldn’t be completed. (kCLErrorDomain error 8.), null)
#0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:597:7)
#1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:158:18)

#2 LocalGeocoding.findAddressesFromQuery (package:geocoder/services/local.dart:18:28)

#3 _WeatherAppState.getLatLngFromLocation (package:weatherapp/main.dart:614:21)

As I'm new to this - please let me know what you need.

I want support about widget for iOS

I see code: name: 'HomeWidgetExampleProvider', iOSName: 'HomeWidgetExample');

I don't see about clase HomeWidgetExampleProvider
Where are this?
Thank you for support

clickPendingIntentTemplate

Hey,
is there a way to setup pendingIntentTemplate with background click. If I use
"HomeWidgetBackgroundIntent.getBroadcast(context)"
for setPendingIntentTemplate, then data sent with setOnClickFillInIntent is empty

Android : configurable widget

Could you please add an example of a configurable widget on Android, using flutter activity to configure the widget ?

Can reload home widget for platform Android, while platform iOs is not

Hello bro,

I can reload home widget for platform Android,
while platform iOs is not.

I can replace content "..." by other content in platform Android, while can not in platform iOs.
Screen Shot 2020-11-19 at 16 48 22

Please tell me what I need to do more.

Code sample :

I used this in main widget as guide told me.

@override
  void initState() {
    super.initState();
    // HOME_SCREEN_WIDGET_ID : nct_home_widget
    HomeWidget.setAppGroupId(Constants.HOME_SCREEN_WIDGET_ID); 
  }

I'm using these code for update data (it works for platform Android)
Screen Shot 2020-11-19 at 16 12 48
Screen Shot 2020-11-19 at 16 12 41

iOs files structure: (HomeWidgetExample created as Widget Extension in Xcode)
Screen Shot 2020-11-19 at 16 47 15

*.entitlements
Screen Shot 2020-11-19 at 16 14 15

HomeWidgetExample.swift

//
//  widget.swift
//  widget

import WidgetKit
import SwiftUI
import Intents

private let widgetGroupId = "nct_home_widget"

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> ExampleEntry {
        ExampleEntry(date: Date(), title: "Placeholder Title", message: "Placeholder Message")
    }

    func getSnapshot(in context: Context, completion: @escaping (ExampleEntry) -> ()) {
        let data = UserDefaults.init(suiteName:widgetGroupId)
        let entry = ExampleEntry(date: Date(), title: data?.string(forKey: "title") ?? "No Title Set", message: data?.string(forKey: "message") ?? "No Message Set")
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        getSnapshot(in: context) { (entry) in
            let timeline = Timeline(entries: [entry], policy: .atEnd)
            completion(timeline)
        }
    }
}

struct ExampleEntry: TimelineEntry {
    let date: Date
    let title: String
    let message: String
}

struct HomeWidgetExampleEntryView : View {
    var entry: Provider.Entry
    let data = UserDefaults.init(suiteName:widgetGroupId)

    var body: some View {
        VStack.init(alignment: .leading, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: {
            Text(entry.title).bold().font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/)
            Text(entry.message).font(.body)
        }
        )}
}

@main
struct HomeWidgetExample: Widget {
    let kind: String = "nct_home_widget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            HomeWidgetExampleEntryView(entry: entry)
        }
        .configurationDisplayName("nct_home_widget")
        .description("nct_home_widget")
    }
}

struct HomeWidgetExample_Previews: PreviewProvider {
    static var previews: some View {
        HomeWidgetExampleEntryView(entry: ExampleEntry(date: Date(), title: "nct_home_widget", message: "nct_home_widget"))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: type 'bool' is not a subtype of type 'String?' in type cast

i have an error when i click the arrow back on android and then i open the app with the home screen widget.

[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: type 'bool' is not a subtype of type 'String?' in type cast
E/flutter (14900): #0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:158:41)
E/flutter (14900): .

this is the erro, i try to check this, but i can't resolved, it just happen when you go back and then u click the widget to open the app again cause android destroy the app, and then idk why cannot rebuild well.
Destroying service.
D/FlutterLocationService(14900): Creating service.
D/FlutterLocationService(14900): Binding to location service.

Delay Problem on Background Intent

Hi, first of all thanks for your package!

When working with home screen widgets on Android, I realized that the background intent (HomeWidgetBackgroundIntent.getBroadcast) sometimes suddenly gets into a delayed state. I thought I implemented something wrong, but after checking, it seems to also happen with the sample from the package (change title on click).

delay_error.mp4

I tested on emulator (API 29) and also on my phone (API 30), it shows the same behaviour.

If it enters the delayed state, the next update happens >10 seconds after a click, sometimes earlier, or sometimes when you click on the widget to open the app (HomeWidgetLaunchIntent.getActivity).

I tried to find something in the logs but without any success.
So I was wondering if you maybe know, what is causing the delays (and maybe how to avoid them) ?

Update ImageView

Is it possible to update an ImageView in the widget from Flutter assets? I have a weather app and looking to update the weather forecast icon on the widget, but I'm struggling to figure out how to do it. I can update a resource file, but I'd prefer not to have to have two copies. I'm only a beginner, so using this to learn. Any help would be appreciated. Thanks.

Issue updating widget when using IntentTimelineProvider

I have a fully working IntentTimelineProvider that allows me to choose content for the widget to show from inside my flutter application. This works great.

The only issue is I can no longer seem to update the widget using HomeWidget.update am I missing something or is this a limitation?

call _checkForWidgetLaunch in afterInit not in didChangeDependencies

I have an app with 2 pages: Homepage and Quiz. When the user presses the widget on homescreen, the app opens with first screen Homepage and then the code from _checkForWidgetLaunch

  void didChangeDependencies() {
    Debug.d("Homepage didChangeDependencies");
    super.didChangeDependencies();
    //once the app is started from the widget, this is triggered ad infinitum
    //  _checkForWidgetLaunch();
    //  HomeWidget.widgetClicked.listen(_launchedFromWidget);
  }

calls _launchedFromWidget and navigates to Quiz (Navigator.push).

When close Quiz/back button, the Homepage.didChangeDependencies() is called and the Quiz is stared again! This is an infinite cycle.

I managed to fix this by adding to Homepage.initState()

    // //https://stackoverflow.com/questions/49466556/flutter-run-method-on-widget-build-complete
    WidgetsBinding.instance.addPostFrameCallback((_) => _afterInit());

and in afterInit call the code

 _afterInit() async {
    _checkForWidgetLaunch();
    HomeWidget.widgetClicked.listen(_launchedFromWidget);
  }

I hope this helps other people as well. Maybe consider testing and updating documentation?

Thank you very much for your great effort, it's truly a wonderful and incredibly useful component.

Issue with Android Widget: No Widget found with Name HomeWidgetExampleProvider

Hi, I am new to flutter. I am having an issue with configuring on Android.

PlatformException (PlatformException(-3, No Widget found with Name HomeWidgetExampleProvider. Argument 'name' must be the same as your AppWidgetProvider you wish to update, java.lang.ClassNotFoundException: com.ajay.owadio.HomeWidgetExampleProvider at java.lang.Class.classForName(Native Method) at java.lang.Class.forName(Class.java:454) at java.lang.Class.forName(Class.java:379) at es.antonborri.home_widget.HomeWidgetPlugin.onMethodCall(HomeWidgetPlugin.kt:64) at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233) at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85) at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:692) at android.os.MessageQueue.nativePollOnce(Native Method) at android.os.MessageQueue.next(MessageQueue.java:335) at android.os.Looper.loop(Looper.java:183) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) Caused by: java.lang.ClassNotFoundException: Didn't find class "com.ajay.owadio.HomeWidgetExampleProvider" on path: DexPathList[[zip file "/data/app/~~ofh_quxw5tSewumlGyuJXQ==/com.ajay.owadio-mgvxU_hG_RGKA8SOFcf_Gg==/base.apk"],nativeLibraryDirectories=[/data/app/~~ofh_quxw5tSewumlGyuJXQ==/com.ajay.owadio-mgvxU_hG_RGKA8SOFcf_Gg==/lib/x86, /data/app/~~ofh_quxw5tSewumlGyuJXQ==/com.ajay.owadio-mgvxU_hG_RGKA8SOFcf_Gg==/base.apk!/lib/x86, /system/lib, /system_ext/lib]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:207) at java.lang.ClassLoader.loadClass(ClassLoader.java:379) at java.lang.ClassLoader.loadClass(ClassLoader.java:312) ... 14 more , null))

I am unable to solve the issue

Failed to register background callback

Hi,

when I try to register a background callback to register clicks on a home widget, I get a ClassCastException in HomeWidgetPlugin.kt:

E/MethodChannel#home_widget(11269): Failed to handle method call
E/MethodChannel#home_widget(11269): java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
E/MethodChannel#home_widget(11269): at es.antonborri.home_widget.HomeWidgetPlugin.onMethodCall(HomeWidgetPlugin.kt:99)
E/MethodChannel#home_widget(11269): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233)
E/MethodChannel#home_widget(11269): at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85)
E/MethodChannel#home_widget(11269): at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:818)
E/MethodChannel#home_widget(11269): at android.os.MessageQueue.nativePollOnce(Native Method)
E/MethodChannel#home_widget(11269): at android.os.MessageQueue.next(MessageQueue.java:326)
E/MethodChannel#home_widget(11269): at android.os.Looper.loop(Looper.java:160)
E/MethodChannel#home_widget(11269): at android.app.ActivityThread.main(ActivityThread.java:6669)
E/MethodChannel#home_widget(11269): at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#home_widget(11269): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
E/MethodChannel#home_widget(11269): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
E/flutter (11269): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: PlatformException(error, java.lang.Integer cannot be cast to java.lang.Long, null, java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
E/flutter (11269): at es.antonborri.home_widget.HomeWidgetPlugin.onMethodCall(HomeWidgetPlugin.kt:99)
E/flutter (11269): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233)
E/flutter (11269): at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85)
E/flutter (11269): at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:818)
E/flutter (11269): at android.os.MessageQueue.nativePollOnce(Native Method)
E/flutter (11269): at android.os.MessageQueue.next(MessageQueue.java:326)
E/flutter (11269): at android.os.Looper.loop(Looper.java:160)
E/flutter (11269): at android.app.ActivityThread.main(ActivityThread.java:6669)
E/flutter (11269): at java.lang.reflect.Method.invoke(Native Method)
E/flutter (11269): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
E/flutter (11269): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
E/flutter (11269): )
E/flutter (11269): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:581:7)
E/flutter (11269): #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:158:18)
E/flutter (11269):
E/flutter (11269):

When I try clicking on a part of a home widget I get an error:

E/HomeWidgetBackgroundService(11269): No callbackHandle saved. Did you call HomeWidgetPlugin.registerBackgroundCallback?

And then the app crashes.

I think I found a solution for this problem.
If I change

val dispatcher = (call.arguments as Iterable<*>).toList()[0] as Long
and the next line to:

val dispatcher = (call.arguments as Iterable<*>).toList()[0].toString().toLong()

val callback = (call.arguments as Iterable<*>).toList()[1].toString().toLong()

The issue with casting is gone and app registers a background callback and everything works as expected.

Best regards,
Aljosa

No Widget found with Name HomeWidgetExampleProvider

After following set up instructions and comparing with example app, I'm not sure what the issue might be.

In my AndroidManifex.xml I have:

       <receiver android:name="HomeWidgetExampleProvider" >
           <intent-filter>
               <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/home_widget_example" />
       </receiver>

And in my android/app/src/main/kotlin/com/someName/app/anotherName/HomeWidgetExampleProvider.kt I have the same code from the example project.
I did add Kotlin support to my Flutter app which was originally configured as Java. After sorting our some build issues, MainActivity.kt path seems to be working, but HomeWidgetExampleProvider.kt in the same directory is not?

Any ideas?

EDIT: I forgot to mention, the example widget will build fine and has the time when initially created, but this error is still thrown and also thrown on subsequent updates from Workmanager periodic task. Time will remain the same as when created.

Button in iOS Widget

I see that the registerBackgroundCallback feature released in 0.1.2 version is not available in iOS. Is there a limitation in iOS platform?
I have seen that widgets click in iOS can just open app using DeepLink (like already implemented in that plugin) but I am not sure about it.

I appreciate any answer.

Add click button on widget

Hello,

I applied this library success,
I add more feature when using this.

  • Show title & message (done)
  • A button for integrate : Play button (doing)
    Screen Shot 2020-11-16 at 14 17 26

I did not found any document details the method like setOnClickListener() (or setTextViewText()) to apply (Even Ctril+Space does not help me also)
So how I implemented a clicked button success?

Please help me indicate that?

Code lines :
Screen Shot 2020-11-16 at 14 17 54
Screen Shot 2020-11-16 at 14 18 11

[✓] Flutter (Channel stable, 1.22.3, on macOS 11.0.1 20B29, locale en-VN)
• Flutter version 1.22.3 at /Users/huytd/flutter
• Framework revision 8874f21e79 (2 weeks ago), 2020-10-29 14:14:35 -0700
• Engine revision a1440ca392
• Dart version 2.10.3

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
• Android SDK at /Users/huytd/android-sdk
• Platform android-30, build-tools 30.0.2
• ANDROID_HOME = /Users/huytd/android-sdk
• Java binary at: /Applications/Android
Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build
1.8.0_242-release-1644-b3-6915495)
• All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.2)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 12.2, Build version 12B45b
• CocoaPods version 1.10.0

[!] Android Studio (version 4.1)
• Android Studio at /Applications/Android Studio.app/Contents
✗ Flutter plugin not installed; this adds Flutter specific functionality.
✗ Dart plugin not installed; this adds Dart specific functionality.
• Java version OpenJDK Runtime Environment (build
1.8.0_242-release-1644-b3-6915495)

[✓] Connected device (1 available)
• Android SDK built for x86 (mobile) • emulator-5554 • android-x86 • Android
7.1.1 (API 25) (emulator)

Exception "Receiver not registered" when exit app

Hello,

I only do one thing,
It is import your library and run app,
After I exit app manually,
I got exception immediately, as image in below :

Screen Shot 2021-04-13 at 15 09 31

In case I don't import your library, It's okay
Screen Shot 2021-04-13 at 15 08 01

I'm sure this bug appear from previous version until latest version.
Please prevent this bug,

Thanks,
p/s : Please notice that I hasn't declare < receiver > tag in manifest xml yet_

I got these error logs :

D/AndroidRuntime(26991): Shutting down VM
E/AndroidRuntime(26991): FATAL EXCEPTION: main
E/AndroidRuntime(26991): java.lang.RuntimeException: Unable to destroy activity {.MainActivity}: java.lang.IllegalArgumentException: Receiver not registered: null
E/AndroidRuntime(26991): at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4203)
E/AndroidRuntime(26991): at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4221)
E/AndroidRuntime(26991): at android.app.ActivityThread.-wrap6(ActivityThread.java)
E/AndroidRuntime(26991): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1538)
E/AndroidRuntime(26991): at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime(26991): at android.os.Looper.loop(Looper.java:154)
E/AndroidRuntime(26991): at android.app.ActivityThread.main(ActivityThread.java:6119)
E/AndroidRuntime(26991): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(26991): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
E/AndroidRuntime(26991): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
E/AndroidRuntime(26991): Caused by: java.lang.IllegalArgumentException: Receiver not registered: null
E/AndroidRuntime(26991): at android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:1007)
E/AndroidRuntime(26991): at android.app.ContextImpl.unregisterReceiver(ContextImpl.java:1330)
E/AndroidRuntime(26991): at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:608)
E/AndroidRuntime(26991): at es.antonborri.home_widget.HomeWidgetPlugin.onDetachedFromActivity(HomeWidgetPlugin.kt:150)
E/AndroidRuntime(26991): at io.flutter.embedding.engine.FlutterEngineConnectionRegistry.detachFromActivity(FlutterEngineConnectionRegistry.java:389)
E/AndroidRuntime(26991): at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onDetach(FlutterActivityAndFragmentDelegate.java:560)
E/AndroidRuntime(26991): at io.flutter.embedding.android.FlutterActivity.release(FlutterActivity.java:595)
E/AndroidRuntime(26991): at io.flutter.embedding.android.FlutterActivity.onDestroy(FlutterActivity.java:616)
E/AndroidRuntime(26991): at android.app.Activity.performDestroy(Activity.java:6881)
E/AndroidRuntime(26991): at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1153)
E/AndroidRuntime(26991): at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4190)
E/AndroidRuntime(26991): ... 9 more

[✓] Flutter (Channel master, 2.2.0-11.0.pre.69, on macOS 11.2.3 20D91
darwin-x64, locale en-VN)
• Flutter version 2.2.0-11.0.pre.69 at /Users/huytd/flutter
• Framework revision 02efffc134 (5 days ago), 2021-04-10 03:49:01 -0400
• Engine revision 8863afff16
• Dart version 2.13.0 (build 2.13.0-222.0.dev)

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
• Android SDK at /Users/huytd/android-sdk
• Platform android-30, build-tools 30.0.2
• ANDROID_HOME = /Users/huytd/android-sdk
• Java binary at: /Applications/Android
Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build
1.8.0_242-release-1644-b3-6915495)
• All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 12.4, Build version 12D4e
• CocoaPods version 1.10.1

[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build
1.8.0_242-release-1644-b3-6915495)

[✓] Connected device (1 available)
• Chrome (web) • chrome • web-javascript • Google Chrome 89.0.4389.128

• No issues found!

How to display a image on home widget android?

I found it was really hard to make this image be round radius!!

any suggestions?

package cn.xx.xx

import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.widget.RemoteViews
import cn.manaai.daybreak.R

import es.antonborri.home_widget.HomeWidgetBackgroundIntent
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider

import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
import com.bumptech.glide.load.resource.bitmap.RoundedCorners

class HomeWidgetGlanceProvider : HomeWidgetProvider(), AppCompatActivity {
    // this load a todo widget, showing todos here
    // so the layout here is different.

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
        appWidgetIds.forEach { widgetId ->
            val views = RemoteViews(context.packageName, R.layout.glance_app_widget).apply {
                // Open App on Widget Click
                val pendingIntent = HomeWidgetLaunchIntent.getActivity(
                        context,
                        MainActivity::class.java)
                setOnClickPendingIntent(R.id.widget_container, pendingIntent)

                // Swap Title Text by calling Dart Code in the Background
                setTextViewText(R.id.nickname, widgetData.getString("title", null)
                        ?: "No Title Set")
                val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(
                        context,
                        Uri.parse("homeWidgetExample://titleClicked2")
                )
                setOnClickPendingIntent(R.id.nickname, backgroundIntent)

                val message = widgetData.getString("message", null)
                setTextViewText(R.id.todonum, message
                        ?: "12")
                // Detect App opened via Click inside Flutter
                val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity(
                        context,
                        MainActivity::class.java,
                        Uri.parse("homeWidgetExample://message?message=$message"))
                setOnClickPendingIntent(R.id.todonum, pendingIntentWithData)

                var avatar = findViewById(R.id.avatar) as ImageView;
                // avatar
                Glide.with(this).load("http://p15.qhimg.com/bdm/720_444_0/t01b12dfd7f42342197.jpg")
                        .apply(RequestOptions.bitmapTransform(RoundedCorners(20)))
//                        .circleCrop()
                        .into(avatar)

            }

            appWidgetManager.updateAppWidget(widgetId, views)
        }
    }
}

Getting Run Time Exception on Android 11

Fatal Exception: java.lang.RuntimeException: Unable to create service es.antonborri.home_widget.HomeWidgetBackgroundService: java.lang.NullPointerException: Attempt to read from field 'java.lang.String io.flutter.view.FlutterCallbackInformation.callbackLibraryPath' on a null object reference
at android.app.ActivityThread.handleCreateService(ActivityThread.java:4673)
at android.app.ActivityThread.access$1700(ActivityThread.java:301)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2196)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

Possibility to set custom font

We have tried to use a custom Font called Rubik:
https://fonts.google.com/specimen/Rubik

Neither by passing the font to the widget itself, nor with attaching a shared resource from flutter to the native android part. Is this a known limit (of the package/ general) for android as the home widget always inherits the system font?

ListView Help

I have been trying to figure out how to add a ListView with programmatically updatable TextViews within the ListView but I am unsure of how to accomplish this. Does anyone have any suggestions?

Thank you

How can I create listView?

Hello!

This is great package for me that not good at Android and iOS.

I want to create home screen widget like gmail ( topbar + listview widget ) in Android
screen-3
)

Can I create a listview widget with this package?
And if it is possible, how do we do it?

How to allow user select value on iOS ?

When the user long presses the HomeWidget, the Event selector will be displayed, and then select the corresponding value to display
How should it be achieved?
like this:
IMG_5095

Is it possible to know when a home screen widget has been added?

Hi,

At the moment I am registering a workmanager periodic task in my app's initstate to update my widget in the background. This works but it is registering & running the task even though there may be no widgets on the home screen for my app. I'm not sure if there is a way to know when the user adds a widget to the home screen so the workmanager task can be registered at that moment? Or is there another point I should be registering the workmanager task? Your example app uses a button, but that wouldn't work in this instance. So the outcome I'd like is to register the workmanager periodic task when a widget is added.

Thankyou!

Multiple Widgets

Is it possible to have multiple widgets that update in the background? I have managed to get two widgets, but cannot get one of them to update in the background.

I have two HomeWidgetProvider Classes, two widget info xml's, two layout xml's, and have two receivers in the manifest. I only have one of the following - I'm assuming this is okay?
<receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"> <intent-filter> <action android:name="com.resortstylebeanbags.localweatherau.action.BACKGROUND" /> </intent-filter> </receiver> <service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/>

Update widget every second

I tried this but doesn't work:
Workmanager.registerPeriodicTask('1', 'widgetBackgroundUpdate', frequency: Duration(seconds: 1));
get this error:
Interval duration lesser than minimum allowed value; Changed to 900000. That's the 15 default minutes. Is not possible to reduce this limit anyhow?

My app shows how much money you earn/pay by second so I need a widget to show also same info without opening the app (more info: https://play.google.com/store/apps/details?id=com.drodriguez.profits)

Failed to create plugin placeholder

Hi, After your #53 update, I have just started trying to get my app working on iOS. I had done quite a bit of work on it, so I'm not sure if this is still related, but now I am getting this error:

An error was encountered processing the command (domain=IXErrorDomain, code=2):
Failed to create plugin placeholder for /app path/build/ios/iphonesimulator/Runner.app/PlugIns/HomeWidgetExtension.appex
Failed to create promise.
Underlying error (domain=IXErrorDomain, code=2):
Failed to set placeholder attributes {app path & name}.HomeWidget
Failed to create promise.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.