Saturday, May 24, 2025
HomeRuby On RailsClasses From Constructing Android Widgets — Cellular (2022)

Classes From Constructing Android Widgets — Cellular (2022)


By Matt Bowen, James Lockhart, Cecilia Hunka, and Carlos Pereira

When the brand new widget announcement was made for iOS 14, our iOS staff went proper to work designing an expertise to leverage the brand new platform. Nevertheless, widgets aren’t new to Android and have been round for over a decade. Shopify cares deeply about its cell expertise and for so long as we’ve had the Shopify cell app, each our Android and iOS groups ship each function one-to-one in unison. With the highlight now on iOS 14, this was an ideal time to revisit our providing on Android.

Since our contribution was the identical throughout each platforms, identical to our iOS counterparts on the time, we knew retailers have been utilizing our widgets, however they wanted extra.

Our widgets primarily give attention to analytics that assist retailers perceive how they’re doing and achieve insights to make higher selections rapidly about their enterprise. Monitoring metrics is a day by day exercise for lots of our retailers, and on cell, we have now the chance to offer retailers a sooner method to entry this information via widgets. They supply retailers a singular avenue to rapidly get a pulse on their retailers that isn’t out there on the internet.

Add Insights widget
Add Insights widget

After gathering suggestions and constantly searching for alternatives to reinforce our widget capabilities, we’re at our third iteration, and we’ll share with you the challenges we confronted and the way we solved them.

A few years in the past Shopify determined to go full on React Native. New improvement must be accomplished in React Native, and we’re additionally migrating some apps to the know-how. This consists of the flagship admin app, which is the companion app to the widgets.

Then why not write the widgets in React Native?

After performing some preliminary investigation, we rapidly hit some roadblocks (like the truth that RemoteViews are the one method to create widgets). There’s presently no official assist within the React Native neighborhood for RemoteViews, which is required to assist widgets. This felt very akin to a sq. peg in a spherical gap. Our iOS app additionally bumped into points supporting React Native, and we have been operating down the identical path. Shopify believes in utilizing the precise software for the job, we imagine that native improvement was the precise name on this case.

When constructing out our structure for widgets, we needed to create a constant expertise on each Android and iOS whereas preserving platform idioms the place it made sense. Within the sections under, we wish to offer you a view of our experiences constructing widgets. Declaring a few of the harder challenges we confronted. Our purpose is to shed some mild round these much less used surfaces, hopefully give some inspiration, and save time in relation to implementing widgets in your purposes.

Fetching Information

Some sorts of widgets have information that change much less regularly (for instance, reminders) and a few that may be forecasted for your complete day (for instance, calendar and climate). In our case, the retailers want up-to-date metrics about their enterprise, so we have to present information as contemporary as potential. Delays in information could cause confusion, and even worse, delay data that might change an motion. Say you observe the inventory market, you count on the inventory app and widget information to be as updated as potential. If the information is a number of hours stale, you’ll have missed one thing essential! For our widgets to be beneficial, we want data to be contemporary whereas contemplating community utilization.

Fetching Information within the App

Widgets could be stored updated with related and well timed data through the use of information out there regionally or fetching it from a server. The server fetching could be initiated by the widget itself or by the host app. In our case, because the app doesn’t want the identical data the widget wanted, we determined it could make extra sense to fetch it from the widget. 

One profit to how widgets are managed within the Android ecosystem over iOS is the flexibleness. On iOS you will have restricted communication between the app and widget, whereas on Android there doesn’t appear to be the identical restrictions. This turns into clear once we take into consideration how we configure a widget. The widget configuration display screen has entry to the entire libraries and lessons that our predominant app does. It’s no completely different than every other display screen within the app. That is principally true with the widget as effectively. We are able to entry the assets contained in our predominant utility, so we don’t must duplicate any code. The one restrictions in a widget include constructing views, which we’ll discover later.

After we save our configuration, we use shared preferences to persist information between the configuration display screen and the widget. When a widget replace is triggered, the shared preferences information for a given widget is used to construct our request, and the outcomes are displayed throughout the widget. We are able to learn that information from anyplace in our app, permitting us to reuse this information in different components of our app if desired.

Making the Widgets Antifragile

