Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Cordova

How to Create an Android Application Widget using the Apache Cordova Plugin ACE

5.00/5 (2 votes)
1 Dec 2017CPOL3 min read 21.1K  
This post contains steps on how to set up an Android application widget in an hybrid mobile application using Apache Cordova and the plugin ACE from Microsoft.

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:

XML
<?xml version='1.0' encoding='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).

XML
<?xml version="1.0" encoding="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.

XML
<?xml version="1.0" encoding="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.

XML
<?xml version="1.0" encoding="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).

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

Java
public void onCreate() {

AppWidgetData.add("Run the app to populate this widget.", _context);
// TODO: Should we refresh data on app resume in some cases?
    }

So, let's add the JavaScript code the ACE documentation suggest to the start page:

JavaScript
if (ace.platform == "Android") {
  setupWidget();
}

function setupWidget() {
  // Handle the app being resumed by a widget click:
  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) {
        // value is the index of the item clicked
        // or -1 if no item has been clicked
    });
}

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:

JavaScript
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)