10

I have created an app widget using collection for my app, The widget shows date and list of items on that particular date. Everything works fine and the widget is updating as required, but sometimes what happens while changing the date in the widget by clicking next and previous button, the list is not refreshed means the items are not updated on that particular date. This behavior is random and its occur sometimes only. So why this happen, anything wrong in my code.

Code that I have used:

WidgetProvider.class

public class WidgetProvider extends AppWidgetProvider 
{   
    private ThemeManager    m_ThemeManagerObject;

    private static String   WIDGET_NEXT_BUTTON = "in.test.widgetApp.WIDGET_NEXT_BUTTON";

    private static String   WIDGET_PREV_BUTTON = "in.test.widgetApp.WIDGET_PREV_BUTTON";    

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) 
    {               
        super.onUpdate(context, appWidgetManager, appWidgetIds);

        // Set Date to current Date
        NoteManager.getSingletonObject().setWidgetToCurrentDate();

        // Code to update the widget by current date 
        updateAppWidget(context, AppWidgetManager.getInstance(context), appWidgetIds);
    }   
    
    @Override
    public void onReceive(Context context, Intent intent) 
    {           
        super.onReceive(context, intent);

        int numOfDays = 1;
        
        ComponentName thisWidget = new ComponentName(context, WidgetProvider.class);
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
        
        if (intent.getAction().equals(WIDGET_NEXT_BUTTON)) 
        {
            // Increase no of days by one
            // Update the widget by new date 
            NoteManager.getSingletonObject().setWidgetDate(numOfDays);          
            updateAppWidget(context, AppWidgetManager.getInstance(context), appWidgetIds);
        }   
        else if (intent.getAction().equals(WIDGET_PREV_BUTTON)) 
        {
            // Decrease no of days by one
            // Update the widget by new date 
            NoteManager.getSingletonObject().setWidgetDate(-numOfDays);         
            updateAppWidget(context, AppWidgetManager.getInstance(context), appWidgetIds);
        }                   
    }

        public void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
    // Get the folder path of all-page-view
    ContextWrapper cw = new ContextWrapper(context.getApplicationContext());
    File customDirectoryPath = cw.getDir(Utilities.CUSTOM_DIRECTORY_NAME_PREFIX, Context.MODE_PRIVATE);
    File allPageDirectoryPath = new File(customDirectoryPath.getPath() + "/" + Utilities.All_PAGE_DIRECTORY_NAME_PREFIX); 

    if (!(allPageDirectoryPath.exists()))
        allPageDirectoryPath.mkdirs();

    // Create an singleton object of ThemeManager class
    m_ThemeManagerObject = ThemeManager.getSingletonObject();
    m_ThemeManagerObject.readTheme(allPageDirectoryPath.getPath());

    // Create an instance of SimpleDateFormat class
    SimpleDateFormat dateFormater = new SimpleDateFormat("dd-MMM, EEE", Locale.US);

    /* loop through all widget instances */
    for (int widgetId : appWidgetIds) 
    { 
        // Create an instance of remote view class
        RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.widget_list);       
        Intent svcIntent = new Intent(context, WidgetService.class);
        svcIntent.setData(Uri.fromParts("content", String.valueOf(widgetId), null));        
        remoteView.setRemoteAdapter(R.id.widget_list, svcIntent);   

        // Show day, month and week day inside the widget
        remoteView.setTextViewText(R.id.txt_date, dateFormater.format(NoteManager.getSingletonObject().getWidgetDate().getTime()));

        // If the list is empty. Show empty widget with juswrite-icon & empty text to the user          
        remoteView.setEmptyView(R.id.widget_list, R.id.widget_empty_text);              

        // On click of next button
        Intent nextButtonIntent = new Intent(WIDGET_NEXT_BUTTON);
        /* use widgetId as second parameter - it helped me to better address particular widget instance */
        PendingIntent nextButtonPendingIntent = PendingIntent.getBroadcast(context, widgetId, nextButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteView.setOnClickPendingIntent(R.id.btn_next_month, nextButtonPendingIntent);
        remoteView.setInt(R.id.btn_next_month, "setBackgroundResource", m_ThemeManagerObject.getNextButtonBgImage());

        // On click of previous button
        Intent prevButtonIntent = new Intent(WIDGET_PREV_BUTTON);
        /* use widgetId as second parameter - same as above */
        PendingIntent prevButtonPendingIntent = PendingIntent.getBroadcast(context, widgetId, prevButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteView.setOnClickPendingIntent(R.id.btn_prev_month, prevButtonPendingIntent);
        remoteView.setInt(R.id.btn_prev_month, "setBackgroundResource", m_ThemeManagerObject.getPrevButtonBgImage());

        // Open application on click of app widget
        Intent clickIntent = new Intent(context, AllPageViewActivity.class);
        PendingIntent clickPI = PendingIntent.getActivity(context, 0,clickIntent,PendingIntent.FLAG_UPDATE_CURRENT);
        remoteView.setOnClickPendingIntent(R.id.widget_empty_text, clickPI);
        remoteView.setOnClickPendingIntent(R.id.txt_date, clickPI);
        
        /* update one widget instance at a time*/
        appWidgetManager.updateAppWidget(widgetId, remoteView);
    }
}
}

