better way to start a service from a home screen app widget - android

I have the following code in my sample app. I'm starting a service from an app widget when a button is clicked. The service's job is to play a short audio clip that depends on which button was clicked.
The app widget contains two buttons (PREV and NEXT). On click events are handled in onUpdate().
From different guides that I've found online I can start the service in two ways:
by creating a broadcast in onUpdate() method and handle it later in onReceive() method of the extended AppWidgetProvider class (as in the sample code for PREV button).
or by calling startService() via a PendingIntent in onUpdate() (as NEXT button).
Which of both is better practice or more commonly used? Thank you
<!-- widget_player.xml -->
<LinearLayout
android:id="#+id/player_controls"
android:layout_width="match_parent"
android:layout_height="30dp"
android:orientation="horizontal" >
<ImageButton
android:id="#+id/btn_player_prev"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="#android:drawable/ic_media_previous" />
<ImageButton
android:id="#+id/btn_player_next"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="#android:drawable/ic_media_next" />
</LinearLayout>
WidgetPlayer class
public class WidgetPlayer extends AppWidgetProvider {
public static String ACTION_WIDGET_PREV = "action.WIDGET_PREV";
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
final int nPlayerWidgets = appWidgetIds.length;
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_player);
for (int i = 0; i < nPlayerWidgets; i++) {
int appWidgetId = appWidgetIds[i];
updateWidgetPlayer(context, appWidgetManager);
Intent intent;
PendingIntent actionPendingIntent;
// PREV button
intent = new Intent(context, WidgetPlayer.class);
intent.setAction(ACTION_WIDGET_PREV);
actionPendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.btn_player_prev, actionPendingIntent);
// NEXT button (not using a broadcast)
intent = new Intent(context, PlayerService.class);
intent.setAction(PlayerService.ACTION_NEXT);
actionPendingIntent = PendingIntent.getService(context, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.btn_player_next, actionPendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
}
#Override
public void onReceive(Context context, Intent intent) {
AppWidgetManager manager = AppWidgetManager.getInstance(context.getApplicationContext());
if (intent.getAction().equals(ACTION_WIDGET_PREV)) {
Intent iPrev = new Intent(PlayerService.ACTION_PREV);
iPrev.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetMetaPlayerIds);
context.startService(iPrev);
updateWidgetPlayer(context, manager);
}
// handle more actions here
else {
super.onReceive(context, intent);
}
}
}

The NEXT button code looks more concise so it is better choice if it does what you want. With the PREVIOUS code you have the extra step of receiving the broadcast which appears unnecessary.
You can pass along the id's in the intent to the service if you need those.

Related

Widget custom icon Android

