2017-09-08 6 views
0

J'ai un widget qui a un ListView d'articles. Lorsque le widget est nouveau et vide, l'utilisateur clique dessus pour charger une activité avec une liste d'objets (chaque objet contient la liste d'éléments) pour sélectionner un objet puis le widget devrait le recevoir et mettre à jour son contenu pour l'afficher. J'ai réussi à atteindre l'étape où l'objet (contenant la liste) est reçu par le AppWidgetProvider et la mise à jour est appelée. Ce que je ne parviens pas à faire, c'est que le fournisseur appelle le RemoteViewService et d'autres étapes. Je vais inclure les classes et les XML pour examen.ListView ne pas être mis à jour dans Android Widget

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
      package="io.litebit.ilfornodellacasa"> 

    <uses-permission android:name="android.permission.INTERNET"/> 

    <application 
     android:allowBackup="true" 
     android:icon="@mipmap/ic_launcher" 
     android:label="@string/app_name" 
     android:roundIcon="@mipmap/ic_launcher_round" 
     android:supportsRtl="true" 
     android:theme="@style/AppTheme"> 
     <activity 
      android:name=".ui.activities.MainActivity" 
      android:launchMode="singleTop"> 
      <intent-filter> 
       <action android:name="android.intent.action.MAIN"/> 

       <category android:name="android.intent.category.LAUNCHER"/> 
      </intent-filter> 
     </activity> 
     <activity 
      android:name=".ui.activities.RecipeActivity" 
      android:launchMode="singleTop"> 
      <meta-data 
       android:name="android.support.PARENT_ACTIVITY" 
       android:value=".ui.activities.MainActivity"/> 
     </activity> 

     <receiver android:name=".ui.widgets.IngredientsWidgetProvider"> 
      <intent-filter> 
       <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> 
      </intent-filter> 

      <meta-data 
       android:name="android.appwidget.provider" 
       android:resource="@xml/ingredients_app_widget_info"/> 
     </receiver> 

     <service 
      android:name=".ui.widgets.WidgetService" 
      android:exported="false" 
      android:permission="android.permission.BIND_REMOTEVIEWS"/> 
    </application> 

</manifest> 

IngredientsWidgetProvider.java

package io.litebit.ilfornodellacasa.ui.widgets; 

import android.app.PendingIntent; 
import android.appwidget.AppWidgetManager; 
import android.appwidget.AppWidgetProvider; 
import android.content.Context; 
import android.content.Intent; 
import android.net.Uri; 
import android.util.Log; 
import android.view.View; 
import android.widget.RemoteViews; 

import io.litebit.ilfornodellacasa.R; 
import io.litebit.ilfornodellacasa.model.Recipe; 
import io.litebit.ilfornodellacasa.ui.activities.MainActivity; 
import io.litebit.ilfornodellacasa.ui.activities.RecipeActivity; 

import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; 

/** 
* Implementation of App Widget functionality. 
*/ 
public class IngredientsWidgetProvider extends AppWidgetProvider { 

    private static final String TAG = IngredientsWidgetProvider.class.getSimpleName(); 
    private static Recipe recipe; 

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, 
           int appWidgetId) { 

     // Construct the RemoteViews object 
     RemoteViews widget = new RemoteViews(context.getPackageName(), 
       R.layout.ingredients_app_widget); 

     // Create pending intent to open the MainActivity 
     Intent mainActivityIntent = new Intent(context, MainActivity.class); 
     mainActivityIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId); 
     mainActivityIntent.setAction(MainActivity.ACTION_UPDATE_WIDGET); 

     PendingIntent pendingIntent = PendingIntent.getActivity(context, 
       0, mainActivityIntent, 0); 

     // Launch pending intent on click 
     widget.setOnClickPendingIntent(R.id.widget_layout, pendingIntent); 


     if (recipe != null) { 
      Log.i(TAG, "Recipe: " + recipe.getName() + " to be visualized"); 
      widget.setViewVisibility(R.id.tv_widget_empty, View.GONE); 
      widget.setViewVisibility(R.id.lv_widget, View.VISIBLE); 

      Intent listIntent = new Intent(context, WidgetService.class); 
      listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 
      listIntent.putExtra(RecipeActivity.KEY_RECIPE, recipe); 
      Uri uri = Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME)); 
      listIntent.setData(uri); 

      widget.setRemoteAdapter(R.id.lv_widget, listIntent); 
      widget.setEmptyView(R.id.lv_widget, R.id.tv_widget_empty); 
     } else { 
      widget.setViewVisibility(R.id.tv_widget_empty, View.VISIBLE); 
      widget.setViewVisibility(R.id.lv_widget, View.GONE); 
     } 

     // Instruct the widget manager to update the widget 
     appWidgetManager.updateAppWidget(appWidgetId, widget); 
    } 

    @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 
    } 

    @Override 
    public void onReceive(Context context, Intent intent) { 
     super.onReceive(context, intent); 
     recipe = intent.getParcelableExtra(RecipeActivity.KEY_RECIPE); 
     if (recipe != null) { 
      Log.i(TAG, "Recipe: " + recipe.getName() + " selected"); 
      updateAppWidget(context, AppWidgetManager.getInstance(context), 
        intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 
          AppWidgetManager.INVALID_APPWIDGET_ID)); 
     } 
    } 
} 