WidgetService.class

public class WidgetService extends RemoteViewsService 
{
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) 
    {
        return(new WidgetDisplay(this.getApplicationContext(), intent));
    }
}

WidgetDisplay.class

public class WidgetDisplay implements RemoteViewsService.RemoteViewsFactory
{
    private File m_CustomDirectoryPath, m_AllPageDirectoryPath;
    
    private NoteManager m_NoteManagerObject;
    
    private ThemeManager m_ThemeManagerObject;
    
    private ArrayList<String>   m_AlarmItemNameArrayList;
    
    private ArrayList<Integer>  m_ItemIndexArray;
    
    private Context ctxt=null;
    
    int appWidgetId;
    
    Bitmap canvasBackground;

    public WidgetDisplay(Context ctxt, Intent intent) 
    {
        this.ctxt=ctxt;
        
        appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
        
        setImageInView(this.ctxt);

    }
    
    private void setImageInView(Context context) 
    {
        ContextWrapper cw = new ContextWrapper(ctxt.getApplicationContext());
        m_CustomDirectoryPath = cw.getDir(Utilities.CUSTOM_DIRECTORY_NAME_PREFIX, Context.MODE_PRIVATE);
        m_AllPageDirectoryPath = new File(m_CustomDirectoryPath.getPath() + "/" + Utilities.All_PAGE_DIRECTORY_NAME_PREFIX); 

        m_NoteManagerObject = NoteManager.getSingletonObject();
        m_ThemeManagerObject = ThemeManager.getSingletonObject();
        
        m_NoteManagerObject.readSettings(m_AllPageDirectoryPath.getPath());
        m_NoteManagerObject.readAllPageChangesFromFile(m_AllPageDirectoryPath.getPath());
        m_NoteManagerObject.readAlarmFromFile(m_AllPageDirectoryPath.getPath());
        m_ThemeManagerObject.readTheme(m_AllPageDirectoryPath.getPath());

        m_AlarmItemNameArrayList = new ArrayList<String>(m_NoteManagerObject.getAlarmCount());
        m_ItemIndexArray = new ArrayList<Integer>(m_NoteManagerObject.getAlarmCount());

        SimpleDateFormat sdFormatter = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);      
        String selectedDate = sdFormatter.format(m_NoteManagerObject.getWidgetDate());
        