The widget structure is inbuilt a manner that updates are aware of battery utilization, the place updates are managed by the system. In the identical manner, our widgets should even be aware of saving bandwidth when fetching information over a community. Whereas growing our second iteration, we got here throughout a peculiar downside that was exacerbated by our particular use case. Since we want information to be contemporary, we at all times pull new information from our backend on each replace. Every replace is roughly quarter-hour aside to keep away from having our widgets cease updating. What we discovered is that widgets name their replace technique onUpdate(), greater than as soon as in an replace cycle. In widgets like calendar, these further calls come with out a lot further price as the information is saved regionally. Nevertheless, in our app, this was triggering two to 5 further community calls for a similar widget with the identical information in fast succession.

With a view to appropriate the pointless roundtrips, we constructed a easy short-lived cache:

  1. The system asks our widget to supply new information from Reportify (Shopify’s information service)
  2. We first look into the native cache utilizing the widgetID supplied by the system.
  3. If there’s information, and that information was set lower than one minute in the past, we return it and keep away from making a community request. We additionally have in mind configuration resembling locale in order to not keep away from forcing updates after a language change.
  4. In any other case, we fetch the information as regular and retailer it within the cache with the timestamp.
A flow diagram highlighting the steps of the cache
The easy short-lived cache move

With this resolution, we diminished unused community calls and system load and averted accumulating incorrect analytics.

Implementing Decoder Technique with Dynamic Picks

We observe the same strategy as we have now on iOS. We create a dynamic set of queries primarily based on what the service provider has configured.

For every metric we have now a corresponding definition implementation. This strategy permits every metric the flexibility to have full flexibility round what information it wants, and the way it decodes the information from the response.

When Android asks us to replace our widgets, we pull the retailers choice from our configuration object. Since every of the metric IDs has a definition, we map over them to create a dynamic set of queries.

We embrace an extension on our response object that binds the definitions to a decoder. Our service sends again an array of the response information comparable to the queries made. We map over the unique definitions, decoding every chunk to the anticipated return kind.

Constructing the UI

Much like iOS, we assist three widget sizes for variations previous to Android 12 and observe the identical guidelines for cell structure, apart from the small widget. The small widget on Android helps a single metric (in comparison with the 2 on iOS) and the smallest widget dimension on Android is a 2×1 grid. We rapidly discovered that solely a single metric would slot in this house, so this design differs barely between the platforms.

Not like iOS with swift previews, we have been restricted to XML previews and operating the widget on the emulator or gadget. We’re additionally constructing widgets dynamically, so even XML previews have been comparatively ineffective if we needed to see a whole widget preview. Widgets are presently on the 2022 Jetpack Compose roadmap, so that is prone to change quickly with Jetpack composable previews.

With the addition of dynamic layouts in Android 12, we created 5 extra sizes to assist every dimension in between the unique three. These new sizes are distinctive to Android. This additionally led to utilizing grid sizes as a part of our naming conference as you’ll be able to see in our WidgetLayout enum under.

For the construction of our widget, we used an enum that acts as a blueprint to map the suitable structure file to an space of our widget. That is significantly helpful once we wish to add a brand new widget as a result of we merely want so as to add a brand new enum configuration.

To construct the widgets dynamically, we learn our configuration from shared preferences and supply that data to the RemoteViews API.

In the event you’re conversant in the RemoteViews API, it’s possible you’ll discover the updateView() technique, which isn’t a default RemoteViews technique. We created this extension technique on account of a problem we bumped into whereas constructing our widget structure on this dynamic method. When a widget updates, the brand new distant views get appended to the prevailing ones. As you’ll be able to most likely guess, the widget didn’t look so nice. Even worse, extra distant views get appended on every subsequent replace. We discovered that combining the 2 RemoteViews API strategies removeAllViews() and addView() solved this downside.

As soon as we construct our distant views, we then go the guardian distant view to the AppWidgetProvider updateAppWidget() technique to show the specified structure.

It’s price noting that we tried to make use of partiallyUpdateAppWidget() to cease our distant views from appending to one another, however encountered the identical subject.

Utilizing Dynamic Dates

