When I first saw this requirement, I didn’t think much about it, but after Google took a dip, I realized that desktop widgets were only native, so it was inevitable to use hybrid stack development. The back end came halfway to write flutter, didn’t learn the original, so I mumbled all the time. After the winter vacation, I decided to learn kotlin and Android native development. This article is a document of my journey to incorporate Android desktop components into the Flutter project.

MethodChannel

This MethodChannel can be used to implement mutual calls between Flutter and Native. I originally thought to write a plugin to provide update and data interaction functions, but I found that someone had written it on GitHub.

The project address

This plugin uses SharedPreferences to upload data. The flutter side stores data using the Save method. The native side inherits the HomeWidgetProvider class and overwrites the Update method. Part of the source code is as follows, you can see the example of the plug-in.

The flutter end

HomeWidget.saveWidgetData<String> ('title', _titleController. Text); HomeWidget.saveWidgetData<String> ('message', _messageController. Text);Copy the code

native

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 {
                //Open App on Widget Click
                setOnClickPendingIntent(R.id.widget_container,
                        PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0))
                setTextViewText(R.id.widget_title, widgetData.getString("title".null)
                        ?: "No Title Set")
                setTextViewText(R.id.widget_message, widgetData.getString("message".null)
                        ?: "No Message Set")
            }

            appWidgetManager.updateAppWidget(widgetId, views)
        }
    }
}
Copy the code

Data parsing

At first I did press the demo and use SharedPreferences to read and write data, but there was a problem with that. The home_widget doesn’t support transferring List

data.

The setStringList and getStringList methods are available on the flutter side when the official SharedPreferences plugin is used, but neither of these methods is available on the native side. I found the problem by looking at the official plug-in source code.

List<String> list = call.argument("value");
commitAsync(preferences.edit().putString(key, LIST_IDENTIFIER + encodeList(list)), result);
Copy the code

You can see that it actually changes the List

to a single String through an encoding function and stores it. So if the flutter side uses list-type data, you might have to add encoding and decoding methods to the native side, which is a bit of a hassle. In the end, I gave up on this approach and chose to use database storage on both ends and use the ORM framework.

Dynamic update of widgets

I read the official documents for the compilation of widgets. I wrote the styles of widgets in the Layout folder and wrote the basic information of widgets in the INFO file in the XML folder.


      
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialKeyguardLayout="@layout/course_widget"
    android:initialLayout="@layout/course_widget"
    android:minWidth="320dp"
    android:minHeight="110dp"
    android:previewImage="@drawable/widget_preview"
    android:updatePeriodMillis="3600000"
    android:widgetCategory="home_screen" />
Copy the code

The android: updatePeriodMillis defined is team by calling the onUpdate () callback method to request from AppWidgetProvider update frequency. This is what the official documentation says about the Settings for this item.

There is no guarantee that actual updates will occur exactly on time at this value, and we recommend keeping the update frequency as low as possible – perhaps no more than once an hour to save battery life. You can also allow users to adjust the frequency in their configuration – some people might want the ticker to update every 15 minutes, others might want it to update only four times a day.

By asking seniors, I learned that this item can only be set to 30 minutes at the minimum, that is, 1800000. By this point, my task would have been solved, but in fact, 30 minutes later, an hour later, and no updates. After the application background is closed, it cannot be updated. Although our requirement is only to update once an hour, this is not possible at present. The home_widget plugin provides a dynamic refresh example using WorkManager, so I wrote the same version, and found that it would not work after two or three hours. In addition, in the process of Baidu, I found that some vendors even block this API… Then I thought about how to keep the background alive, using the front desk service method, and tried for a few days, found that after the restart, it does not work…

Look carefully at the document

I was about to give up when I looked at the official Android documentation and it was clear.

Note: If the device is dormant at the time of this update (as defined by updatePeriodMillis), the device awakens to perform the update. If you don’t update more than once an hour, this probably won’t cause serious problems with battery life. However, if you need to update more frequently and/or do not need to update while the device is asleep, you can instead perform updates based on an alarm that does not wake the device up. To do this, use AlarmManager to set up an alarm with the Intent that the AppWidgetProvider will receive. Set the alarm type to ELAPSED_REALTIME or RTC so that the alarm will sound only when the device is awake. Then, set updatePeriodMillis to zero (“0”).

Using AlarmManager, through actual measurement, it is feasible and the problem is solved.

val pending = PendingIntent.getBroadcast(context, 0, intent, 0)
val alarm: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarm.cancel(pending)
val interval = 1000 * 60.toLong()
alarm.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), interval, pending)
Copy the code

legacy

Desktop components have different sizes on onePlus phones. The component that says 52 is displayed on OnePlus, but 51 is displayed on OnePlus. (It should be a problem of the onePlus system itself, the user responded that after upgrading the system, the widget display is normal.)