        for(int i=0; i<m_NoteManagerObject.getAlarmCount(); i++)
        {
            String ArrayDate = sdFormatter.format(m_NoteManagerObject.getAlarmTime(i));         
            if(selectedDate.equals(ArrayDate))
            {
                File noteDirectoryPath = new File(m_CustomDirectoryPath.getPath() + "/" + m_NoteManagerObject.getAlarmFolder(i));
                m_AlarmItemNameArrayList.add(noteDirectoryPath.getPath() + "/" + m_NoteManagerObject.getAlarmItem(i));

                m_ItemIndexArray.add(i);
            }
        }
    }

    @Override
    public int getCount() 
    {
        return(m_AlarmItemNameArrayList.size());
    }
    
    @Override
    public RemoteViews getViewAt(int position) 
    {       
        new ImageLoaderTask(position).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

        // Set combine image to the image view using remote view instance
        RemoteViews remoteView = new RemoteViews(ctxt.getPackageName(), R.layout.widget_list_item);
        remoteView.setImageViewBitmap(R.id.image_view, canvasBackground);
        
        // Set time text view using remote view instance
        SimpleDateFormat timeFormater;
        
        if(m_NoteManagerObject.get24HourFormat())
        {
            timeFormater = new SimpleDateFormat("HH:mm", Locale.US);
        }
        else
        {
            timeFormater = new SimpleDateFormat("hh:mm a", Locale.US );
        }
        
        // Show time on the top of each image view
        String time = timeFormater.format(m_NoteManagerObject.getAlarmTime(m_ItemIndexArray.get(position)));            
        remoteView.setTextViewText(R.id.text_alarm_time,  time);        
                
        Intent clickIntent = new Intent(ctxt, AllPageViewActivity.class);
        PendingIntent clickPI=PendingIntent.getActivity(ctxt, 0,clickIntent,PendingIntent.FLAG_UPDATE_CURRENT);
        remoteView.setOnClickPendingIntent(R.id.image_view, clickPI);

        return(remoteView);
    }

    class ImageLoaderTask extends AsyncTask<URL, Integer, Long>
    {
        private int position;
        
        ImageLoaderTask(int position)
        {
            this.position = position;
        }

        @Override
        protected void onPreExecute()
        {
            // Get foreground and background image
            Bitmap bitmapImage = BitmapFactory.decodeFile(m_AlarmItemNameArrayList.get(position)).copy(Bitmap.Config.ARGB_8888, true);
            canvasBackground = BitmapFactory.decodeResource(ctxt.getResources(), m_ThemeManagerObject.getWidgetListItemBgImage(m_ItemIndexArray.get(position), bitmapImage)).copy(Bitmap.Config.ARGB_8888, true);
            
            // Scaled foreground image and combine with the background image
            bitmapImage = Bitmap.createScaledBitmap(bitmapImage, 380, bitmapImage.getHeight() / 2, true);               
            Canvas comboImage = new Canvas(canvasBackground);
            comboImage.drawBitmap(bitmapImage, 0f, 0f, null);
        }

        @Override
        protected Long doInBackground(URL... urls)
        {
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... progress)
        {

        }

        @Override
        protected void onPostExecute(Long result)
        {

        }
    }
    
    @Override
    public void onCreate(){ 
    }

    @Override
    public void onDestroy(){
    }
    
    @Override
    public RemoteViews getLoadingView()
    {
        return(null);
    }

    @Override
    public int getViewTypeCount(){
        return(1);
    }

    @Override
    public long getItemId(int position){
        return(position);
    }

    @Override
    public boolean hasStableIds(){
        return(true);
    }

    @Override
    public void onDataSetChanged(){
    }
}
iknow
  • 8,358
  • 12
  • 41
  • 68
AndroidDev
  • 4,521
  • 24
  • 78
  • 126
  • Anyone know the clue for this answer – AndroidDev Apr 09 '13 at 09:39
  • After lot of search, at last i got my answer. Actually i have to call appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_list); onReceive() method. I have posted my updated code as an answer of this question for future use. – AndroidDev May 03 '13 at 10:36

3 Answers3

5

WidgetProvider.class

public class WidgetProvider extends AppWidgetProvider 
{   
    private NoteManager         m_NoteManagerObject;

    private ThemeManager        m_ThemeManagerObject;

    private SimpleDateFormat    m_DateFormater;

    private static String       WIDGET_NEXT_BUTTON = "in.test.widgetApp.WIDGET_NEXT_BUTTON";

    private static String       WIDGET_PREV_BUTTON = "in.test.widgetApp.WIDGET_PREV_BUTTON";    

    public WidgetProvider()
    {
        // Create an singleton object of NoteManager class
        m_NoteManagerObject = NoteManager.getSingletonObject();
        // Create an singleton object of ThemeManager class
        m_ThemeManagerObject = ThemeManager.getSingletonObject();
        // Create an instance of SimpleDateFormat class
        m_DateFormater = new SimpleDateFormat("dd-MMM, EEE", Locale.US);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) 
    {               
        super.onUpdate(context, appWidgetManager, appWidgetIds);

        // Set Date to current Date
        m_NoteManagerObject.setWidgetToCurrentDate();

        // Get the folder path of all-page-view
        ContextWrapper cw = new ContextWrapper(context.getApplicationContext());
        File customDirectoryPath = cw.getDir(Utilities.CUSTOM_DIRECTORY_NAME_PREFIX, Context.MODE_PRIVATE);
        File allPageDirectoryPath = new File(customDirectoryPath.getPath() + "/" + Utilities.All_PAGE_DIRECTORY_NAME_PREFIX); 

        if (!(allPageDirectoryPath.exists()))
            allPageDirectoryPath.mkdirs();

        m_ThemeManagerObject.readTheme(allPageDirectoryPath.getPath());     

        // Set up the intent that starts the WidgetService, which will
        // provide the views for this collection.   
        // When intents are compared, the extras are ignored, so we need to embed the extras
        // into the data so that the extras will not be ignored.
        Intent intent  = new Intent(context, WidgetService.class);
        intent.setData(Uri.fromParts("content", String.valueOf(appWidgetIds), null));

        // Instantiate the RemoteViews object for the App Widget layout.
        // Set up the RemoteViews object to use a RemoteViews adapter. 
        // This adapter connects to a RemoteViewsService  through the specified intent.
        // This is how you populate the data.
        RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.widget_list);           
        remoteView.setRemoteAdapter(R.id.widget_list, intent);  