One essential piece of data on our widget is the final up to date timestamp. It helps take away confusion by permitting the retailers to rapidly understand how contemporary the information is they’re taking a look at. If the information is sort of stale (say you went to the cottage for the weekend and missed a couple of updates) and there wasn’t a displayed timestamp, you’d assume the information you’re taking a look at is as much as the second. This could trigger pointless confusion for our retailers. The answer right here was to make sure there’s some communication to our service provider when the final replace was made.

In our earlier design, we solely had small widgets, they usually have been capable of show just one metric. This data resulted in an extended piece of textual content that, on smaller units, would generally wrap and present over two strains. This was nice when house was ample in our older design however not in our new information wealthy designs. We explored how we might greatest work with timestamps on widgets, and probably the most promising resolution was to make use of relative time. As an alternative of getting a static worth resembling “as of three:30pm” like our earlier iteration. We’d have a dynamic date that might seem like: “1 min, 3 sec in the past.”

One factor to recollect is that regardless that the widget is seen, we have now a restricted variety of updates we will set off. In any other case, it could be consuming numerous pointless assets on the gadget. We knew we couldn’t preserve triggering updates on the widget as usually as we needed. Android has a method for fixing this with TextClock. Nevertheless, there’s no assist for relative time, which wouldn’t be helpful in our use case. We additionally explored utilizing Alarms, however doubtlessly updating each minute would devour an excessive amount of battery. 

One large takeaway we had from these explorations was to at all times check your widgets below completely different circumstances. Particularly low battery or poor community. These surfaces are way more restrictive than basic purposes and the OS is more likely to disregard updates.

We finally determined that we wouldn’t use relative time and stored the widget’s refresh time as a timestamp. This manner we have now full management over issues like date formatting and styling.

Including Configuration

Our new widgets have quite a lot of configuration choices, permitting our retailers to decide on precisely what they care about. For every widget dimension, the service provider can choose the shop, a sure variety of metrics and a date vary. That is the one a part of the widget that doesn’t use RemoteViews, so there aren’t any restrictions on what kind of View it’s possible you’ll wish to use. We share data between the configuration and the widget by way of shared preferences.

Insights widget configuration
Insights widget configuration

Working with Charts and Photographs

Android widgets are restricted to RemoteViews as their constructing blocks and are very restrictive by way of the view varieties supported. If you should assist something exterior of primary textual content and pictures, you should be a bit inventive.

Our widgets assist each a sparkline and spark bar chart constructed utilizing the MPAndroidCharts library. We now have these charts already configured and styled in our predominant utility, so the reuse right here was excellent; besides, we will’t use any customized Views in our widgets. Fortunately, this library is creating the charts by way of drawing to the canvas, and we merely export the charts as a bitmap to a picture view. 

As soon as we have been capable of measure the widget, we constructed a chart of the required dimension, create a bitmap, and set it to a RemoteView.ImageView. One small factor to recollect with this strategy, is that if you wish to have clear backgrounds, you’ll have to make use of ARGB_8888 because the Bitmap Config. This straightforward bitmap to ImageView strategy allowed us to deal with any customized drawing we would have liked to do. 

Eliminating Flickering

One minor, however annoying subject we encountered all through the period of the mission is what we prefer to name “widget flickering.” Flickering is a side-effect of the widget updating its information. Between updates, Android makes use of the initialLayout from the widget’s configuration as a placeholder whereas the widget fetches its information and builds its new RemoteViews. We discovered that it wasn’t potential to eradicate this conduct, so we applied a few methods to cut back the frequency and period of the glint.

The primary technique is used when a service provider first locations a widget on the house display screen. That is the place we will cut back the frequency of flickering. In an earlier part “Making the Widgets Antifragile,” we shared our short-lived cache. The cache comes into play as a result of the OS will set off a number of updates for a widget as quickly because it’s positioned on the house display screen. We’d generally see a fast three or 4 sparkles, brought on by updates of the widget. After the widget will get its information for the primary time, we stop any extra updates from occurring for the primary 60 seconds, lowering the frequency of flickering.

The second technique reduces the period of a flicker (or how lengthy the initialLayout is displayed). We retailer the widgets configuration as a part of shared preferences every time it’s up to date. We at all times have a snapshot of what widget data is presently displayed. When the onUpdate() technique is known as, we invoke a renderEarlyFromCache() technique as early as potential. The aim of this technique is to construct the widget by way of shared preferences. We offer this cached widget as a placeholder till the brand new information has arrived.

