Button click lost on widget when screen is rotated - android

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.

Related

How can i run methods in AppWidget?

i learned today how to develop AppWidget and my question is, how can i run methods in the app widget?
example:
I have a button in the xml and i want the button calculate something.
-the part of create the widget works fine and now i want to add some methods to the button.
how does its works in "App Widgets"?
here is my code:
public class WorkClockWidget extends AppWidgetProvider {
MySharedPreferences shared;
RemoteViews views;
private Context context;
private String text;
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
for (int i = 0; i < appWidgetIds.length; i++) {
views = new RemoteViews(context.getPackageName(), R.layout.clock_widget);
/// i have button in the "R.layout.clock_widget" .
//what i need to do if i want the button run the "someText()" method?
appWidgetManager.updateAppWidget(appWidgetIds[i], views);
}
}
//The method i want to run when i press on the button.
public String someText(){
System.out.println("Works!!!");
return "Test if this method works";
}
}
One more question:
If i want my widget add data to my database, i have to use contentProvider?
You can't call a method directly, but you can fire an Intent. One way would do this would be to have an Intent send a broadcast, using PendingIntent.getBroadcast. (I'm not sure you need the category in there, but that's how I do that in my own code, so I'm including it in this example.)
Intent intent = new Intent("com.myapp.button_press").addCategory("com.myapp");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.widget_button, pendingIntent);
Next you need a BroadcastReceiver to receive that broadcast. One sneaky way to do that is to use your AppWidgetProvider class as the receiver, since that what an app widget really is. You would have to modify the manifest entry for your app widget to include the Intent that you've created in onUpdate:
<receiver android:name=".widget.MintAppWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="com.myapp.button_press"/>
<category android:name="com.myapp"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="#xml/my_provider_info"/>
</receiver>
Also, when you override onReceive, be sure to call super.onReceive if the Intent is not your special Intent, so that the base AppWidgetProvider class can process the normal intent and call onUpdate.
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if ("com.myapp.button_press".equals(action)) {
// First handle your special intent action
someText();
} else {
// otherwise let Android call onUpdate
super.onReceive(context, intent);
}
}

Android widget fails to start activity when clicked first time