IngredientViewHolderFactory.java

package io.litebit.ilfornodellacasa.ui.widgets; 

import android.content.Context; 
import android.util.Log; 
import android.widget.RemoteViews; 
import android.widget.RemoteViewsService; 

import java.util.ArrayList; 
import java.util.List; 

import io.litebit.ilfornodellacasa.R; 
import io.litebit.ilfornodellacasa.model.Ingredient; 
import io.litebit.ilfornodellacasa.ui.utils.Utils; 

/** 
* Copyright 2017 Ramy Bittar 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 

public class IngredientViewHolderFactory implements RemoteViewsService.RemoteViewsFactory { 

    private static final String TAG = IngredientViewHolderFactory.class.getSimpleName(); 
    private List<Ingredient> ingredients = new ArrayList<>(); 
    private Context context; 
    private int appWidgetId; 
    private Utils utils; 

    IngredientViewHolderFactory(Context context, List<Ingredient> ingredients, int appWidgetId) { 

     this.context = context; 
     this.appWidgetId = appWidgetId; 
     this.ingredients = ingredients; 
     utils = new Utils(context); 
     Log.i(TAG, "Public constructor"); 
    } 

    @Override 
    public void onCreate() { 
     Log.i(TAG, "appWidgetId = " + this.appWidgetId); 
    } 

    @Override 
    public void onDataSetChanged() { 

    } 

    @Override 
    public void onDestroy() { 

    } 

    @Override 
    public int getCount() { 
     if (ingredients != null) { 
      return ingredients.size(); 
     } 
     return 0; 
    } 

    @Override 
    public RemoteViews getViewAt(int i) { 
     final RemoteViews viewHolder = new RemoteViews(context.getPackageName(), 
       R.layout.viewholder_ingredient); 
     Ingredient ingredient = ingredients.get(i); 
     viewHolder.setTextViewText(R.id.tv_ingredient, ingredient.getIngredient()); 
     String quantity = utils.getQuantity(
       ingredient.getQuantity(), 
       ingredient.getMeasure(), 
       Utils.UNIT_SYS_IMPERIAL); 
     viewHolder.setTextViewText(R.id.tv_quantity, quantity); 

     return viewHolder; 
    } 

    @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 false; 
    } 
} 

WidgetService.java

package io.litebit.ilfornodellacasa.ui.widgets; 

import android.appwidget.AppWidgetManager; 
import android.content.Intent; 
import android.util.Log; 
import android.widget.RemoteViewsService; 

import io.litebit.ilfornodellacasa.model.Recipe; 
import io.litebit.ilfornodellacasa.ui.activities.RecipeActivity; 

/** 
* Copyright 2017 Ramy Bittar 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 

public class WidgetService extends RemoteViewsService { 
    private static final String TAG = WidgetService.class.getSimpleName(); 

    /** 
    * Invokes the remote view factory 
    * @param intent passed from the calling widget provider to the remote view factory 
    * @return RemoteViewsFactory object 
    */ 
    @Override 
    public RemoteViewsFactory onGetViewFactory(Intent intent) { 
     Log.i(TAG, "onGetViewFactory called"); 
     Recipe recipe = intent.getParcelableExtra(RecipeActivity.KEY_RECIPE); 
     int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 
       AppWidgetManager.INVALID_APPWIDGET_ID); 
     return (new IngredientViewHolderFactory(this.getApplicationContext(), 
       recipe.getIngredients(), 
       appWidgetId)); 
    } 
} 

MainActivity.java

package io.litebit.ilfornodellacasa.ui.activities; 

import android.app.PendingIntent; 
import android.appwidget.AppWidgetManager; 
import android.content.Intent; 
import android.content.res.Configuration; 
import android.os.Bundle; 
import android.support.v7.app.AppCompatActivity; 
import android.support.v7.widget.GridLayoutManager; 
import android.support.v7.widget.LinearLayoutManager; 
import android.support.v7.widget.RecyclerView; 
import android.util.Log; 
import android.view.Menu; 
import android.view.MenuItem; 
import android.widget.Toast; 