        // Show day, month and week day inside the widget
        remoteView.setTextViewText(R.id.txt_date, m_DateFormater.format(m_NoteManagerObject.getWidgetDate().getTime()));

        // The empty view is displayed when the collection has no items.        
        remoteView.setEmptyView(R.id.widget_list, R.id.widget_empty_text);              

        // On click of next button
        // This section makes it possible for items to have individualized behavior.
        // Set the action for the intent.
        // When the user touches a particular view, it will have the effect of
        // broadcasting ACTION.
        Intent nextButtonIntent = new Intent(context, WidgetProvider.class);            
        nextButtonIntent.setAction(WIDGET_NEXT_BUTTON);
        PendingIntent nextButtonPendingIntent = PendingIntent.getBroadcast(context, 0, nextButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteView.setOnClickPendingIntent(R.id.btn_next_month, nextButtonPendingIntent);
        remoteView.setInt(R.id.btn_next_month, "setBackgroundResource", m_ThemeManagerObject.getNextButtonBgImage());

        // On click of previous button
        // This section makes it possible for items to have individualized behavior.
        // Set the action for the intent.
        // When the user touches a particular view, it will have the effect of
        // broadcasting ACTION.
        Intent prevButtonIntent = new Intent(context, WidgetProvider.class);
        prevButtonIntent.setAction(WIDGET_PREV_BUTTON);
        PendingIntent prevButtonPendingIntent = PendingIntent.getBroadcast(context, 0, prevButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteView.setOnClickPendingIntent(R.id.btn_prev_month, prevButtonPendingIntent);
        remoteView.setInt(R.id.btn_prev_month, "setBackgroundResource", m_ThemeManagerObject.getPrevButtonBgImage());

        // Open application on click of app widget
        Intent clickIntent = new Intent(context, AllPageViewActivity.class);
        PendingIntent clickPI = PendingIntent.getActivity(context, 0,clickIntent,PendingIntent.FLAG_UPDATE_CURRENT);
        remoteView.setOnClickPendingIntent(R.id.widget_empty_text, clickPI);
        remoteView.setOnClickPendingIntent(R.id.txt_date, clickPI); 

        appWidgetManager.updateAppWidget(appWidgetIds, remoteView); 
    }   

    @Override
    public void onReceive(Context context, Intent intent) 
    {           
        super.onReceive(context, intent);

        int numOfDays = 1;

        ComponentName thisWidget = new ComponentName(context, WidgetProvider.class);
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);

        if (intent.getAction().equals(WIDGET_NEXT_BUTTON)) 
        {
            // Increase no of days by one
            m_NoteManagerObject.setWidgetDate(numOfDays);

            // Update remote view
            RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.widget_list);               
            remoteView.setTextViewText(R.id.txt_date, m_DateFormater.format(m_NoteManagerObject.getWidgetDate().getTime()));
            appWidgetManager.updateAppWidget(appWidgetIds, remoteView); 

            // Update list content of the widget
           // This will call onDataSetChanged() method of WidgetDisplay class
            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_list);
        }   
        else if (intent.getAction().equals(WIDGET_PREV_BUTTON)) 
        {
            // Decrease no of days by one
            m_NoteManagerObject.setWidgetDate(-numOfDays);  

            // Update remote view
            RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.widget_list);               
            remoteView.setTextViewText(R.id.txt_date, m_DateFormater.format(m_NoteManagerObject.getWidgetDate().getTime()));
            appWidgetManager.updateAppWidget(appWidgetIds, remoteView); 

            // Update list content of the widget
              // This will call onDataSetChanged() method of WidgetDisplay class
            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_list);
        }                   
    }
}
AndroidDev
  • 4,521
  • 24
  • 78
  • 126
  • Excellent answer to your own question, kuddos ! You saved me a lot of research time ;) – 2Dee Dec 01 '13 at 15:18
2

You can try if changing PendingIntent.FLAG_UPDATE_CURRENT to PendingIntent.FLAG_CANCEL_CURRENTin your nextButtonPendingIntent and prevButtonPendingIntent pending intents will help.

Paweł Wyrwiński
  • 1,393
  • 9
  • 13
  • Its Already set as PendingIntent.FLAG_UPDATE_CURRENT in nextButtonPendingIntent and prevButtonPendingIntent. – AndroidDev Apr 05 '13 at 11:19
  • I have made mistake in my original answer (I've switched order of flags while pasting them in). Editing now. – Paweł Wyrwiński Apr 05 '13 at 11:21
  • Nope not working sometimes it update the list but sometimes it doesn't, but the date changes, means on click of button, from onReceive() method it went to updateAppWidget(), but from there it unable to call the WidgetService class. As a result it will nt able to call WidgetDisplay class and data are not updated, but sometimes it works like a charm. – AndroidDev Apr 05 '13 at 11:26
  • So it breaks before setting pendingIntents for next & prev buttons. One thing that looks suspicious to me is: svcIntent.setData(Uri.fromParts("content", **String.valueOf(appWidgetIds)**, null)); Executing String.valueOf(**int[]**) will produce something like "[I@142bece", probably you should update your `updateAppWidget` method to iterate over appWidgetIds and performing its logic for every **single** instance of widget (setting pendingIntents etc.). By the way I found it's nice idea to use current widgetId as second parameter in PendingIntent.getBroadcast(...) call instead of 0. – Paweł Wyrwiński Apr 05 '13 at 11:57
  • Can u show me with an some line of code, Actually am unable to get what actually i need to change in my code. – AndroidDev Apr 05 '13 at 12:03
  • Here, look at this: http://pastebin.com/jmESUFNX. Sorry for any errors in this snippet - I don't have SDK installed on this PC. This is a general idea only, but there's a chance it will work on the first try :) – Paweł Wyrwiński Apr 05 '13 at 12:16
  • I have make changes in my code...and updated in my question, Now what happen date is changing but listItem is not at all updated. is there any other places where i can make some changes. – AndroidDev Apr 05 '13 at 12:25
  • It looks like there is inconsistency in `svcIntent` data payload. You should: **1)** replace `svcIntent.setData(Uri.fromParts("content", String.valueOf(widgetId), null)` with: `svcIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)` **OR** **2)** leave it as is and instead update WidgetDisplay's constructor to obtain `"content"` you previously stored into intent (I don't know right method, maybe [this answer](http://stackoverflow.com/a/11387266/2085106) will help you). I suggest 1st alternative, I've used it recently and it worked. – Paweł Wyrwiński Apr 05 '13 at 12:57
  • To be honest I don't think my previous comment will help fix your widget's updating issue. I have no clue what to do next :-( – Paweł Wyrwiński Apr 05 '13 at 13:14
  • Ok...i will work on it and try to solve it...let u know if any help required – AndroidDev Apr 05 '13 at 13:31
  • I think in you application you have to work with date and widget class match that date to database? – Harshid Apr 08 '13 at 07:38
  • @Harshid Yeh what u say is right..but my issue is the update issue...so sometime it update and sometime it doesn't on change of date.. – AndroidDev Apr 08 '13 at 08:23
  • @Harshid Not like that. Actually in my case user add different image on different date, and all this image are store inside the phone memory / sdcard. Now when the user set a particular date on the widget using next and previous button, i am displaying image set on that particular date, now my issue is sometime it update & sometimes doesn't update. – AndroidDev Apr 08 '13 at 09:24
1

When you create the PendingIntents in updateAppWidget, for each widget, you create these two:

PendingIntent nextButtonPendingIntent = PendingIntent.getBroadcast(context, widgetId, nextButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent prevButtonPendingIntent = PendingIntent.getBroadcast(context, widgetId, prevButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);

For both, you set the second argument to widgetId. The PendingIntent system doesn't look to see if the Intent argument is different. (In fact, it definititely shouldn't do this - otherwise you couldn't update an existing PendingIntent to a new Intent.) This means that nextButtonPendingIntent and prevButtonPendingIntent end up the same.

The solution is to put known, distinct, numbers in that argument and to put other useful information (like the widget Id) inside the intent:

nextButtonIntent.putExtra("widget_id", widgetId);

And to retrieve it in the onReceive():

int widgetId = intent.getIntExtra("widget_id");
Neil Townsend
  • 6,024
  • 5
  • 35
  • 52