Gathering Analytics

Largest Insights widget in light mode
Largest widget in mild mode

Since our first widgets have been developed, we added strategic analytics in key areas in order that we might perceive how retailers have been utilizing the performance.  This allowed us to study from the utilization so we might enhance on them. The info staff constructed dashboards displaying detailed views of what number of widgets have been put in, the most well-liked metrics, and sizes.

Many of the information used to construct the dashboards got here from analytics occasions fired via the widgets and the Shopify app.

For these new widgets, we needed to raised perceive adoption and retention of widgets, so we would have liked to seize how customers are configuring their widgets over time and which of them are being added or eliminated.

Detecting Addition and Elimination of Widgets 

Not like iOS, capturing this information in Android is straight-forward. To seize when a service provider provides a widget, we ship our analytical occasion when the configuration is saved. When eradicating a widget, the widgets built-in onDeleted technique provides us the widget ID of the eliminated widget. We are able to then lookup our widget data in shared preferences and ship our occasion prior completely deleting the widget data from the gadget. 

After we began improvement of our new widgets, our utility was focusing on Android 11. We knew we’d be focusing on Android 12 finally, however we didn’t need the improve to dam the discharge. We determined to implement Android 12 particular options as soon as our utility focused the newer model, resulting in an unexpected subject through the improve course of with widget choice.

Our strategy to widget choice in earlier variations was to show every out there dimension as an possibility. With the introduction of responsive layouts, we not wanted to show every dimension as its personal possibility. Retailers can now decide a single widget and resize to their desired structure. In earlier variations, retailers can choose a small, medium, and huge widget. In variations 12 and up, retailers can solely choose a single widget that may be resized to the identical layouts as small, medium, massive, and several other different layouts that fall in between. This sample follows what Google does with their massive climate widget included on units, in addition to an instance of their documentation. We disabled the medium and small widgets in Android 12 by including a flag to our AndroidManifest and setting that worth in attrs.xml for every model:

The strategy above behaves as anticipated, the medium and small widgets have been now unavailable from the picker. Nevertheless, if a service provider was already on Android 12 and had added a medium or massive widget earlier than our widget replace, these widgets have been faraway from their house display screen. This might simply be seen as a bug and cut back confidence within the function. On reflection, we might have prototyped what the improve would have regarded prefer to a service provider who was already on Android 12.

Permitting solely the big widget to be out there was a data-driven choice. By monitoring widget utilization at launch, we noticed that the big widget was the most well-liked and eradicating the opposite two would have the least influence on present widget retailers.

Constructing New Layouts

We encountered an error when constructing the brand new layouts that match between the unique small, medium and huge widgets.

After researching the error, we have been exceeding the Binder transaction buffer. Nevertheless, the buffer’s dimension is 1mb and the error displayed .66mb, which wasn’t exceeding the documented buffer dimension. The error has appeared to stump numerous builders. After experimenting with methods to get the scale down, we discovered that we might both drop a few complete layouts or take away assist for a fourth and fifth row of the small metric. We selected the latter, which is why our 2×3 widget solely has three rows of knowledge when it has room for 5.

Rethinking the Configuration Display

Now that we have now one widget to select from, we needed to rethink what our configuration display screen would seem like to a service provider. With out our three fastened sizes, we might not show a set variety of metrics in our configuration. 

Our solely selection was to show the utmost variety of metrics out there for the biggest dimension (which is seven on the time of this writing). Not solely did this take advantage of sense to us from a UX perspective, however we additionally needed to do it this manner due to how the brand new responsive layouts work. Android has to know the entire potential layouts forward of time. Even when a person shrinks their widget to a dimension that shows a single metric, Android has to know what the opposite six are, so it may be resized to our largest structure with none hiccups.

We additionally up to date the outline that’s displayed on the high of the configuration display screen that explains this conduct.

Capturing Extra Analytics

On iOS, we seize analytical information when a service provider reconfigures a widget to achieve insights into utilization patterns. Reconfiguration for Android was solely potential in model 12 and because of the limitations of the AppWidgetProvider’s onAppWidgetOptionsChanged() technique, we have been unable to seize this information on Android.

