Skip to content

Tutorial: make a widget

Alex Speller edited this page Feb 6, 2014 · 23 revisions

Goal

Make a widget that can be placed on the home screen. The widget should display some current information and respond to user interaction. It should be configurable at install time.

Prerequisites

This tutorial has been tested with the following setups

Platform JDK ant Ruby ruboto RubotoCore Device API level Tester
OS X 10.8.2 1.7.0_09 1.8.2 MRI 1.8.7 0.10.0.rc.1 0.4.9 Samsung Galaxy S3 android-15 donv
OS X 10.8.2 1.7.0_09 1.8.2 MRI 1.8.7 0.10.0.rc.1 0.4.9 Emulator android-15 donv

Create the app, install, and run it

Connect your device.

ruboto gen app --package org.ruboto.example.widget --target android-15
cd widget
rake update_scripts:restart

You should see the standard "What hath Matz wrought?" screen.

A fresh app

Generate your widget provider

ruboto gen subclass android.appwidget.AppWidgetProvider --name WidgetProvider --method_base on

Declare your widget in the manifest

Add the following lines to your AndroidManifest.xml file, at the end of the application tag:

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

Add the widget info

res/xml/widget_info.xml

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="86400000"
    android:previewImage="@drawable/ic_launcher"
    android:initialLayout="@layout/widget"
    android:configure="org.ruboto.example.widget.WidgetConfigure" 
    android:resizeMode="horizontal|vertical"
/>

Edit the widget provider Java source

Edit the file src/org/ruboto/example/widget/WidgetProvider.java. Change the generated code for onReceive.

Remove this line near the top of the file:

		ScriptLoader.loadScript(this);

and edit the onReceive method.

Change

    if (!JRubyAdapter.isInitialized()) {
      Log.i("Method called before JRuby runtime was initialized: WidgetProvider#onReceive");
      {super.onReceive(context, intent); return;}
    }

to this:

    if (!JRubyAdapter.setUpJRuby(context)) {
      Log.e("Unable to initialize the JRuby runtime.");
      {super.onReceive(context, intent); return;}
    }
    ScriptLoader.loadScript(this);

Add the widget provider code

Edit the src/widget_provider.rb file to this:

require 'ruboto/base'
require 'ruboto/package'
require 'widget_configure'

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.os.SystemClock
import android.util.Log
import android.widget.RemoteViews

class WidgetProvider
  import org.ruboto.example.widget.R

  TAG = 'WidgetProvider'

  def onUpdate(context, appWidgetManager, appWidgetIds)
    Log.d(TAG, "onUpdate")
    appWidgetIds.each do |appWidgetId|
      titlePrefix = WidgetConfigure.loadTitlePref(context, appWidgetId)
      WidgetProvider.updateAppWidget(context, appWidgetManager, appWidgetId, titlePrefix)
    end
  end

  def onDeleted(context, appWidgetIds)
    Log.d(TAG, "onDeleted")
    appWidgetIds.each do |appWidgetId|
      WidgetConfigure.deleteTitlePref(context, appWidgetId)
    end
  end

  def onEnabled(context)
    Log.d(TAG, "onEnabled")
  end

  def onDisabled(context)
    Log.d(TAG, "onDisabled")
    PackageManager pm = context.getPackageManager()
  end

  def self.updateAppWidget(context, appWidgetManager, appWidgetId, titlePrefix)
    Log.d(TAG, "updateAppWidget appWidgetId=#{appWidgetId} titlePrefix=#{titlePrefix}")
    text = context.getString(R.string.widget_text_format, WidgetConfigure.loadTitlePref(context, appWidgetId))
    views = RemoteViews.new(context.getPackageName(), R.layout.widget)
    views.setTextViewText(Ruboto::Id.text, text)
    appWidgetManager.updateAppWidget(appWidgetId, views)
  end
end

Add the widget layout

Add a new file res/layout/widget.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#88ffffff"
    android:textColor="#ffff0000"
    android:text="Hullo!"
    android:gravity="center"
    android:textSize="36pt"
/>

Add the display text format string

Edit res/values/strings.xml and add the following line inside the resources tag:

  <string xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" name="widget_text_format"><xliff:g id="prefix">%1$s</xliff:g></string>

Generate the configuration activity

ruboto gen class Activity --name WidgetConfigure

Declare the configuration activity

Edit the AndroidManifest.xml and change

        <activity android:name='WidgetConfigure'/>

to this

<activity android:name="WidgetConfigure">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
    </intent-filter>
