I am using AlarmManager to update my widgets. And I want to stop it if there is no widget on homescreen. But I am facing a problem with detecting if there is no widget on home screen.
As whenever I try to get the AppWidgetIds using this way:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIDs = appWidgetManager
.getAppWidgetIds(new ComponentName(context, Widget.class));
I get the a length of appWidgetIDs while actually there is no widget on homescreen. Why?
Therefore, I would like to know if there is a way to detect that a widget id is exists on homescreen.
Thank you upfront.
Congratulations, you've encountered phantom appwidgets. It appears to be documented on the Android issue tracker. They usually occur when the configuration activity for an appwidget is canceled, though it seems to be through improper implementation of the configuration activity; developers neglect to include the appwidget ID as an extra when setting the activity result to RESULT_CANCELED. (even Google's ApiDemos sample application neglects to do this!)
The proper implementation is like this:
public class AppWidgetConfigActivity extends Activity {
private int appWidgetId;
private Intent resultValue;
protected void onCreate(bundle saved) {
super.onCreate(saved);
// get the appwidget id from the intent
Intent intent = getIntent();
appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
// make the result intent and set the result to canceled
resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
setResult(RESULT_CANCELED, resultValue);
// if we weren't started properly, finish here
if (appwidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
/* ... */
}
/* ... */
private void finishConfigure() {
/* finish configuring appwidget ... */
setResult(RESULT_OK, resultValue);
}
}
Thus far I know of no way to detect the presence of a phantom appwidget without doing your own bookkeeping. I suggest storing a SharedPreferences value indicating that the configuration activity was not canceled and then querying this value in your other code. You can also use this information to "delete" a phantom widget if you come across one. In your appwidget configuration activity:
private void finishConfigure() {
/* finish configuring appwidget ... */
setResult(RESULT_OK, resultValue);
String key = String.format("appwidget%d_configured", appwidgetId);
SharedPreferences prefs = getSharedPreferences("widget_prefs", 0);
prefs.edit().putBoolean(key, true).commit;
}
Then you can check that you have at least one non-phantom appwidget like so:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
AppWidgetHost appWidgetHost = new AppWidgetHost(context, 1); // for removing phantoms
SharedPreferences prefs = getSharedPreferences("widget_prefs", 0);
boolean hasWidget = false;
int[] appWidgetIDs = appWidgetManager.getAppWidgetIds(new ComponentName(context, Widget.class));
for (int i = 0; i < appWidgetIDs.length; i++) {
int id = appWidgetIDs[i];
String key = String.format("appwidget%d_configured", id);
if (prefs.getBoolean(key, false)) {
hasWidget = true;
} else {
// delete the phantom appwidget
appWidgetHost.deleteAppWidgetId(id);
}
}
if (hasWidget) {
// proceed
} else {
// turn off alarms
}
Related
Good Day! I want to add text from inside application to widget, i have a main activity and it has list view and lots of text contents and it has a button to add the text to widget via shared preference, it's works fine when i close the widget and recreate it only, otherwise it's not automatically refresh.if anyone know; how to solve this please help me. here i attached the widget code below.
public class WidgetMaster extends AppWidgetProvider {
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
Intent intentHome = new Intent(context, MainActivity.class);
PendingIntent pendingIntentHome = PendingIntent.getActivity(context, 0, intentHome, 0);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_master);
views.setOnClickPendingIntent(R.id.wid_home, pendingIntentHome);
final SharedPreferences sharedPreferencestoWi = context.getSharedPreferences(String.valueOf(R.string.addTextToWidgetPref), MODE_PRIVATE);
String forWidget = sharedPreferencestoWi.getString("textToWidget", "");
String dum = "add from reading";
if(forWidget.equals("")){
views.setTextViewText(R.id.dum_appwidget_text, dum);
appWidgetManager.updateAppWidget(appWidgetId, views);
} else {
views.setTextViewText(R.id.appwidget_text, forWidget);
views.setViewVisibility(R.id.appwidget_text, 0);
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
}
}
How do you update the widget from mainActivity?
In the widget configuration file you can specify a fixed update interval. The smallest update interval is 1800000 milliseconds (30 minutes).
But its better to update widget programmatically, to do this You should send a broadCast to update widget and use a
method like alarmManager or Handler for a repeating task for example you can use the following broadCast to update widget form mainActivity:
Intent intent = new Intent(this, WidgetMaster.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
int[] ids = AppWidgetManager.getInstance(getApplication())
.getAppWidgetIds(new ComponentName(getApplication(),WidgetMaster.class));
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
sendBroadcast(intent);
I am trying to run a widget in my app. The widget data (sync frequency, username, etc) is decided in the WidgetConfig class. It then opens a widget provider, which creates the widget, and lastly a service updates the views and makes the server calls to update data.
Here is some of my code:
//First Widget config is called:
public class WidgetConfig extends Activity{
//Stuff happens here to get data from EditTexts and spinners and converts
//them to strings.
//Eventually a button is pressed which enters all the information:
public void onClick(View v) {
//Creates a unique string with the app widget ID
String str = Integer.toString(appWidgetId);
sp.putString(editor, str + "::" + "username", user_name);
//The appWidgetID was unique and so I thought it would work as an
//identifier for shared prefs.
//This is the intent for opening the provider
Intent intentUpdate = new Intent(context, MailWidgetProvider.class);
//I left out the rest of the pending update code as it is irrelevant to this.
}
}
//Next the AppWidgetProvider is called
public class MailWidgetProvider extends AppWidgetProvider {
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
ComponentName thisWidget = new ComponentName(context,
MailWidgetProvider.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
//This is the intent to open up and run the service
Intent intent = new Intent(context.getApplicationContext(),
MailWidgetUpdateService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);
context.startService(intent);
}
}
//Service Class
public class MailWidgetUpdateService extends Service {
public void onStart(Intent intent, int startId) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this
.getApplicationContext());
int[] allWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
ComponentName thisWidget = new ComponentName(getApplicationContext(),
MailWidgetProvider.class);
int[] allWidgetIds2 = appWidgetManager.getAppWidgetIds(thisWidget);
//Loop through the IDs
for (int widgetId : allWidgetIds) {
int awid = widgetId;
String str = Integer.toString(widgetId);
String user_name = sp.getString(settings, str + "::" + "chosen_accout_string", "Loading...");
Log.d(TRACKING_USERNAME, user_name);
/*
Issue Here, see explanation below
*/
}
}
Although I am updating my shared preferences with a unique identifier and the respective data
String str = Integer.toString(appWidgetId);
sp.putString(editor, str + "::" + "username", user_name);)
It would look like this: ("2749::username)
Whenever I run the code in the service class to pull the data, it loops through what SHOULD only be 1 app widget ID (seems like 2 in the array for some reason) and pulls the data.
The first loop pulls nothing and either gives an incorrect default value (Like loading... above) or a null pointer if I do not have a default value.
How do I retrieve the data in my Service class from the shared preferences when it seems to be passing more than one widget ID for only 1 widget?
I am trying to create an application with a widget. When the user places the widget on the desktop a listview should come up with a list of items. The user selects an item then the widget is created with the respective text related to that item. For simplicity I am using now an alertdialog. The input text should appear only in the widget I create. It should not appear in the other widgets. How am I supposed to do that? I have not found anyhing about this.
The AppWidgetProvider:
public class MyWidgetProvider extends AppWidgetProvider {
private static final String ACTION_CLICK = "ACTION_CLICK";
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.i("INFO", "AppWidgetProvider");
ComponentName thisWidget = new ComponentName(context, MyWidgetProvider.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
//Intent intent = new Intent(context.getApplicationContext(), UpdateWidgetService.class);
//intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);
//context.startService(intent);
Intent intentA = new Intent(context.getApplicationContext(), NoteListToWidget.class);
intentA.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intentA);
}
}
The service:
public class UpdateWidgetService extends Service {
public static String ACTION_WIDGET_CONFIGURE = "ConfigureWidget";
public static String ACTION_WIDGET_RECEIVER = "ActionReceiverWidget";
String value;
Dialog dialog_newitem;
#Override
public void onStart(Intent intent, int startId) {
Log.i("INFO", "UpdateWidgetService");
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this.getApplicationContext());
int[] allWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
ComponentName thisWidget = new ComponentName(getApplicationContext(), MyWidgetProvider.class);
int[] allWidgetIds2 = appWidgetManager.getAppWidgetIds(thisWidget);
for (int widgetId : allWidgetIds) {
int number = (new Random().nextInt(100));
String v = GlobalVars.getValue();
RemoteViews remoteViews = new RemoteViews(this.getApplicationContext().getPackageName(), R.layout.widget_layout);
//remoteViews.setTextViewText(R.id.update, "Random: " + String.valueOf(number));
remoteViews.setTextViewText(R.id.update, v);
Intent configIntent = new Intent(this, Notepad.class);
configIntent.setAction(ACTION_WIDGET_CONFIGURE);
PendingIntent configPendingIntent = PendingIntent.getActivity(this, REQUEST_CODE_ONE, configIntent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.widgetlayout, configPendingIntent);
appWidgetManager.updateAppWidget(widgetId, remoteViews);
}
stopSelf();
super.onStart(intent, startId);
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
This is the Activity with the AlertDialog (the latter listview):
public class NoteListToWidget extends Activity {
Dialog dialog_newitem;
EditText et_newitem;
String value;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dialog_newitem = new Dialog(NoteListToWidget.this);
dialog_newitem.setContentView(R.layout.dialog_productlists_grp_capitalized);
dialog_newitem.setTitle("Select");
dialog_newitem.setCancelable(true);
et_newitem = (EditText) dialog_newitem.findViewById(R.id.et_item_name);
Button btn_Save = (Button) dialog_newitem.findViewById(R.id.btn_save_pr);
btn_Save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
value = et_newitem.getText().toString();
GlobalVars.setValue(value);
dialog_newitem.dismiss();
/*
Intent serviceIntent = new Intent();
serviceIntent.setAction("com.myapp.app.UpdateWidgetService");
startService(serviceIntent);
*/
Intent intent = new Intent(NoteListToWidget.this, com.myapp.app.UpdateWidgetService.class);
startService(intent);
}
});
Button btn_Cancel = (Button) dialog_newitem.findViewById(R.id.btn_cancel_pr);
btn_Cancel.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog_newitem.dismiss();
}
});
dialog_newitem.show();
}
}
I don't how and from where should I call the NoteListToWidget activity and how to make this whole thing work.
First of all it is impossible to answer your question in full. This is because it seems you are missing a lot of things about how app-widgets are working, so you must read more about them. However I will give you a place to start.
The onUpdate method of the AppWidgetProvider is called each time an app-widget that it is already placed on the desktop needs an update. That is, even if you manage to display your list from there and the user cancels the operation there is NO way to remove your app-widget from the onUpdate method.
Furthermore when the onUpdate method finishes the AppWidgetProvider is destroyed, so even if you manage to display your list from there there is NO way to get the items that user selected. Long story short, you canNOT do it from onUpdate method and you canNOT do it by this approach.
However, you CAN do it by using a configuration activity in a specified point of the app-widget's creation process but this cannot be answered in the context of your question because you must first understand how the app-widget system works in Android. The best place to start is this link. I had the same problems with my first app-widget application (and almost the same concerns with you) but then I started to read and read, finally after 1.5 months I 've managed to make something like the thing you want to do. So don't say
How am I supposed to do that? I have not found anyhing about this
and start reading, this is the life with Android. It's all in the above link, there are also very good open-source projects in google code like this weather widget which allows the user to configure it BEFORE it is placed on the desktop.
Hope this helps...
Can someone tell me how to check that my widget have been placed on the homescreen?
I have some code in my app that should run only if the widget is placed on the homescreen.
Just saying, but...
int ids[] = AppWidgetManager.getInstance(this).getAppWidgetIds(new ComponentName(this,MyAppWidgetProvider.class));
Toast.makeText(this, "Number of widgets: "+ids.length, Toast.LENGTH_LONG).show();
You need to store that information yourself. I usually use the application preferences, but you could use anything. Generally widgets use services to communicate, so your code that does stuff is likely in a service, but using the preference allows any portion of your app to access this.
In your widget class that extends AppWidgetProvider the onEnabled is called when the widget is put on a homescreen and the onDeleted is (usually) called when it's removed. onDisabled is called when all copies are removed.
So in the code of your widget provider:
#Override
public void onEnabled(Context context) {
super.onEnabled(context);
setWidgetActive(true);
context.startService(new Intent(appContext, WidgetUpdateService.class));
}
#Override
public void onDisabled(Context context) {
Context appContext = context.getApplicationContext();
setWidgetActive(false);
context.stopService(new Intent(appContext, WidgetUpdateService.class));
super.onDisabled(context);
}
private void setWidgetActive(boolean active){
Context appContext = context.getApplicationContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(Constants.WIDGET_ACTIVE, active);
edit.commit();
}
Elsewhere in code, you would check to see if the widget is active by:
public boolean isWidgetActive(Context context){
Context appContext = context.getApplicationContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean(Constants.WIDGET_ACTIVE, false);
}
I know it's an old question, but looking at this today I saw that there are a couple of problems with the accepted answer from #larsona1:
if the user cleared the shared preferences - there's still widget, but the app won't know about it.
if the user regret between "add widget" and before pressing "ok" - onEnabled will be called anyway, and a widget will be registered in the home screen even though there is no widget, and no way to remove it later. (it may be a bug in ADT home launcher).
I found a solution to the first problem.
No shared preferences are needed at all, since it's unreliable anyway. It has to be checked in runtime.
// in some class you define a static variable, say in S.java
static boolean sWidgetMayExist = true;
In your widget provider:
// MyAppWidgetProvider.java
// to respond to runtime changes, when widgets are added and removed
#Override
public void onEnabled(Context context) {
super.onEnabled(context);
S.sWidgetMayExist = true;
}
#Override
public void onDisabled(Context context) {
super.onDisabled(context);
S.sWidgetMayExist = true;
}
And, in your service code add this:
AppWidgetManager manager = null;
RemoteViews views = null;
ComponentName widgetComponent = null;
// ..and in your update thread
if (!S.sWidgetMayExist) { return; }
if (manager == null || widgetComponent == null) {
widgetComponent = new ComponentName(c,
MyAppWidgetProvider.class);
manager = AppWidgetManager.getInstance(c);
}
if (manager.getAppWidgetIds(widgetComponent) == null) {
S.sWidgetMayExist = false;
}
#Waza_Be is right as looking at the "AppWidgetIds" list to know the number of active widgets (those installed on your homescreen) is the correct way to know this information.
However, keep in mind that you SHOULD don't have to look at this by yourself.
Check the official Android documentation for best practice about widgets :
https://developer.android.com/guide/topics/appwidgets/index.html#AppWidgetProvider
The right approach is to override only the onUpdate() method and iterate through the list of "active" widgets :
public class ExampleAppWidgetProvider extends AppWidgetProvider {
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];
// Create an Intent to launch ExampleActivity
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(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.appwidget_provider_layout);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
And as your own widget provider overrides AppWidgetProvider, you will NOT go into the onUpdate() method if you have no widgets active on the home screen!
See the onReceive() code of Android AppWidgetProvider that checks already for you that "appWidgetIds.length > 0":
public void onReceive(Context context, Intent intent) {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
}
(...)
}
What about the following:
boolean widgetExists(Context context, int appWidgetId) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
AppWidgetProviderInfo info = appWidgetManager.getAppWidgetInfo(appWidgetId);
return (info != null);
}
From the docs for appWidgetManager.getAppWidgetInfo():
If the appWidgetId has not been bound to a provider yet, or you don't have access to that appWidgetId, null is returned.
I've got a widget which will update itself whenever there's a configuration change (such as screen orientation), and whenever the phone is unlocked. This process involves setting onClick handlers for the buttons on my widget. This works well, however I have found that there's a usage case which causes my app to not respond to onClick events. This particular case is whenever the launcher restarts itself.
Is there a way to detect when a launcher restarts, so I can update my widget manually? Or is there another way to ensure onClick handlers are not lost?
Turns out I was spamming new RemoteViews() when I should have just called it once to produce the view, and then referred to that one instance when required. In my solution, I have a class variable which stores this single RemoteView instance, and a getter to access it.
Proposal by #Glitch might not work for certain cases, especially app widget with ListView. This is because ListView will get very slow (Try to scroll the ListView) after appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, list_id) had been called several time.
My guess is, the single RemoteView instance will keep all its executed instruction in a list. Over the time, the instruction list will grow. Every time appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, list_id), the large instruction list will be executed all over again.
My proposed solution is as follow. However, I believe it will only work on certain device, as not all devices will receive same broadcast message during launcher restarting.
#SuppressLint("NewApi")
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals("com.sec.android.widgetapp.APPWIDGET_RESIZE")) {
// http://stackoverflow.com/questions/17396045/how-to-catch-widget-size-changes-on-devices-where-onappwidgetoptionschanged-not
handleTouchWiz(context, intent);
// Possible launcher restart.
handleLauncherRestart(context, intent);
} else if (action.equals("android.appwidget.action.APPWIDGET_UPDATE_OPTIONS")) {
// Possible launcher restart.
handleLauncherRestart(context, intent);
}
super.onReceive(context, intent);
}
private void handleLauncherRestart(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
updateAppWidget(context, appWidgetManager, appWidgetId);
}
private void handleTouchWiz(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra("widgetId", 0);
int widgetSpanX = intent.getIntExtra("widgetspanx", 0);
int widgetSpanY = intent.getIntExtra("widgetspany", 0);
if (appWidgetId > 0 && widgetSpanX > 0 && widgetSpanY > 0) {
Bundle newOptions = new Bundle();
// We have to convert these numbers for future use
// http://stackoverflow.com/questions/10008521/appwidget-size-calculation
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 74 - 2);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 74 - 2);
} else {
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 70 - 30);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 70 - 30);
}
onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
}