📜 ⬆️ ⬇️

Android StackView based card widget (something like Tinder)

Hello.

A month ago, the first article was published, devoted to my first product for personal and family finances.

Part 1
')
The application can be downloaded from the link.

Since then, I changed the name to “Budget. Basket. Notifications "and accordingly added functionality that allows you to quickly create a shopping cart and use it. The mechanism for creating a shopping cart is quite simple, logical, and therefore convenient: in the catalog tree you mark several positions within the category and click the “To cart” button. This list of positions with the indication of the root category falls into the order. Such an action can be repeated as many times as necessary, i.e. for example, made orders for:

- jelly
- groceries
- meat fish
- vegetables fruits
- household goods

As a result, we have a logical grouped shopping lists. They are displayed in the form of widget cards. Click on the card and the order opens. The beauty is that this immediately appears to all members of the group, i.e. Mom made orders, and Dad went on the way and bought everything. It remains only to make the total amount in each order.

Catalog tree


Shopping List Card Widget


Implementation

1. Class of service
In it lives the listener of a local database of orders.

When a change occurs in the orders table, we trigger the update of the widgets.

public class StackWidgetService extends RemoteViewsService { private Observer observer; @Inject FirestoreRepository repository; @Override public void onCreate() { super.onCreate(); Injector.getApplicationComponent().inject(this); observer = (Observer<List<MoneyTransaction>>) moneyTransactions -> { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); int appWidgetIds[] = appWidgetManager.getAppWidgetIds(new ComponentName(this, OrdersWidgetProvider.class)); appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stack_view); }; repository.getLiveOrders().observeForever(observer); } @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } @Override public void onDestroy() { repository.getLiveOrders().removeObserver(observer); super.onDestroy(); } } 


2. Adapter class for stack of cards
 public class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private List<MoneyTransaction> mWidgetItems = new ArrayList<MoneyTransaction>(); private Context mContext; @Inject FirestoreRepository repository; public StackRemoteViewsFactory(Context context, Intent intent) { mContext = context; Injector.getApplicationComponent().inject(this); } public void onCreate() { } public void onDestroy() { // In onDestroy() you should tear down anything that was setup for your data source, // eg. cursors, connections, etc. mWidgetItems.clear(); } public int getCount() { return mWidgetItems.size(); } public RemoteViews getViewAt(int position) { // position will always range from 0 to getCount() - 1. // We construct a remote views item based on our widget item xml file, and set the // text based on the position. RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_stack_orders_item); if(position < mWidgetItems.size()) { MoneyTransaction moneyTransaction = mWidgetItems.get(position); String dateStr = FormatTool.getDateStringShort(moneyTransaction.getDate()); rv.setTextViewText(R.id.tv_date, dateStr); rv.setTextViewText(R.id.tv_correspondent, moneyTransaction.getCategoryObject().getName()); rv.setTextViewText(R.id.tv_comment, moneyTransaction.getComment()); Date today = Calendar.getInstance().getTime(); Date date = FormatTool.getStartOfPeriod(moneyTransaction.getDate(), Constants.PERIODICITY_DAY); long days = TimeUnit.MILLISECONDS.toDays(today.getTime() - date.getTime()); if(days == 0) { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.green_with_border); } else if(days == 1) { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.orange_with_border); } else { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.red_with_border); } // Next, we set a fill-intent which will be used to fill-in the pending intent template // which is set on the collection view in StackWidgetProvider. Bundle extras = new Bundle(); extras.putString(FirestoreTables.PATH, moneyTransaction.getPath()); Intent fillInIntent = new Intent(); fillInIntent.setAction(OrdersWidgetProvider.CLICK_ACTION); fillInIntent.putExtras(extras); rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); } // Return the remote views object. return rv; } public RemoteViews getLoadingView() { // You can create a custom loading view (for instance when getViewAt() is slow.) If you // return null here, you will get the default loading view. return null; } public int getViewTypeCount() { return 1; } public long getItemId(int position) { return position; } public boolean hasStableIds() { return true; } public void onDataSetChanged() { repository.getOrders(FormatTool.getEndOfDayString(Calendar.getInstance().getTime())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(transactions -> { mWidgetItems = transactions; StackRemoteViewsFactory.this.onDataSetChanged(); }); } } 


3. Widget provider class
 public class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private List<MoneyTransaction> mWidgetItems = new ArrayList<MoneyTransaction>(); private Context mContext; @Inject FirestoreRepository repository; public StackRemoteViewsFactory(Context context, Intent intent) { mContext = context; Injector.getApplicationComponent().inject(this); } public void onCreate() { } public void onDestroy() { // In onDestroy() you should tear down anything that was setup for your data source, // eg. cursors, connections, etc. mWidgetItems.clear(); } public int getCount() { return mWidgetItems.size(); } public RemoteViews getViewAt(int position) { // position will always range from 0 to getCount() - 1. // We construct a remote views item based on our widget item xml file, and set the // text based on the position. RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_stack_orders_item); if(position < mWidgetItems.size()) { MoneyTransaction moneyTransaction = mWidgetItems.get(position); String dateStr = FormatTool.getDateStringShort(moneyTransaction.getDate()); rv.setTextViewText(R.id.tv_date, dateStr); rv.setTextViewText(R.id.tv_correspondent, moneyTransaction.getCategoryObject().getName()); rv.setTextViewText(R.id.tv_comment, moneyTransaction.getComment()); Date today = Calendar.getInstance().getTime(); Date date = FormatTool.getStartOfPeriod(moneyTransaction.getDate(), Constants.PERIODICITY_DAY); long days = TimeUnit.MILLISECONDS.toDays(today.getTime() - date.getTime()); if(days == 0) { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.green_with_border); } else if(days == 1) { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.orange_with_border); } else { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.red_with_border); } // Next, we set a fill-intent which will be used to fill-in the pending intent template // which is set on the collection view in StackWidgetProvider. Bundle extras = new Bundle(); extras.putString(FirestoreTables.PATH, moneyTransaction.getPath()); Intent fillInIntent = new Intent(); fillInIntent.setAction(OrdersWidgetProvider.CLICK_ACTION); fillInIntent.putExtras(extras); rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); } // Return the remote views object. return rv; } public RemoteViews getLoadingView() { // You can create a custom loading view (for instance when getViewAt() is slow.) If you // return null here, you will get the default loading view. return null; } public int getViewTypeCount() { return 1; } public long getItemId(int position) { return position; } public boolean hasStableIds() { return true; } public void onDataSetChanged() { repository.getOrders(FormatTool.getEndOfDayString(Calendar.getInstance().getTime())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(transactions -> { mWidgetItems = transactions; StackRemoteViewsFactory.this.onDataSetChanged(); }); } } 


4. Layout widget
 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <StackView android:id="@+id/stack_view" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:loopViews="true"/> <TextView android:id="@+id/empty_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:background="@drawable/round_light_button_switcher" android:gravity="center" android:padding="20dp" android:text="@string/empty_view_text" android:textSize="40sp" android:textStyle="bold"/> </FrameLayout> 


5. Layout cards
 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/widget_item" android:layout_width="match_parent" android:layout_height="match_parent" android:loopViews="false" android:orientation="vertical"> <TextView android:id="@+id/tv_date" style="@style/WhiteCenterBold20" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="4dp"/> <TextView android:id="@+id/tv_correspondent" style="@style/WhiteCenterBold20" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="4dp" android:maxLines="2" /> <TextView android:id="@+id/tv_comment" style="@style/White18" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="4dp" android:maxLines="10"/> </LinearLayout> 


6. XML description of the provider
 <?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget_stack_orders_layout" android:minWidth="200dp" android:minHeight="200dp" android:previewImage="@drawable/widget"> </appwidget-provider> 


Implementation issues

This is a bit confusing, but so far uncritical. I still do not understand why this is happening.
Can someone tell me how to fix it?

Source: https://habr.com/ru/post/452998/


All Articles