I have an android widget which is actually a button to start an activity.The widget works fine using this code but there is a small bug :
When I add the widget to the home screen , clicking it first time doesn't do anything , second time it works. Then it works normally from 1st click. And the same thing happens when the phone is restarted.Can someone help me solve this problem ??
MyWidgetIntentReceiver.java
public class MyWidgetIntentReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("Start")) {
onUpdate(context);
}
}
private void onUpdate(Context context) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.widget_demo);
Intent configIntent = new Intent(context, WidgetFlash.class);
configIntent.putExtra("widget", 1);
configIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent configPendingIntent = PendingIntent.getActivity(context,
0, configIntent, 0);
remoteViews.setOnClickPendingIntent(R.id.widget_button,
configPendingIntent);
MyWidgetProvider.pushWidgetUpdate(context.getApplicationContext(),
remoteViews);
} }
MyWidgetProvider.java :
public class MyWidgetProvider extends AppWidgetProvider {
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_demo);
remoteViews.setOnClickPendingIntent(R.id.widget_button, buildButtonPendingIntent(context));
pushWidgetUpdate(context, remoteViews);
}
public static PendingIntent buildButtonPendingIntent(Context context) {
Intent intent = new Intent();
intent.setAction("Start");
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public static void pushWidgetUpdate(Context context, RemoteViews remoteViews) {
ComponentName myWidget = new ComponentName(context, MyWidgetProvider.class);
AppWidgetManager manager = AppWidgetManager.getInstance(context);
manager.updateAppWidget(myWidget, remoteViews);
}}
Widget in Manifest:
<receiver android:name="com.hybernect.flashlight.MyWidgetProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="#xml/demo_widget_provider" />
</receiver>
<receiver
android:name="com.hybernect.flashlight.MyWidgetIntentReceiver"
android:label="widgetBroadcastReceiver" >
<intent-filter>
<action android:name="Start" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="#xml/demo_widget_provider" />
</receiver>
It looks to me like you might be very new to Android widgets, and are missing some of the basics that you need to know. My advice is to find some more tutorials other than the one you worked off, to compare different ways that widgets are handled.
I wrote a simple widget as a demo tutorial - it contains all the boilerplate code required for a widget, and very little else:
WiFi Widget Demo (github)
WiFi Widget (Play store)
I wrote it in such a way to make it easy for anyone to remove the "wifi" related code, and adapt it to their own widget requirements. It might be perfect for you to look at, and relatively simple to add a single button to it.
You are mixing up the different onUpdate() methods in your MyWidgetIntentReceiver and MyWidgetProvider. Below I will try to explain what is wrong with the code that you gave in your original question.
When you add the widget to the home screen, MyWidgetProvider.onUpdate() is called, which in turn calls buildPendingIntent():
public static PendingIntent buildButtonPendingIntent(Context context) {
Intent intent = new Intent();
intent.setAction("Start");
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
This creates a PendingIntent that will send a broadcast with action set to "Start", when the button is clicked.
And that is what happens. The button is clicked, you send a broadcast with "Start" in it, and this is received by your MyWidgetIntentReceiver.
I do not understand why you have chosen to do it this way, as you could rather just start the activity from this class.
When your MyWidgetIntentReceiver.onReceive() method runs, it calls its own onUpdate() method.
(Note: this is poor practice; try to do as little as possible within a BroadcastReceiver)
This new onUpdate() method sets the click action to launch the activity:
Intent configIntent = new Intent(context, WidgetFlash.class);
configIntent.putExtra("widget", 1);
configIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent configPendingIntent = PendingIntent.getActivity(context,
0, configIntent, 0);
remoteViews.setOnClickPendingIntent(R.id.widget_button,
configPendingIntent);
And so the second time you click the button, the WidgetFlash activity is launched.
To summarise:
your first update calls PendingIntent.getBroadcast(), which sends "Start" to the receiver
your second update calls PendingIntent.getActivity(), which starts the WidgetFlash
I do not understand the need for your MyWidgetIntentReceiver in this code.

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

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.

Android AppWidget onClick not working after garbage collection

I created an AppWidget for my App and setup the updatePeriodMillis to 0,
because this Widget is not doing anything, if the user does not interact.
Everything works fine, untill Android cleans the ram. Then the widget won't respond anymore until the App is started again or the device is rebooted (in both cases the onUpdate() will run again).
So my question: What do I need to do, to bring it back to work, after Android kicked out the Application?
This is part of the manifest:
<receiver android:name="WidgetProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="PATH.widgetBtnStartClicked" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="#xml/appwidget_provider_info" />
</receiver>
This is part of my WidgetProvider:
public class WidgetProvider extends AppWidgetProvider {
private static final String BTN_START_CLICKED = "PATH.widgetBtnStartClicked";
private static Values values;
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
// get RemoteView (widget):
for (int i = 0; i < appWidgetIds.length; i++) {
int appWidgetId = appWidgetIds[i];
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget);
// Register onClick for App-start-button:
Intent intentLaunch = new Intent(BTN_APP_LAUNCH_CLICKED);
intentLaunch.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntentLaunch = PendingIntent.getBroadcast(
context, appWidgetId, intentLaunch,
PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_btn_launch,
pendingIntentLaunch);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
#Override
public void onReceive(Context context, Intent intent) {
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget);
super.onReceive(context, intent);
AppWidgetManager appWidgetManager = AppWidgetManager
.getInstance(context);
ComponentName componentName = new ComponentName(context,
WidgetProvider.class);
if (intent.getAction().equals(BTN_APP_LAUNCH_CLICKED)) {
//do some stuff..
}
// update views
appWidgetManager.updateAppWidget(componentName, views);
}
I hope there is everything you need to understand the problem. Just tell me, if not!
I think your app relies only on onUpdate() to refresh the widget. There are other events that cause the pending intents to 'drop off' the widget.
Such as the RAM clearing you mention.
I recommend you:
+ put your widget update code in a separate service class
+ have that class triggered when different events happen. onUpdate() and other events, example, the one that causes your RAM to be cleared.
+ ensure you update everything each time with remoteviews, because no old pending intents etc are preserved.
You can take total control over the events that trigger the widget to update.
If it suits your situation, you can also set an arbitrary alarm, using the alarmmanager and a receiver class. in this way, you can set the alarm to only be received when the phone wakes, and use that to call your update service class. in your update service class, then clear any alarm (if the update is called from another event) and set another alarm.
There are lots of questions on SO about who to use a service with widgets. It shouldnt take long to work it out. To put that info here is outside of the scope of this question.

Android appwidget service won't start

When I'm running in debugging mode I can't seem to reach any breakpoints that are inside of the service, why is that?
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
context.startService(new Intent(context, UpdateService.class));
}
public static class UpdateService extends Service {
#Override
public void onStart(Intent intent, int startId) {
// Build the widget update for today
RemoteViews updateViews = buildUpdate(this);
// Push update for this widget to the home screen
ComponentName thisWidget = new ComponentName(this, WidgetProvider.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, updateViews);
}
public RemoteViews buildUpdate(Context context) {
return new RemoteViews(context.getPackageName(), R.id.widget_main_layout);
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
The "onUpdate"-method is only executed if the widget is initalized (e.g. put on the homescreen) or the updatePeriodMillis are expired. If you want to execute the service e.g. by a click on the widget, you have to "attach" a pending intent like this:
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final Intent intent = new Intent(context, UpdateService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
// Get the layout for the App Widget and attach an on-click listener to
// the button
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout....);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
for(int i=0,n=appWidgetIds.length;i<n;i++){
int appWidgetId = appWidgetIds[i];
appWidgetManager.updateAppWidget(appWidgetId , views);
}
(cleaned up version of a working widget).
The point is, that the onUpdate() method is really very seldom executed. The real interaction with a widget is specified through pending intents.
Your Service might not be registered in the manifest. Or your AppWidgetProvider might not be registered in the manifest.
You might want to think of not using a service for what you're doing. If it's just running the updateViews() once a day then consider just setting android:updatePeriodMillis to 86400000 in the XML file that's linked to your appwidget. Your XML file would look something like this:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="72dp"
android:maxWidth="72dp"
android:updatePeriodMillis="86400000" >
</appwidget-provider>
This will have android update your appwidget once a day without having a service run in the background that might get killed by a task killer that the user is running which then stops your widget from updating. Just a note, if you need it to update faster than every 30 minutes then android:updatePeriodMillis won't work (it's minimum value is 30 minutes) at that point I'd recommend using an AlarmManager since that'll use up less battery than a Service and also won't be killed by task killers.

Categories

Resources