2017-09-08 42 views
0

항목의 ListView가있는 위젯이 있습니다. 위젯이 새롭고 비어있을 때 사용자는이를 클릭하여 객체 목록 (각 객체는 항목 목록을 포함 함)으로 활동을로드하고 객체를 선택하고 위젯은이를 수신하고 내용을 업데이트하여 표시해야합니다. 개체 (목록 포함)가 AppWidgetProvider에 의해 수신되고 업데이트가 호출되는 단계에 도달했습니다. 내가하지 못한 것은 공급자가 RemoteViewService와 더 많은 단계를 호출하도록하는 것이다. 검토를 위해 클래스와 XML을 포함하겠습니다.안드로이드 위젯에서 ListView가 업데이트되지 않습니다.

의 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

0,123,516
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> 
012,351,

미리 도움을 주셔서 감사합니다.

Ramy Bittar은

답변

0

당신이 notifyAppWidgetViewDataChanged를 호출하지 않는 나에게 보인다. 데이터가 변경된 경우 컬렉션 뷰를 업데이트해야한다고 위젯에 알릴 필요가 있습니다.

또한

AppWidgetManager.notifyAppWidgetViewDataChanged

는 컬렉션을 사용하는 응용 프로그램 위젯의 Keeping Collection Data Fresh

한 기능이 그대로 - 사용자에게 제공하는 기능입니다 주제 위젯의 컬렉션 샘플에서 살펴 - 날짜 내용. 예를 들어 사용자에게 의받은 편지함의 스냅 샷을 제공하는 Android 3.0 Gmail 앱 위젯을 예로 들어 보겠습니다. 이를 가능하게하려면 RemoteViewsFactory 및 콜렉션 뷰를 트리거하여 새로운 데이터를 가져 와서 표시 할 수 있어야합니다. 당신은 을 notifyAppWidgetViewDataChanged AppWidgetManager 호출() 당신은 updateAppWidget 후 notifyAppWidgetViewDataChanged()을 (해야

+0

알고있는 한, 나는 어떤 수집도 사용하고 있지 않다. 어쨌든, notifyAppWidgetViewDataChanged를 onDataSetChanged() 메소드에 추가했는데 아무 일도 없었습니다. 로그로 주요 메소드를 표시했고, 매니페스트에 포함되어 있고 AppWidgetProvider에서 호출되었지만 서비스가 시작되지 않고 있음을 알 수 있습니다. –

+0

컬렉션을 사용하기 위해 ListView를 사용하고 있습니다 ... updateAppWidget 뒤에 notifyAppWidgetViewDataChanged를 추가해야합니다. –

0

)와이를 달성. 이렇게 논리를 만드십시오 :

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

그 중 아무 것도 도움이되지 않았습니다. 나는 BroadcastReceiver의 라이프 사이클을 잘못 처리하고 있다고 생각한다. –