I’ll share extra details about constructing our layouts so as to give context to our downside. Setting breakpoints for brand spanking new dynamic layouts works very effectively throughout all units. Google recommends making a mapping of your breakpoints to the specified distant view structure. To construct on a earlier instance the place we confirmed the buildWidgetRemoteView() technique, we used this technique once more as a part of our breakpoint mapping. This strategy permits us to reliably map our breakpoints to the specified widget structure.

When reconfiguring or resizing a widget, the onAppWidgetOptionsChanged() technique is known as. That is the place we’d wish to seize our analytical information about what had modified. Sadly,  this view mapping doesn’t exist right here. We now have entry to width and peak values which might be measured in dp, initially showing to be helpful. At first, we felt that we might discern these measurements into one thing significant and map these values again to our structure sizes. After testing on a few units, we realized that the strategy was unreliable and would result in a big quantity of unhealthy analytical information. With out confidently figuring out what dimension we’re coming from, or going to, we determined to omit this explicit analytics occasion from Android. We hope to convey this to Google’s consideration, and get it included in a future launch.

Already having a pair of present widgets, we needed to determine the way to transition to the brand new widgets as they’d be changing the prevailing implementation.

We didn’t discover a lot documentation round migrating widgets. The docs solely supplied a method to improve your widget, which implies including the brand new options of Android 12 to one thing you already had. This wasn’t relevant to us since our present widgets have been so completely different from those we have been constructing.

The most important subject that we couldn’t get round was associated to the sizing methods of our present and new widgets. The prevailing widgets used a set width and peak in order that they’d at all times be sq.. Our new widgets take no matter house is obtainable. There wasn’t a method to assure that the brand new widget would slot in the out there house that the prevailing widget had occupied. If the prevailing widget was the identical dimension as the brand new one, it could have been price exploring additional.

The preliminary plan we had hoped for, was to make one in all our widgets rework into the brand new widget whereas eradicating the opposite one. Given the above, this technique wouldn’t work.

The compromise we got here to, in order to not fully take away all of a service provider’s widgets in a single day, was to deprecate the outdated widgets on the similar time we launch the brand new one. To deprecate, we up to date our outdated widget’s UI to show a message informing that the widget is not supported and the service provider should add the brand new ones.

Screen displaying the notice: This widget is no longer active. Add the new Shopify Insights widget for an improved view of your data. Learn more.
Widget deprecation message

There’s no manner so as to add a brand new widget programmatically or to convey the service provider to the widget picker by tapping on the outdated widget. We added some communication to assist ease the transition by updating our assist middle docs, together with data round the way to use widgets, pointing our outdated widgets to open the assistance middle docs, and simply giving plenty of time earlier than eradicating the deprecation message. Ultimately, it wasn’t probably the most splendid scenario and we got here away studying in regards to the pitfalls throughout the two ecosystems.

As we proceed to find out about how retailers use our new era of widgets and Android 12 options, we’ll proceed to hone in on the most effective expertise throughout each our platforms. This additionally opens the way in which for different groups at Shopify to construct on what we’ve began and create extra widgets to assist Retailers. 

On the subject of cell solely platforms, this leads us into getting on top of things on WearOS. Our WatchOS app is about to get a refresh with the addition of Widgetkit; it seems like a good time to lastly give our Android Retailers watch assist too!

Matt Bowen is a cell developer on the Core Admin Expertise staff. Situated in West Warwick, RI. Private hobbies embrace exercising and watching the Boston Celtics and New England Patriots.

James Lockhart is a Workers Developer primarily based in Ottawa, Canada. Experiencing cell improvement over the previous 10+ years: from Phonegap to native iOS/Android and now React native. He’s an avid baker and cook dinner when not contributing to the Shopify admin app.

Cecilia Hunka is a developer on the Core Admin Expertise staff at Shopify. Based mostly in Toronto, Canada, she loves dwell music and jigsaw puzzles.

Carlos Pereira is a Senior Developer primarily based in Montreal, Canada, with greater than a decade of expertise constructing native iOS purposes in Goal-C, Swift and now React Native. At present contributing to the Shopify admin app.


Wherever you’re, your subsequent journey begins right here! If constructing programs from the bottom as much as clear up real-world issues pursuits you, our Engineering weblog has tales about different challenges we have now encountered. Intrigued? Go to our Engineering profession web page to seek out out about our open positions and find out about Digital by Design.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments