Skip to content

Add Recent Orders Widget #1567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Add Recent Orders Widget #1567

wants to merge 2 commits into from

Conversation

ashnohe
Copy link

@ashnohe ashnohe commented May 16, 2025

Uses list widget canonical layout from Platform Samples for resizing. Platform Samples Canonical Widget Layouts: https://github.com/android/platform-samples/tree/main/samples/user-interface/appwidgets

Creates a generated widget preview, using APIs in Glance 1.2: https://developer.android.com/jetpack/androidx/releases/glance#1.2.0-alpha01

ashnohe added 2 commits May 16, 2025 10:59
Users list widget canonical layout from Platform Samples for resizing.

Creates a generated widget preview, using APIs in Glance 1.2.
@ashnohe ashnohe requested a review from a team as a code owner May 16, 2025 18:20
@ashnohe ashnohe requested a review from mlykotom May 16, 2025 18:20
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @ashnohe, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

Summary of Changes

Hello team, gemini-code-assist here to provide a summary of this pull request. This PR introduces a new "Recent Orders" widget to the Jetsnack application using Jetpack Glance. The implementation follows the canonical list widget layout patterns demonstrated in the Android Platform Samples, ensuring responsiveness across different widget sizes. It also leverages the new generated widget preview APIs available in Glance 1.2.

Highlights

  • New Glance Widget: Adds a new App Widget, RecentOrdersWidget, implemented using Jetpack Glance, along with its RecentOrdersWidgetReceiver.
  • Responsive Layout: Implements a responsive layout (ImageTextListLayout) for the widget that adapts based on available size, displaying items in a list (with or without images) or a grid.
  • Glance 1.2 Preview API: Integrates the Glance 1.2 generated preview API, allowing the system to automatically generate previews for the widget in different sizes.
  • Widget Data Handling: Includes a basic in-memory data repository (RecentOrdersDataRepository) to provide sample data for the widget and manage data per widget instance.
  • Deep Linking: Adds a deep link for the shopping cart screen to allow the widget's cart icon action to navigate directly to the cart.

Changelog

Click here to see the changelog
  • Jetsnack/app/build.gradle.kts
    • Added androidx.glance:glance-appwidget and androidx.glance:glance-preview dependencies.
    • Updated androidx.glance version to 1.2.0-alpha01.
  • Jetsnack/app/src/main/AndroidManifest.xml
    • Registered the RecentOrdersWidgetReceiver with the necessary intent filter and meta-data pointing to the widget info XML.
  • Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt
    • Added logic in onCreate to call setWidgetPreviews on app startup for API 34+.
    • Implemented setWidgetPreviews function to use GlanceAppWidgetManager to set generated widget previews if they are not already set.
  • Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt
    • Added a deep link (https://jetsnack.example.com/home/cart) to the HomeSections.CART composable route.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/ActionDemonstrationActivity.kt
    • Added a new activity (ActionDemonstrationActivity) to serve as a target for widget actions, displaying a message indicating the action source.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/CollectionsKtx.kt
    • Added a computeIfAbsent extension function for MutableMap.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/RecentOrdersWidget.kt
    • Added the RecentOrdersWidget class extending GlanceAppWidget, defining sizeMode and previewSizeMode.
    • Implemented provideGlance to load data from the repository and provide widget content using ImageTextListLayout.
    • Implemented WidgetContent composable to build the widget UI based on data and size.
    • Implemented providePreview to provide content for generated previews.
    • Added the RecentOrdersWidgetReceiver class extending GlanceAppWidgetReceiver.
    • Implemented onDeleted in the receiver to clean up data associated with deleted widget instances.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/data/RecentOrdersDataRepository.kt
    • Added the RecentOrdersDataRepository class to simulate fetching and holding recent order data.
    • Defined DemoDataItem data class to represent sample order data.
    • Included companion object methods (getImageTextListDataRepo, cleanUp) to manage repository instances per widget ID.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/layout/EmptyListContent.kt
    • Added EmptyListContent composable to display a 'No data' message and button when the order list is empty, using NoDataContent.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/layout/ImageTextListLayout.kt
    • Added the ImageTextListLayout composable, the main layout for the widget.
    • Implemented logic to switch between list and grid views based on widget size (ImageTextListLayoutSize).
    • Defined ImageTextListItemData data class for list item content.
    • Added FilledHorizontalListItem composable to render individual items.
    • Included preview annotations and a preview composable for the layout.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/layout/ListItem.kt
    • Added a generic ListItem composable to structure content within a list item (leading, headline, supporting, trailing).
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/layout/NoDataContent.kt
    • Added a generic NoDataContent composable to display a message and button for empty states.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/layout/RoundedScrollingLazyColumn.kt
    • Added RoundedScrollingLazyColumn composables, providing a LazyColumn with rounded corners and optional item spacing.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/layout/RoundedScrollingLazyVerticalGrid.kt
    • Added RoundedScrollingLazyVerticalGrid composables, providing a LazyVerticalGrid with rounded corners and optional cell spacing.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/utils/ActionUtils.kt
    • Added ActionUtils object with a helper function actionStartDemoActivity to create actions for launching the demo activity.
  • Jetsnack/app/src/main/java/com/example/jetsnack/widget/utils/PreviewAnnotations.kt
    • Added custom annotation classes (SmallWidgetPreview, MediumWidgetPreview, LargeWidgetPreview) for defining common widget preview sizes.
  • Jetsnack/app/src/main/res/drawable/add_shopping_cart.xml
    • Added a new vector drawable for an 'add shopping cart' icon.
  • Jetsnack/app/src/main/res/drawable/shopping_cart.xml
    • Added a new vector drawable for a 'shopping cart' icon.
  • Jetsnack/app/src/main/res/drawable/widget_logo.xml
    • Added a new vector drawable for the Jetsnack widget logo.
  • Jetsnack/app/src/main/res/values-xlarge/dimens.xml
    • Added dimension and integer resources defining target cell size, min/max size, and min/max resize size for the widget on large screens (tablets).
  • Jetsnack/app/src/main/res/values/dimens.xml
    • Added dimension and integer resources defining target cell size, min/max size, and min/max resize size for the widget on handheld devices.
  • Jetsnack/app/src/main/res/values/strings.xml
    • Added string resources for the widget title, name, description, shopping cart button label, and sample no data/add button text.
  • Jetsnack/app/src/main/res/xml/snack_order_widget_info.xml
    • Added the appwidget-provider XML file, configuring the widget's description, initial layout, size constraints (min/max, resize), resize mode, preview image, and category.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


A widget appears,
Showing snacks from past years,
Glance makes it bright,
Adapting to light,
Your orders, now free from all fears.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a "Recent Orders Widget" using Android Glance, which is a significant feature addition. The implementation leverages canonical layouts from platform samples and new Glance 1.2 APIs for widget previews.

Overall, the structure is well-organized, and the use of Glance features like SizeMode.Exact, responsive preview sizes, and per-widget data repositories (RecentOrdersDataRepository) is commendable. The new layout components (ImageTextListLayout, ListItem, etc.) are modular and follow good Compose/Glance practices.

I've identified a critical issue related to image resource handling that could lead to a crash, and a few medium-severity issues concerning package naming, hardcoded strings, and the use of a custom collection extension where a standard library alternative exists. Addressing these will improve the robustness and maintainability of the new widget.

Additionally, there are minor points regarding copyright year consistency and newlines at the end of files, which are good to address for overall code hygiene. I'll also inquire about a potentially missing drawable referenced in the widget's XML configuration.

Summary of Findings

  • Critical: Image Resource Handling: In ImageTextListLayout.kt, the conversion item.supportingImage.toString().toInt() for ImageProvider can cause a NumberFormatException if item.supportingImage is null. This needs to be handled safely.
  • Medium: Package Naming Inconsistency: ActionDemonstrationActivity.kt uses a package name from platform samples (com.example.platform...). It should be moved to a package within the app's namespace (e.g., com.example.jetsnack.widget.ui).
  • Medium: Hardcoded String: In RecentOrdersDataRepository.kt, the trailingIconButtonContentDescription is hardcoded. It should be extracted to a string resource for localization and maintainability.
  • Medium: Custom computeIfAbsent Extension: The custom computeIfAbsentExt in CollectionsKtx.kt (used in RecentOrdersDataRepository.kt) can be replaced by the standard library's MutableMap.getOrPut function, which is more idiomatic and reduces custom code.
  • Low: Copyright Year and EOF Newlines: Many new files have a copyright year of 2025 and are missing a newline at the end of the file. These should be updated for consistency.
  • Query: Missing Preview Drawable: The snack_order_widget_info.xml references @drawable/backward_compatible_widget_preview. This drawable is not in the PR. Is it an existing asset or needs to be added for compatibility with older Android versions?

Merge Readiness

This pull request adds valuable widget functionality. However, due to the critical issue identified in image handling and several medium-severity concerns, I recommend that these changes be addressed before merging. Specifically, the potential crash in ImageTextListLayout.kt must be fixed. Addressing the other points will also improve the overall quality and maintainability of the codebase. As an AI, I am not authorized to approve pull requests; please ensure further review and approval from team members after the suggested changes are made.

Comment on lines +282 to +284
Image(
provider = ImageProvider(item.supportingImage.toString().toInt()),
// contentDescription is null because in this sample, it serves merely as a visual; but if

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

There's a potential crash here if item.supportingImage is null. The supportingImage in ImageTextListItemData is an Int?. If it's null, item.supportingImage.toString() will result in the string "null", and then toInt() will throw a NumberFormatException.

To fix this, you should safely unwrap the nullable Int and only create the ImageProvider and Image composable if it's not null.

How about something like this?

item.supportingImage?.let {
    Image(
        provider = ImageProvider(it),
        // ... other parameters
    )
}

This would also mean the SupportingImage composable itself might need to conditionally render the Image.

        provider = ImageProvider(item.supportingImage ?: 0), // Fallback to a default or handle null appropriately

* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.platform.ui.appwidgets.glance.layout

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The package name com.example.platform.ui.appwidgets.glance.layout seems to be from the platform samples. For better project organization and consistency, would it be more appropriate to move this activity to a package within the com.example.jetsnack namespace, perhaps something like com.example.jetsnack.widget.ui or com.example.jetsnack.ui.widget?

Comment on lines +65 to +67
trailingIconButton = R.drawable.add_shopping_cart,
trailingIconButtonContentDescription = "Add to Shopping Cart",
snackKeys = item.snackKeys

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The trailingIconButtonContentDescription is hardcoded as "Add to Shopping Cart". For better internationalization and maintainability, should this be a string resource? For example, context.getString(R.string.add_to_cart_content_description).

Suggested change
trailingIconButton = R.drawable.add_shopping_cart,
trailingIconButtonContentDescription = "Add to Shopping Cart",
snackKeys = item.snackKeys
trailingIconButtonContentDescription = LocalContext.current.getString(R.string.add_to_cart_content_description), // Assuming you add this string resource

Comment on lines +120 to +122
return synchronized(repositories) {
repositories.computeIfAbsentExt(glanceId) { RecentOrdersDataRepository() }!!
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider using the standard library's getOrPut function for MutableMap here. It achieves the same result as computeIfAbsentExt for this use case (getting or creating a RecentOrdersDataRepository) and is more idiomatic in Kotlin. This would also allow for the removal of the custom computeIfAbsentExt extension from CollectionsKtx.kt.

For example:

repositories.getOrPut(glanceId) { RecentOrdersDataRepository() }

Since RecentOrdersDataRepository() is non-null, the !! operator would no longer be necessary.

        repositories.getOrPut(glanceId) { RecentOrdersDataRepository() }

Copy link
Member

@bentrengrove bentrengrove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments but feel free to merge once fixed


class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
lifecycleScope.launch(Dispatchers.Default) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ok on a background thread? Noob widget question


package com.example.jetsnack.widget

fun <K, V> MutableMap<K,V>.computeIfAbsent(key : K, calulation :(K)->V):V? {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in calculation


package com.example.jetsnack.widget

fun <K, V> MutableMap<K,V>.computeIfAbsent(key : K, calulation :(K)->V):V? {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -12,7 +12,7 @@ androidx-compose-bom = "2025.05.00"
androidx-constraintlayout = "1.1.1"
androidx-core-splashscreen = "1.0.1"
androidx-corektx = "1.16.0"
androidx-glance = "1.1.1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need up update the central toml file in scripts or else this will be overriden by the next sample update

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants