Introduction
I wanted to build a mobile hybrid application with the format of an Android application widget that can provide you information at the home screen.
Since I've been using for other projects Apache cordova, I looked for a solution with it and I found a plug in called ACE. It was build by Microsoft. I had a hard time making it work, so I decided to write what needs to be done step by step.
Background
It helped me a lot to understand how an Android application widget works, to read the Android official documentation at https://developer.android.com/guide/topics/appwidgets/index.html.
Also, you should take a look to Microsoft ACE plug in official documentation for the application widget at http://microsoft.github.io/ace/docs/native-ui/#four.
Using the Code
These are the steps you should follow to create an application widget using ACE:
Step 1: Add the plug in to Your Project
Following instructions at the github ACE home page (https://github.com/Microsoft/ace), you can use:
cordova plug in add plug in
Or in Visual Studio, open config.xml at the root of your project and under plugins, custom make it point to https://github.com/microsoft/ace.git or to a local copy.
Step 2: Customize Your AndroidManifest.xml
At the project's platform/Android, you will find the AndroidManifest.xml automatically generate for your project. We need to modify it to add the widget.
Ace documentation tells you to copy this XML to the native/android/res folder of your project, but like this your changes will be replicated to the platform/android manifest during building.
The manifest must be copied at native/android (outside the res folder) and the final result should be like this:
='1.0'='utf-8'
<manifest android:hardwareAccelerated="true" android:versionCode="yourversion"
android:versionName="versionname" package="your.package.name"
xmlns:android="http://schemas.android.com/apk/res/android">
<supports-screens android:anyDensity="true" android:largeScreens="true"
android:normalScreens="true" android:resizeable="true" android:smallScreens="true"
android:xlargeScreens="true" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:hardwareAccelerated="true" android:icon="@drawable/icon"
android:label="@string/app_name" android:supportsRtl="true">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"
android:label="@string/activity_name" android:launchMode="singleTop"
android:name="MainActivity" android:windowSoftInputMode="adjustResize">
<intent-filter android:label="@string/launcher_name">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="your.package.name.ListWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/list_widget_info" />
</receiver>
<service android:exported="false" android:name="run.ace.AppWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<activity android:name="run.ace.AceActivity"
xmlns:android="http://schemas.android.com/apk/res/android" />
</application>
<uses-sdk android:minSdkVersion="yourminsdk" android:targetSdkVersion="yourtargetsdk" />
</manifest>
Step 3: Create the Application Widget Provider Info
It's an XML file describing the metadata of the application widget among other properties: used layout, the size (my example uses four columns and one row of the home screen), if it's resizable, etc.
This file should be at native/android/res/xml folder and should be called list_widget_info.xml (matching the android:resource
attribute of the meta-data of the receiver at the manifest).
="1.0"="utf-8"
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="250dp"
android:minHeight="40dp"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/list_widget_preview"
android:initialLayout="@layout/list_widget_layout">
</appwidget-provider>
Step 4: Add a Preview Image for the Widget
Add the preview image as native/android/res/drawable/list_widget_preview.png. It will be the one displayed in the list of widgets.
This image may be the same used for the application, which is at res/icons/android at your project.
Step 5: Add the Widget Layout
The layout used by the widget should be added as native/android/res/layout/list_widget_layout.xml.
This widget will display a list. Here, you could change styles as background color or font color.
="1.0"="utf-8"
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_widget_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:loopViews="true" />
Step 6: Define the Widget Layout Item
The following should be saved as native/android/res/layout/list_widget_item.xml. ACE documentation wrongly called it list_widget_view, where list_widget_view is the id of the layout and not the item.
="1.0"="utf-8"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_widget_item"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/list_widget_item_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColor="#ffffff"
android:textSize="40px" />
</FrameLayout>
Step 7: Add the Widget Provider Class
This code should be added as native/android/src/your/package/name/ListWidgetProvider.java.
It should match the receiver Android name at the manifest (your.package.name.ListWidgetProvider).
package your.package.name;
public class ListWidgetProvider extends run.ace.AppWidgetProvider
{
@Override
protected int getLayoutResourceId(android.content.Context context)
{
return run.ace.NativeHost.getResourceId("list_widget_layout", "layout", context);
}
@Override
protected int getViewResourceId(android.content.Context context)
{
return run.ace.NativeHost.getResourceId("list_widget_view", "id", context);
}
@Override
protected int getItemResourceId(android.content.Context context)
{
return run.ace.NativeHost.getResourceId("list_widget_item", "id", context);
}
@Override
protected int getItemTextResourceId(android.content.Context context)
{
return run.ace.NativeHost.getResourceId("list_widget_item_text", "id", context);
}
@Override
protected int getItemLayoutResourceId(android.content.Context context)
{
return run.ace.NativeHost.getResourceId("list_widget_item", "layout", context);
}
}
Step 8: Populate the Application Widget
ACE provides you with an AppWidgetData
class and an AppWidgetService
class.
If you run the application at this stage, the widget will display the default message the AppWidgetService
assigns to the AppWidgetData
at creation:
public void onCreate() {
AppWidgetData.add("Run the app to populate this widget.", _context);
}
So, let's add the JavaScript code the ACE documentation suggest to the start page:
if (ace.platform == "Android") {
setupWidget();
}
function setupWidget() {
ace.addEventListener("android.intentchanged", checkForWidgetActivation);
ace.android.appWidget.clear();
for (var i = 0; i < 10; i++) {
ace.android.appWidget.add("Item with index " + i);
}
}
function checkForWidgetActivation() {
if (ace.platform != "Android") {
return;
}
ace.android.getIntent().invoke("getIntExtra", "widgetSelectionIndex", -1, function (value) {
});
}
You will have the following error:
ace is no defined.
For this object to be defined, code added shouldn't be called before the device is ready like this:
document.addEventListener('deviceready', onDeviceReady.bind(this), false);
function onDeviceReady() {
if (ace.platform == "Android") {
setupWidget();
}
};
Next, you should customize the content of the application widget with something different to "Item with index " + i and everything should be working fine.
Points of Interest
You could research more about the Android app widget by simply creating one in Android Studio.
History
- 1st December, 2017: Initial version