import java.util.List; 

import io.litebit.ilfornodellacasa.R; 
import io.litebit.ilfornodellacasa.model.Recipe; 
import io.litebit.ilfornodellacasa.model.RecipeListSerializer; 
import io.litebit.ilfornodellacasa.sync.RecipeSyncTask; 
import io.litebit.ilfornodellacasa.ui.adapters.RecipeAdapter; 
import io.litebit.ilfornodellacasa.ui.widgets.IngredientsWidgetProvider; 
import pocketknife.BundleSerializer; 
import pocketknife.PocketKnife; 
import pocketknife.SaveState; 

/** 
* Copyright 2017 Ramy Bittar 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 

public class MainActivity extends AppCompatActivity implements RecipeSyncTask.SyncRecipesCallback, 
     RecipeAdapter.OnRecipeClicked { 

// private static final String TAG = MainActivity.class.getSimpleName(); 

    private static final String TAG = MainActivity.class.getSimpleName(); 
    public static final String ACTION_UPDATE_WIDGET = TAG + ".action.update_widget"; 

    private boolean updateWidget = false; 
    private int appWidgetId; 
    private RecipeAdapter adapter; 

    @SaveState 
    @BundleSerializer(RecipeListSerializer.class) 
    List<Recipe> recipes; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     PocketKnife.bindExtras(this); 
     PocketKnife.restoreInstanceState(this, savedInstanceState); 

     RecyclerView recyclerView = findViewById(R.id.recycler_view); 
     RecyclerView.LayoutManager layoutManager; 
     if (isTabletAndLandscape()) { 
      layoutManager = new GridLayoutManager(this, 3); 
     } else { 
      layoutManager = new LinearLayoutManager(this); 
     } 
     recyclerView.setLayoutManager(layoutManager); 

     adapter = new RecipeAdapter(null, this); 
     recyclerView.setAdapter(adapter); 

     if (recipes == null || recipes.size() == 0) { 
      RecipeSyncTask syncTask = new RecipeSyncTask(this); 
      syncTask.syncRecipes(); 
     } else { 
      refreshRecyclerView(recipes); 
     } 

     if (!getIntent().getAction().equals("")) { 
      updateWidget = getIntent().getAction().equals(ACTION_UPDATE_WIDGET); 
      appWidgetId = getIntent().getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0); 
     } 

     Log.i(TAG, "updateWidget = " + updateWidget); 
    } 

    private boolean isTabletAndLandscape() { 
     return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE 
       && getResources().getConfiguration().screenWidthDp >= 900; 
    } 

    private void refreshRecyclerView(List<Recipe> recipes) { 
     this.adapter.switchData(recipes); 
    } 

    @Override 
    public boolean onCreateOptionsMenu(Menu menu) { 
     getMenuInflater().inflate(R.menu.menu, menu); 
     return true; 
    } 

    @Override 
    public boolean onOptionsItemSelected(MenuItem item) { 
     switch (item.getItemId()) { 
      case R.id.action_settings: 
       return true; 
      default: 
       return super.onOptionsItemSelected(item); 
     } 
    } 

    @Override 
    protected void onSaveInstanceState(Bundle outState) { 
     PocketKnife.saveInstanceState(this, outState); 
     super.onSaveInstanceState(outState); 
    } 

    @Override 
    public void onSyncResponse(List<Recipe> newRecipes) { 
     recipes = newRecipes; 
     refreshRecyclerView(this.recipes); 
     Toast.makeText(this, recipes.size() + " recipe(s) found.", Toast.LENGTH_SHORT).show(); 
    } 

    @Override 
    public void onSyncFailure(Throwable throwable) { 
     Toast.makeText(this, "Something wrong happened. Check system log for details.", 
       Toast.LENGTH_SHORT).show(); 
    } 

    @Override 
    public void onClick(int recipeId) { 
     Recipe currentRecipe = null; 
     for (Recipe recipe : recipes) { 
      if (recipe.getId() == recipeId) { 
       currentRecipe = recipe; 
      } 
     } 
     if (updateWidget) { 
      sendIntentToWidget(currentRecipe); 
     } else { 
      sendIntentToRecipeActivity(currentRecipe); 
     } 
    } 

    private void sendIntentToWidget(Recipe currentRecipe) { 
     Intent recipeIntent = new Intent(this, IngredientsWidgetProvider.class); 
     recipeIntent.putExtra(RecipeActivity.KEY_RECIPE, currentRecipe); 
     recipeIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 
     recipeIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 
     sendBroadcast(recipeIntent); 
     finish(); 
    } 

    private void sendIntentToRecipeActivity(Recipe currentRecipe) { 
     Intent recipeIntent = new Intent(this, RecipeActivity.class); 
     recipeIntent.putExtra(RecipeActivity.KEY_RECIPE, currentRecipe); 
     recipeIntent.putExtra(RecipeActivity.KEY_STEP_ID, RecipeActivity.NO_STEP_SELECTED); 
     startActivity(recipeIntent); 
    } 
} 

viewholder_ingredient.xml

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
       android:layout_width="match_parent" 
       android:layout_height="wrap_content" 
       android:orientation="vertical" 
       android:paddingTop="10dp" 
       android:paddingBottom="0dp" 
       android:paddingLeft="14dp" 
       android:paddingRight="14dp"> 

    <TextView 
     android:id="@+id/tv_ingredient" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:textStyle="bold"/> 

    <TextView 
     android:id="@+id/tv_quantity" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content"/> 
</LinearLayout> 

ingredients_app_widget.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
       xmlns:tools="http://schemas.android.com/tools" 
       android:id="@+id/widget_layout" 
       android:layout_width="match_parent" 
       android:layout_height="match_parent" 
       android:background="#66ffffff" 
       android:orientation="vertical" 
       android:padding="@dimen/widget_margin"> 

    <ListView 
     android:id="@+id/lv_widget" 
     android:layout_width="match_parent" 
     android:layout_height="match_parent" 
     android:visibility="visible"/> 

    <TextView 
     android:id="@+id/tv_widget_empty" 
     android:layout_width="match_parent" 
     android:layout_height="match_parent" 
     android:text="@string/click_to_select_a_recipe" 
     android:visibility="gone" 
     tools:text="Empty list text"/> 
</LinearLayout> 

ingredients_app_widget_info.xml

<?xml version="1.0" encoding="utf-8"?> 
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" 
        xmlns:tools="http://schemas.android.com/tools" 
        android:initialKeyguardLayout="@layout/ingredients_app_widget" 
        android:initialLayout="@layout/ingredients_app_widget" 
        android:minHeight="125dp" 
        android:minWidth="250dp" 
        android:previewImage="@mipmap/ic_launcher" 
        android:resizeMode="vertical" 
        android:updatePeriodMillis="86400000" 
        android:widgetCategory="home_screen" 
        android:configure="io.litebit.ilfornodellacasa.ui.activities.MainActivity" 
        tools:targetApi="jelly_bean_mr1"> 
</appwidget-provider> 

Merci de votre aide à l'avance.

Ramy Bittar

Répondre

0

Il me semble que vous n'êtes pas appeler notifyAppWidgetViewDataChanged. Si les données ont changé, vous devez informer le widget que la vue de collection doit être mise à jour.

AppWidgetManager.notifyAppWidgetViewDataChanged

Aussi, jetez un oeil dans l'échantillon des collections des widgets le sujet Keeping Collection Data Fresh

Une caractéristique de widgets d'applications qui utilisent des collections est la capacité de fournir aux utilisateurs une mise à -date contenu. Par exemple, considérons le widget de l'application Android 3.0 Gmail , qui fournit aux utilisateurs un instantané de leur boîte de réception. Pour que cela soit possible, vous devez être en mesure de déclencher votre vue RemoteViewsFactory et collection pour récupérer et afficher les nouvelles données . Vous y parvenir avec l'appel AppWidgetManager notifyAppWidgetViewDataChanged()

+0

Pour autant que je sache, je n'utilise pas de collections. Quoi qu'il en soit, j'ai ajouté notifyAppWidgetViewDataChanged à la méthode onDataSetChanged() et rien ne s'est passé. J'ai marqué les méthodes principales avec des logs et je peux voir que d'une façon ou d'une autre le service ne démarre pas bien qu'il soit inclus dans le manifeste et appelé depuis AppWidgetProvider. –

+0

Vous utilisez un ListView pour utiliser des collections .... Le notifyAppWidgetViewDataChanged doit être ajouté après le fichier updateAppWidget –

0

Vous devez faire notifyAppWidgetViewDataChanged() après updateAppWidget(). Donc faire une logique comme ceci:

appWidgetManager.updateAppWidget(widgetId, views); 
appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.lessons); // R.id.lessons - it's your listview id 
+0

Rien de tout cela n'a vraiment aidé. Je suppose que je fais quelque chose de mal à gérer le cycle de vie de BroadcastReceiver. –