I would like to make a custom image for a widget, i tried
views.setImageViewResource(R.id.red_button, R.drawable.button_default);
and
Bitmap icon = BitmapFactory.decodeResource(context.getResources(),
R.drawable.button_default);
views.setImageViewBitmap(R.id.red_button, icon);
but it says problem loading widget all the time, no matter what I try, am I missing something? can someone point me to the right documantion/what to do?
Edit:
full xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_green_dark"
android:orientation="vertical"
android:padding="#dimen/widget_margin">
<ImageButton
android:id="#+id/red_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Button"
app:srcCompat="#drawable/button_default" />
</LinearLayout>
code:
public class NewAppWidget extends AppWidgetProvider {
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
CharSequence widgetText = context.getString(R.string.appwidget_text);
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
Intent intent = new Intent(context, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.red_button, pi);
views.setTextViewText(R.id.red_button, widgetText);
Bitmap icon = BitmapFactory.decodeResource(context.getResources(),
R.drawable.button_default);
views.setImageViewBitmap(R.id.red_button, icon);
//views.setImageViewResource(R.id.red_button, R.drawable.button_default);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
#Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
#Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
}
Thanks!
The error message ("problem loading widget") is not caused by any problems with displaying the drawable, it is showing because of this line in your code:
views.setTextViewText(R.id.red_button, widgetText);
You can only use setTextViewText(int, CharSequence) with resource ids for Views extending from TextView (according to the docs, it's equivalent to TextView.setText(CharSequence) ).
The resource id R.id.red_button belongs to an ImageButton which extends from ImageView not from TextView, that's why you get the error message.
So if you want to show some text you need to add a Textview to your app widget's layout.

Android working with views in widget

I am trying to make a widget for my app, and I want it to be with a buttonm progress bar and text view, which would later be changed, so I need to somehow work with the views themselves, like I can from an activity, show a view, hide a view, change a button background etc...
I tried tweaking with the sample code from the Android documentations, but I can only start an activity with this
public class WidgetProvider extends AppWidgetProvider {
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
int appWidgetId = appWidgetIds[i];
Intent intent = new Intent(context, ActivityMain.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
views.setOnClickPendingIntent(R.id.sync_button, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
#Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
}
}
Does the widget need to be connected to an activity in order to make actions (connect to the internet, write in the database, write in the objects of the apps instance (if there is one))? And can I work with views the way I am asking?
Outside of setting on-click PendingIntents on views, there isn't much you can do inside of a widget. The standard recipe is:
Make a PendingIntent that sends a broadcast (PendingIntent.getBroadcast()) and set it on the appropriate view in your widget.
In the BroadcastReceiver that receives the intent, you update the widget to show a spinner and start a Service to do whatever long-running work you want to do (e.g connect to the internet).
Once that work is done, you can update your widget again and remove the spinner.

Widget click gives back last activity

When I click on my application widget, instead of going back to the last activity I left open (when going into the home screen), Android restarts my application from scratch.
Is there a way to make that click behave exactly like my app icon, or like the "recent apps manager"?
Here is how I implemented the widget:
public class WidgetLauncher extends AppWidgetProvider {
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
Intent active = new Intent(context, COPD_Main.class);
active.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, active, 0);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.launch_support);
views.setOnClickPendingIntent(R.id.launch_support_launcher, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
PS: I am aware there are several questions around like:
this
but it did not fix my problem at all.
I believe the problem is the Intent flag you are using. Try to use:
active.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
isntead of NEW_TASK.
From the docs:
If set, the activity will not be launched if it is already running at
the top of the history stack.

Widget ImageButton listener not allways called

I have a simple Widget (Android 2.1) containing just a LinearLayout, itself containing an ImageButton.
The ImageButton has a on-click listener.
The problem is: If I put several of this same widget on my home screen, some are working (listener called when button pressed), and some are not! I cannot see any pattern in which are working and which are not.
Here is the widget layout:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/ImageButton01"
android:src="#drawable/widget_running"
android:background="#null">
</ImageButton>
And here is the widget provider code:
public class GPAppWidgetProvider extends AppWidgetProvider {
private String mTag = getClass().getSimpleName();
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;
Log.e(mTag, "onUpdate ");
// Perform this loop procedure for each App Widget that belongs to this
// provider
for (int i = 0; i < N; i++) {
Log.e(mTag, "widget onUpdate one loop");
int appWidgetId = appWidgetIds[i];
// Create an Intent
Intent intent = new Intent(context, GPService.class).setAction(ACTION_WIDGET_TOGGLE_PAUSE);
intent.putExtra("widgetId", appWidgetId);
PendingIntent pauseIntent = PendingIntent.getService(context, 0, intent, 0);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.gp_appwidget);
views.setOnClickPendingIntent(R.id.ImageButton01, pauseIntent);
// widget update
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
I had the same problem.
Don't forget to set a different "requestCode" when you call "getService" :
public static PendingIntent getService (Context context, int requestCode, Intent intent, int flags)
And make sure your "appWidgetId" is different for each widget.

Button click lost on widget when screen is rotated

I have a very simple widget application which consists of a LinearLayout with a background and an ImageButton.
In the AppWidgetProvider onUpdate() method, I register the click of the button to broadcast an intent. When the widget first loads, everything runs fine and the click is captured. The problem occurs when the screen is rotated, and the click is never captured again even if the screen is rotated back.
What do I have to do to re-register the click when the screen rotates?
below is some segments of code I am using.
AppWidgetProvider
#Override
public void onReceive(Context context, Intent intent)
{
super.onReceive(context, intent);
if(intent.getAction().equals("test.CLICK"))
{
CallTestMethod(context);
}
}
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
final int N = appWidgetIds.length;
// Perform this loop procedure for each App Widget that belongs to this provider
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
RemoteViews views=new RemoteViews(context.getPackageName(), R.layout.widget);
Intent clickintent=new Intent("test.CLICK");
PendingIntent pendingIntentClick=PendingIntent.getBroadcast(context, 0, clickintent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.change_mode, pendingIntentClick);
SetInitialLayout(context);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
Manifest
<receiver android:name=".Widget" android:label="#string/widget_name">
<intent-filter>
<action android:name="android.appwidget.action.ACTION_APPWIDGET_CONFIGURE" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="test.CLICK" />
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="#xml/widget_mode_switcher" />
</receiver>
Layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/widget_layout"
android:layout_width="140dip"
android:layout_height="140dip"
android:scaleType="fitCenter"
android:orientation="vertical"
android:background="#drawable/background">
<ImageButton
android:id="#+id/change_mode"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitCenter"
android:orientation="vertical"
android:src="#drawable/none_selected"
android:background="#null"
android:clickable="true" />
</LinearLayout>
Thank you anyone for your help!
This helped me: Android widget ImageButton loses image when screen is rotated
In short, you have to register the clicks (views.setOnClickPendingIntent) before EVERY call to awm.updateAppWidget
I used a solution which requires a service on the widgetapp because it can handle orientation changes to the widgetapp. Unfortunately onReceive or onUpdate doesn't get called by orientation changes, but the service's onConfigurationChanged does get called. You need to have your service constantly running to detect these orientations as well. When the service detects the orientation change, then you proceed to change the remote view.
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
context.startService(new Intent(context, MyUpdateService.class));
}
This is the service class that you need to implement. You can look at this if you need more information about the service. http://android-developers.blogspot.com/2009/04/introducing-home-screen-widgets-and.html
public static class MyUpdateService extends Service
{
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public void onStart(Intent intent, int startId)
{
super.onStart(intent, startId);
// Update the widget
RemoteViews remoteView = buildRemoteView(this);
// Push update to homescreen
pushUpdate(remoteView);
}
public RemoteViews buildRemoteView(Context context)
{
int layoutID = R.layout.widget;
if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
layoutID = R.layout.widget_landscape;
}
//Here is where you set your onclick listeners again since the remote views need to be refreshed/recreated
RemoteViews updateView = new RemoteViews(context.getPackageName(),layoutID);
// Create an Intent to launch ExampleActivity
Intent intent = new Intent(this, yourAndroidActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
intent, 0);
updateView.setOnClickPendingIntent(R.id.yourClickableViewHere, pendingIntent);
return updateView;
}
#Override
public void onConfigurationChanged(Configuration newConfig)
{
RemoteViews remoteView = buildRemoteView(this);
// Push update to home screen
pushUpdate(remoteView);
}
private void pushUpdate(RemoteViews updateViews)
{
ComponentName myWidget = new ComponentName(this, YourAppWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
//This is where you can update your remoteViews
updateViews.setTextViewText(R.id.YOUR_TEXTVIEW_ON_WIDGET, "" + "TEXT THAT WILL SHOW UP");
manager.updateAppWidget(myWidget, updateViews);
}
}
}
As I understand it, Android actually kills and recreates your activity every time the screen is rotated. Yuck, I know.
So anyway, I suspect if you put log statements in all the various lifecycle callbacks, you'll find that update is only called the one time. You probably need to handle listening for clicks in another callback. I couldn't tell you which one without checking some reference material. I'll leave that as an exercise to the reader.
hay have you use
android:configChanges="keyboardHidden|orientation"
with your Activity in Androidmanifest file.
I am new to android, but I am fairly certain that this way of doing it will work.
#Override
public void onReceive(Context context, Intent intent)
{
super.onReceive(context, intent);
if(intent.getAction().equals("test.CLICK"))
{
getIntent().putExtra("Just received click", true);
CallTestMethod(context);
}
}
Then in the onCreate you can see if it should recreate the click event by checking getIntent().getBooleanExtra("Just received click", false) (false refers to the default value. If that code returns true, then the above code did it's job and you should recreate the click event)
This should work because the intent object (and it's extras) are saved, even if your app is temporarily killed. The intent will accept any Serializable extra.

Categories

Resources