</activity>

This will fire the WidgetConfigure activity when a new widget is created. It will allow us to set parameters for each widget instance.

Add the configuration activity code

Edit src/widget_configure.rb to this:

require 'ruboto/widget'

import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import android.widget.EditText

import java.util.ArrayList

import org.ruboto.example.widget.R

ruboto_import_widgets :Button, :EditText, :LinearLayout, :TextView

class WidgetConfigure
  TAG = "WidgetConfigure"

  PREFS_NAME = "org.ruboto.example.widget.WidgetProvider"
  PREF_PREFIX_KEY = "prefix_"

  def on_create(bundle)
    super
    set_title 'Ruboto Widget Configuration'
    @@mAppWidgetId = AppWidgetManager::INVALID_APPWIDGET_ID
    setResult(RESULT_CANCELED)

    self.content_view =
        linear_layout :orientation => :vertical do
          @text_view = text_view :text => 'Configure me!', :id => 42, 
                                 :layout => {:width => :match_parent},
                                 :gravity => :center, :text_size => 48.0
          @mAppWidgetPrefix = edit_text :id => 43, :layout => {:width => :match_parent}
          button :text => 'Save', :layout => {:width => :match_parent}, 
                                  :id => 44, :on_click_listener => proc {|v| configure(v) }
        end
    intent = getIntent()
    extras = intent.getExtras()
    if (extras != nil)
        @mAppWidgetId = extras.getInt(
                AppWidgetManager::EXTRA_APPWIDGET_ID, AppWidgetManager::INVALID_APPWIDGET_ID)
    end
    if (@mAppWidgetId == AppWidgetManager::INVALID_APPWIDGET_ID)
        finish()
    end

    @mAppWidgetPrefix.setText(WidgetConfigure.loadTitlePref(self, @mAppWidgetId))

  rescue Exception
    puts "Exception creating activity: #{$!}"
    puts $!.backtrace.join("\n")
  end

  def configure(v)
    context = self
    titlePrefix = @mAppWidgetPrefix.getText().toString()
    WidgetConfigure.saveTitlePref(context, @mAppWidgetId, titlePrefix)
    appWidgetManager = AppWidgetManager.getInstance(context)
    WidgetProvider.updateAppWidget(context, appWidgetManager, @mAppWidgetId, titlePrefix)
    resultValue = Intent.new()
    resultValue.putExtra(AppWidgetManager::EXTRA_APPWIDGET_ID, @mAppWidgetId.to_java(:int))
    setResult(RESULT_OK, resultValue)
    finish()
  end

  def self.saveTitlePref(context, appWidgetId, text)
        prefs = context.getSharedPreferences(PREFS_NAME, 0).edit()
        prefs.putString("#{PREF_PREFIX_KEY}#{appWidgetId}", text)
        prefs.commit()
  end

  def self.loadTitlePref(context, appWidgetId)
    prefs = context.getSharedPreferences(PREFS_NAME, 0)
    prefix = prefs.getString("#{PREF_PREFIX_KEY}#{appWidgetId}", nil)
    if (prefix != nil)
      return prefix
    else
      return "Hullo!"
    end
  end

  def self.deleteTitlePref(context, appWidgetId)
  end

  def self.loadAllTitlePrefs(context, appWidgetIds, texts)
  end
end

Add a usage screen

Edit *src/widget_activity.rb to this:

require 'ruboto/widget'

java_import android.content.pm.PackageManager
ruboto_import_widgets :LinearLayout, :TextView

class WidgetActivity
  def on_create(bundle)
    super
    set_title 'Ruboto Flashlight'
    flash_available = package_manager.hasSystemFeature(PackageManager::FEATURE_CAMERA_FLASH)

    self.content_view =
        linear_layout :orientation => :vertical do
          @text_view = text_view :text => flash_available ? 'Place the Flashlight widget on your home screen.' : 'No flash available.',
                                 :id => 42, :layout => {:width => :match_parent}, 
                                 :gravity => :center, :text_size => 48.0
        end
  rescue
    puts "Exception creating activity: #{$!}"
    puts $!.backtrace.join("\n")
  end
end

Install the app

rake install

Use the widget

Navigate to your app list, and select to see the widgets. You should find a widget called "Widget". Long-click it, and then drag it to a free space on your home screen. The configuration activity will pop up. Set a custom display text and click "Save". The text you wrote will appear in the widget on the home screen.

Congratulations!

Clone this wiki locally