{"id":59345,"date":"2026-01-12T10:05:00","date_gmt":"2026-01-12T18:05:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=59345"},"modified":"2026-01-12T10:00:42","modified_gmt":"2026-01-12T18:00:42","slug":"how-to-build-android-widgets-with-dotnet-maui","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/how-to-build-android-widgets-with-dotnet-maui\/","title":{"rendered":"How to Build Android Widgets with .NET MAUI"},"content":{"rendered":"<blockquote><p>This is a guest blog from <a href=\"https:\/\/www.linkedin.com\/in\/toinedeboer\/\">Toine de Boer<\/a>.<\/p><\/blockquote>\n<p>This blog takes a look at the Android side of the interactive widget created in the previous blog on <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/how-to-build-ios-widgets-with-dotnet-maui\/\">iOS Widgets<\/a>. Android is generally less strict and easier to work with, and you can build everything directly within your .NET MAUI project in Visual Studio. The complexity comes from the many available options to get a task done and the need to consider older Android versions.<\/p>\n<p>Just as in the iOS widget blog, this isn&#8217;t a step-by-step tutorial. Instead, it highlights the biggest and most important parts in the order you&#8217;ll typically encounter obstacles when building an Android widget. It begins with creating a simple static widget and gradually builds toward a configurable, fully interactive widget. Source of a fully working widget on <a href=\"https:\/\/github.com\/Toine-db\/Maui.WidgetExample\">GitHub &gt; Maui.WidgetExample<\/a>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2026\/01\/android-widgetexample.webp\" alt=\"Screenshot of an example Android widget\" \/><\/p>\n<h2>Prerequisites<\/h2>\n<p>For Android widgets there are no real prerequisites. Although there are no .NET MAUI components for Android widgets, we can build them ourselves using the native Android approach. The upside is that everything can be done inside Visual Studio; optionally you can use Android Studio for designing layouts with its visual editor and preview tools. Having worked extensively with Android XML layouts in the past, and because this blog focuses on functionality rather than appearance, I&#8217;m manually creating the XML layouts while using Copilot for assistance.<\/p>\n<p>As soon as I want something that looks a bit more polished, I switch to Android Studio to create the layouts in XML. To do this, create a dummy Android project, add a widget (for example, via File &gt; New &gt; Widget), and start designing the XML layout. This allows you to take full advantage of the live preview tool and the attributes panel, where you can see and edit all available options for each view.<\/p>\n<p>.NET MAUI handles Android-specific resource files well when you organize them in a folder structure similar to a native Android app in the <code>.\/Platforms\/Resources<\/code> folder. Thanks to this, creating a widget does NOT require any changes in your <code>.csproj<\/code>\u2014the project file remains untouched. I create or copy files and folders outside Visual Studio to prevent Visual Studio from modifying the <code>.csproj<\/code>.<\/p>\n<p>For the iOS widgets, I already created a .NET MAUI project demonstrating a way to communicate with a widget, and I&#8217;ll be reusing that for Android. Most of the existing code remains the same. All the added Android widget code goes into the <code>.\/Platforms\/Android<\/code> folder.<\/p>\n<h2>Creating the Widget<\/h2>\n<p>Android widgets are not normal views like in your app, but they do live inside the .NET MAUI app. Widgets are limited to the set of Android views available through RemoteViews; custom views are not supported. You can still style them reasonably well, though you will need to be creative with shapes, vectors, and other drawables.<\/p>\n<p>The starting point of an Android widget is an <code>AppWidgetProvider<\/code>. It can use an <code>AppWidgetManager<\/code> to supply <code>RemoteViews<\/code> to widgets based on ID. <code>RemoteViews<\/code> are used by Android to display views from another process; they use the same XML layout style as normal Android views, but they are loaded into a <code>RemoteViews<\/code> object.<\/p>\n<pre><code class=\"language-csharp\">[BroadcastReceiver(Label = \"My Widget\")]\r\n[MetaData(AppWidgetManager.MetaDataAppwidgetProvider, Resource = \"@xml\/mywidget_provider_info\")]\r\npublic class MyWidgetProvider : AppWidgetProvider\r\n{\r\n   public override void OnUpdate(Context? context, AppWidgetManager? appWidgetManager, int[]? appWidgetIds)\r\n   {\r\n      if (context == null || appWidgetIds == null || appWidgetManager == null)\r\n      {\r\n         return;\r\n      }\r\n\r\n      foreach (var appWidgetId in appWidgetIds)\r\n      {\r\n         var views = new RemoteViews(context.PackageName, Resource.Layout.mywidget);\r\n         views.SetTextViewText(Resource.Id.widgetText, \"Count:5 (static)\");\r\n         appWidgetManager.UpdateAppWidget(appWidgetId, views);\r\n      }\r\n   }\r\n}<\/code><\/pre>\n<p>The <code>AppWidgetProvider<\/code> relies on a configuration file located in the <code>Resources\/xml<\/code> folder, referenced via the <code>MetaData<\/code> attribute in the <code>Resource<\/code> field. This file lets you configure widget settings such as preview images, dimensions, resize limits, and features.<\/p>\n<pre><code class=\"language-xml\">&lt;!-- Resources\/xml\/mywidget_provider_info.xml --&gt;\r\n&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;appwidget-provider xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\r\n   android:minWidth=\"120dp\"\r\n   android:minHeight=\"80dp\"\r\n   android:maxResizeWidth=\"140dp\"\r\n   android:updatePeriodMillis=\"0\"\r\n   android:initialLayout=\"@layout\/mywidget\"\r\n   android:resizeMode=\"horizontal|vertical\"\r\n   android:widgetCategory=\"home_screen\"\r\n   android:configure=\"widgetexample.WidgetConfigurationActivity\"\r\n   android:widgetFeatures=\"reconfigurable\"\r\n   android:previewImage=\"@drawable\/mywidget_preview_image\" \/&gt;<\/code><\/pre>\n<p>One of the most important entries is <code>android:initialLayout<\/code>, which refers to the view layout located in <code>Resources\/layout<\/code>. This layout is loaded into a <code>RemoteView<\/code>.<\/p>\n<pre><code class=\"language-xml\">&lt;!-- Resources\/layout\/mywidget.xml --&gt;\r\n&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;LinearLayout\r\n   xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\r\n   android:orientation=\"horizontal\"\r\n   android:layout_width=\"match_parent\"\r\n   android:layout_height=\"wrap_content\"&gt;\r\n\r\n   &lt;TextView\r\n      android:id=\"@+id\/widgetText\"\r\n      android:text=\"Static widget\"\r\n      android:textSize=\"16sp\"\r\n      android:layout_width=\"wrap_content\"\r\n      android:layout_height=\"wrap_content\" \/&gt;\r\n&lt;\/LinearLayout&gt;<\/code><\/pre>\n<p>A big difference between a <code>RemoteView<\/code> and a regular Android view is that you can&#8217;t directly manipulate the view from your code. Each view only allows a limited set of properties to be updated, and all changes must go through the <code>RemoteView<\/code>s object using its helper methods. You pass in the resource ID of the view you want to update, for example: <code>views.SetTextViewText(Resource.Id.widgetText, \"Hello World\");<\/code><\/p>\n<p>At this point, you can build the app and should see the widget appear in your phone\u2019s widget gallery.<\/p>\n<h2>Data sharing between App and Widget<\/h2>\n<p>Sharing data between app and widget is simpler than on iOS because inside the widget you can use C# .NET MAUI code. You can even share in-memory data between widget and app to some extent, although this can be unreliable due to lifecycle issues. A persistent storage approach is recommended. Because we already set up shared data storage for the iOS widgets, we continue using the existing <code>SharedPreferences<\/code> mechanism on Android for cross-platform consistency.<\/p>\n<pre><code class=\"language-csharp\">\/\/ example how to store data in .NET MAUI (Preferences)\r\nPreferences.Set(\"MyDataKey\", \"my data to share\", \"group.com.enbyin.WidgetExample\");\r\n\r\n\/\/ example how to store data on Android, on the same location (SharedPreferences)\r\nvar preferences = context.GetSharedPreferences(\"group.com.enbyin.WidgetExample\", Context.MODE_PRIVATE);\r\nvar value = preferences.GetString(\"MyDataKey\", null);<\/code><\/pre>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>Storage keys are case-sensitive; keep keys simple and lowercase to avoid problems.<\/div><\/p>\n<h2>Communication from App to Widget<\/h2>\n<p>Like on iOS, the Android widget doesn&#8217;t know when the app updates data, and the app doesn&#8217;t automatically know when the widget does. On Android, there are several ways to communicate between the app and the widget; you can even update widget views at runtime from your app. The most reliable mechanism is using <code>Intents<\/code>.<\/p>\n<p>The principle is simple: you create an <code>Intent<\/code> and give it an action string. This <code>Intent<\/code> can then be broadcast whenever needed; for example, after a background process finishes or when a user presses a button on a widget.<\/p>\n<pre><code class=\"language-csharp\">\/\/ Broadcast an Intent with action \u2018ActionAppwidgetUpdate\u2019\r\nvar intent = new Android.Content.Intent(AppWidgetManager.ActionAppwidgetUpdate);\r\nAndroid.App.Application.Context.SendBroadcast(intent);<\/code><\/pre>\n<p>Components that need to receive <code>Intent<\/code>s declare the same action string. In .NET MAUI, subscribing to <code>Intent<\/code> actions is straightforward using the <code>[IntentFilter]<\/code> attribute. If a component needs to receive <code>Intent<\/code>s from outside its own app, set <code>Exported = true<\/code>. Widgets need this because they effectively live outside the app.<\/p>\n<pre><code class=\"language-csharp\">\/\/ Subscribing your AppWidgetProvider to listen to Intents\r\n[BroadcastReceiver(Label = \"My Widget\", Exported = true)]\r\n[IntentFilter(new[] { AppWidgetManager.ActionAppwidgetUpdate })]\r\n[MetaData(AppWidgetManager.MetaDataAppwidgetProvider, Resource = \"@xml\/mywidget_provider_info\")]\r\npublic class MyWidgetProvider : AppWidgetProvider\r\n{\r\n   public override void OnReceive(Context? context, Intent? intent)\r\n   {\r\n      var myIntent = Intent;\r\n      \/\/ ...\r\n   }\r\n}<\/code><\/pre>\n<pre><code class=\"language-csharp\">\/\/ Subscribing a Service to listen to Intents\r\n[Service(Exported = true)]\r\n[IntentFilter(new[] { AppWidgetManager.ActionAppwidgetUpdate })]\r\npublic class WidgetListenerService : Service\r\n{\r\n   public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)\r\n   {\r\n      var myIntent = intent;\r\n      return StartCommandResult.NotSticky;\r\n   }\r\n}<\/code><\/pre>\n<pre><code class=\"language-csharp\">\/\/ Subscribing an Activity to listen to Intents\r\n[IntentFilter(new[] { Intent.ActionView })]\r\npublic class MyActivity : Activity\r\n{ \r\n   protected override void OnCreate(Bundle savedInstanceState)\r\n   {\r\n      base.OnCreate(savedInstanceState);\r\n      var myIntent = Intent;\r\n   }\r\n}<\/code><\/pre>\n<h3>Refreshing Widgets Manually<\/h3>\n<p>The <code>AppWidgetProvider<\/code> listens by default to <code>Intent<\/code>s with the built\u2011in action <code>AppWidgetManager.ActionAppwidgetUpdate<\/code>. When such an <code>Intent<\/code> is received, it typically triggers a refresh of the widget. You can limit which widgets respond by scoping the <code>Intent<\/code> to your package and including specific widget IDs.<\/p>\n<pre><code class=\"language-csharp\">\/\/ Refreshing all Widgets of a specific AppWidgetProvider\r\npublic static void RefreshWidget(Context context)\r\n{\r\n   var appWidgetManager = AppWidgetManager.GetInstance(context);\r\n   var componentName = new ComponentName(context, Java.Lang.Class.FromType(typeof(MyWidgetProvider)));\r\n   var appWidgetIds = appWidgetManager?.GetAppWidgetIds(componentName);\r\n\r\n   var intent = new Intent(AppWidgetManager.ActionAppwidgetUpdate);\r\n   intent.SetPackage(context.PackageName);\r\n   intent.PutExtra(AppWidgetManager.ExtraAppwidgetIds, appWidgetIds);\r\n\r\n   context.SendBroadcast(intent);\r\n}<\/code><\/pre>\n<pre><code class=\"language-csharp\">\/\/ Handling incoming Intent on an AppWidgetProvider\r\n[BroadcastReceiver(Label = \"My Widget\", Exported = true)]\r\n[IntentFilter(new[]{ AppWidgetManager.ActionAppwidgetUpdate})]\r\n[MetaData(AppWidgetManager.MetaDataAppwidgetProvider, Resource = \"@xml\/mywidget_provider_info\")]\r\npublic class MyWidgetProvider : AppWidgetProvider\r\n{\r\n   public override void OnUpdate(Context? context, AppWidgetManager? appWidgetManager, int[]? appWidgetIds)\r\n   {\r\n      if (context == null || appWidgetManager == null || appWidgetIds == null)\r\n      {\r\n         return;\r\n      }\r\n\r\n      foreach (var appWidgetId in appWidgetIds)\r\n      {\r\n         var views = BuildRemoteViews(context, appWidgetId);\r\n         appWidgetManager.UpdateAppWidget(appWidgetId, views);\r\n      }\r\n   }\r\n\r\n   public override void OnReceive(Context? context, Intent? intent)\r\n   {\r\n      base.OnReceive(context, intent);\r\n   }\r\n}<\/code><\/pre>\n<h3>Refreshing Widgets by Schedule<\/h3>\n<p>Android offers several ways to schedule widget updates, and these approaches can be combined:<\/p>\n<ol>\n<li><code>updatePeriodMillis<\/code> setting: Defined in the <code>appwidget-provider<\/code> XML configuration file. Easiest option, uses a fixed update interval, minimum 30 minutes.<\/li>\n<li><code>AlarmManager<\/code>: Capable of repeatedly broadcasting an <code>Intent<\/code>. Minimum interval of about 60 seconds; may be restricted. Alarms are not restored after device restart.<\/li>\n<li><code>WorkManager<\/code>: Reliable, survives device restarts, supports many configuration options. Minimum repeat interval is 15 minutes.<\/li>\n<\/ol>\n<h2>Making Widgets Interactive<\/h2>\n<p>Widgets can allow users to perform small actions, for example by pressing a button. These actions are triggered via <code>Intent<\/code>s and must be wrapped in a <code>PendingIntent<\/code> so they can be executed long after the widget and its views have been created. <code>PendingIntent<\/code>s are reusable; keep the <code>requestCode<\/code> unique to avoid overwriting them.<\/p>\n<pre><code class=\"language-csharp\">\/\/ Attach Intent to the increment button\r\nvar incrementIntent = new Intent(context, typeof(MyWidgetProvider));\r\nincrementIntent.SetAction(\"com.enbyin.WidgetExample.INCREMENT_COUNTER\");\r\nvar incrementPendingIntent = PendingIntent.GetBroadcast(\r\n   context,\r\n   101,\r\n   incrementIntent,\r\n   PendingIntentFlags.UpdateCurrent | (Build.VERSION.SdkInt &gt;= BuildVersionCodes.S ? PendingIntentFlags.Mutable : 0)\r\n);\r\n\r\nviews.SetOnClickPendingIntent(Resource.Id.widgetIncrementButton, incrementPendingIntent);<\/code><\/pre>\n<p>Handle button interactions by listening for custom actions in the <code>AppWidgetProvider<\/code>:<\/p>\n<pre><code class=\"language-csharp\">[BroadcastReceiver(Label = \"My Widget\", Exported = true)]\r\n[IntentFilter(new[] {\r\n   AppWidgetManager.ActionAppwidgetUpdate,\r\n   \"com.enbyin.WidgetExample.INCREMENT_COUNTER\"\r\n})]\r\n[MetaData(AppWidgetManager.MetaDataAppwidgetProvider, Resource = \"@xml\/mywidget_provider_info\")]\r\npublic class MyWidgetProvider : AppWidgetProvider\r\n{\r\n   public override void OnReceive(Context? context, Intent? intent)\r\n   {\r\n      if (intent == null || context == null)\r\n      {\r\n         base.OnReceive(context, intent);\r\n         return;\r\n      }\r\n\r\n      switch (intent.Action)\r\n      {\r\n         case \"com.enbyin.WidgetExample.INCREMENT_COUNTER\":\r\n         {\r\n            var currentCount = Preferences.Get(MainPage.SharedStorageAppIncomingDataKey, 0);\r\n            currentCount++;\r\n            Preferences.Set(MainPage.SharedStorageAppIncomingDataKey, currentCount);\r\n            UpdateAllWidgets(context);\r\n            return;\r\n         }\r\n      }\r\n\r\n      base.OnReceive(context, intent);\r\n   }\r\n}<\/code><\/pre>\n<h2>Communication from Widget to App<\/h2>\n<p>All triggers, data transmissions, and other forms of communication to and from the widget are handled via <code>Intent<\/code>s. Components such as BroadcastReceivers, Services, AppWidgetProviders, and Activities can specify which types of <code>Intent<\/code>s they listen to, and can also broadcast any <code>Intent<\/code> for others to receive. Below is a brief overview of the data flows showing how these components communicate via <code>Intent<\/code>s to manage the interactive widget.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2026\/01\/android-widget-data-flow.webp\" alt=\"Widget data flow on Android\" \/><\/p>\n<p>You can also use an <code>Intent<\/code> to launch your app. Instead of broadcasting <code>Intent<\/code>s in the background, send them directly to an <code>Activity<\/code> by using <code>PendingIntent.GetActivity()<\/code>. Like any <code>Intent<\/code>, you can attach data to it. A common approach when launching the app with an <code>Intent<\/code> is to use deep links (URLs) to pass structured data into the app. You can then retrieve the incoming data in <code>OnCreate()<\/code> (for a cold start) or <code>OnNewIntent()<\/code> (when the activity is already running).<\/p>\n<pre><code class=\"language-csharp\">\/\/ Example of making a PendingIntent using Deep Link \/ URL\r\nvar openAppIntent = new Intent(Intent.ActionView);\r\nopenAppIntent.SetData(global::Android.Net.Uri.Parse($\"{App.UrlScheme}:\/\/{App.UrlHost}?counter={currentCount}\"));\r\nopenAppIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTop);\r\nvar openAppPendingIntent = PendingIntent.GetActivity(\r\n   context,\r\n   103,\r\n   openAppIntent,\r\n   PendingIntentFlags.UpdateCurrent | (Build.VERSION.SdkInt &gt;= BuildVersionCodes.S ? PendingIntentFlags.Immutable : 0)\r\n);\r\n\r\nviews.SetOnClickPendingIntent(Resource.Id.widgetText, openAppPendingIntent);<\/code><\/pre>\n<pre><code class=\"language-csharp\">[Activity]\r\n[IntentFilter(new[] { Intent.ActionView },\r\n   Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },\r\n   DataScheme = App.UrlScheme,\r\n   DataHost = App.UrlHost)]\r\npublic class MainActivity : MauiAppCompatActivity\r\n{\r\n   protected override void OnCreate(Bundle? savedInstanceState)\r\n   {\r\n      base.OnCreate(savedInstanceState);\r\n      HandleIntent(Intent);\r\n   }\r\n\r\n   protected override void OnNewIntent(Intent? intent)\r\n   {\r\n      base.OnNewIntent(intent);\r\n      HandleIntent(intent);\r\n   }\r\n\r\n   private static void HandleIntent(Intent? intent)\r\n   {\r\n      if (intent?.Data != null)\r\n      {\r\n         var url = intent.Data.ToString();\r\n         \/\/ handle the URL as needed\r\n      }\r\n   }\r\n}<\/code><\/pre>\n<p>Compared to iOS widgets, Android widgets can directly access C# components. The <code>IServiceProvider<\/code> is also available, since <code>MauiProgram.CreateMauiApp()<\/code> is invoked just as it is in your app. Treat the widget as if it lives outside the app; keep business logic out of the <code>AppWidgetProvider<\/code>. If you want an <code>Intent<\/code> to first go through the <code>AppWidgetProvider<\/code> for widget UI updates, fire a new <code>Intent<\/code> from there and broadcast it to the app for business logic.<\/p>\n<pre><code class=\"language-csharp\">public override void OnReceive(Context? context, Intent? intent)\r\n{\r\n   if (intent == null || context == null)\r\n   {\r\n      base.OnReceive(context, intent);\r\n      return;\r\n   }\r\n\r\n   switch (intent.Action)\r\n   {\r\n      case \"com.enbyin.WidgetExample.INCREMENT_COUNTER\":\r\n      {\r\n         var currentCount = Preferences.Get(MainPage.SharedStorageAppIncomingDataKey, 0);\r\n         currentCount++;\r\n\r\n         \/\/ Send silent trigger to app for background work\r\n         var silentIntent = new Intent(context, typeof(WidgetSilentReceiver));\r\n         silentIntent.SetAction(WidgetToAppSilentIntentAction);\r\n         silentIntent.PutExtra(WidgetToAppSilentExtraValueField, currentCount);\r\n         silentIntent.SetPackage(context.PackageName);\r\n         context.SendBroadcast(silentIntent);\r\n\r\n         UpdateAllWidgets(context);\r\n         return;\r\n      }\r\n   }\r\n\r\n   base.OnReceive(context, intent);\r\n}<\/code><\/pre>\n<p>When you need to execute short actions in response to an <code>Intent<\/code>, a standard <code>BroadcastReceiver<\/code> is a great fit. Receivers can run only briefly before Android stops them. If you need more time, use a background <code>Service<\/code>.<\/p>\n<pre><code class=\"language-csharp\">[BroadcastReceiver(Exported = true)]\r\n[IntentFilter([ MyWidgetProvider.WidgetToAppSilentIntentAction ])]\r\npublic class WidgetSilentReceiver : BroadcastReceiver\r\n{\r\n   public override void OnReceive(Context? context, Intent? intent)\r\n   {\r\n      if (context == null || intent == null || intent.Action != MyWidgetProvider.WidgetToAppSilentIntentAction)\r\n      {\r\n         return;\r\n      }\r\n\r\n      var counterValue = intent.GetIntExtra(MyWidgetProvider.WidgetToAppSilentExtraValueField, int.MinValue);\r\n      if (counterValue != int.MinValue)\r\n      {\r\n         Preferences.Set(MainPage.SharedStorageAppIncomingDataKey, counterValue);\r\n      }\r\n\r\n      MainPage.RefreshWidget(); \r\n   }\r\n}<\/code><\/pre>\n<h2>Creating a configurable Widget<\/h2>\n<p>In the widget configuration file there is an option to specify an <code>Activity<\/code> as the user configuration screen for the widget. Such a widget configuration activity should be a small activity that stores its configuration changes immediately. After closing this screen the widget will automatically update once.<\/p>\n<pre><code class=\"language-xml\">&lt;!-- Resources\/xml\/mywidget_provider_info.xml --&gt;\r\n&lt;?xml version=\"1.0\" encoding=\"utf-8\" ?&gt;\r\n&lt;appwidget-provider\r\n   xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\r\n   android:widgetFeatures=\"reconfigurable\"\r\n   android:configure=\"widgetexample.WidgetConfigurationActivity\"&gt;\r\n   &lt;!-- other widget settings --&gt;\r\n&lt;\/appwidget-provider&gt;<\/code><\/pre>\n<p>The field <code>android:configure<\/code> must reference the <code>Activity<\/code> with the <code>Name<\/code> specified in the <code>Activity<\/code> itself. Pay attention to the <code>Name<\/code> value.<\/p>\n<pre><code class=\"language-csharp\">[Activity(Label = \"Configure Widget\",\r\n   Exported = true,\r\n   Name = \"widgetexample.WidgetConfigurationActivity\",\r\n   Theme = \"@android:style\/Theme.Material.Light.Dialog\",\r\n   ConfigurationChanges = ConfigChanges.UiMode)]\r\n[IntentFilter([AppWidgetManager.ActionAppwidgetConfigure])]\r\npublic class WidgetConfigurationActivity : Activity\r\n{\r\n   protected override void OnCreate(Bundle? savedInstanceState)\r\n   {\r\n      base.OnCreate(savedInstanceState);\r\n\r\n      SetResult(Result.Canceled);\r\n\r\n      var extras = Intent?.Extras;\r\n      if (extras != null)\r\n      {\r\n         _appWidgetId = extras.GetInt(AppWidgetManager.ExtraAppwidgetId, AppWidgetManager.InvalidAppwidgetId);\r\n      }\r\n\r\n      \/\/ Build the configuration View\r\n      \/\/ The views used are NOT special widget views, so you can use event handlers\r\n      \/\/ to store configuration changes\r\n      SetContentView(layout);\r\n   }\r\n}<\/code><\/pre>\n<p>The configuration activity is a standard Android <code>Activity<\/code>, which means it uses regular Android views rather than <code>RemoteView<\/code>s. You could build these screens using the full .NET MAUI framework, but for this blog I used basic XML layouts and a standard non-.NET MAUI <code>Activity<\/code>.<\/p>\n<h2>Using the Context<\/h2>\n<p>A critical concern is using the correct Android <code>Context<\/code>. It is tempting to always use the easily accessible <code>Android.App.Application.Context<\/code>, but when working with widgets this context can be null in many situations. This becomes even more important when widgets trigger background services, as using the wrong context can cause the service to crash silently. Use the context provided by Android to the background service; if that&#8217;s not available, at least check whether <code>Platform.CurrentActivity<\/code> is accessible.<\/p>\n<h2>Performance Considerations<\/h2>\n<p>Each Android app can have only one <code>Application<\/code> instance. Widgets (AppWidgetProviders) and BroadcastReceivers automatically run within the same application as the app. Using them loads the entire .NET MAUI stack, including a call to <code>MauiProgram.CreateMauiApp()<\/code>. This can cause an initial delay of a few seconds, for example when a widget button is pressed for the first time.<\/p>\n<p>You can reduce this delay by avoiding unnecessary UI-related work. A simple approach is to create a minimal version of the .NET MAUI app that initializes only the essentials needed for the widget. For example:<\/p>\n<pre><code class=\"language-csharp\">public static class MauiProgram\r\n{\r\n   public static MauiApp CreateMauiApp()\r\n   {\r\n      var builder = MauiApp.CreateBuilder();\r\n      builder\r\n         .UseMauiApp&lt;App&gt;()\r\n         .ConfigureFonts(fonts =&gt;\r\n         {\r\n            fonts.AddFont(\"OpenSans-Regular.ttf\", \"OpenSansRegular\");\r\n            fonts.AddFont(\"OpenSans-Semibold.ttf\", \"OpenSansSemibold\");\r\n         });\r\n\r\n#if DEBUG\r\n      builder.Logging.AddDebug();\r\n#endif\r\n\r\n      return builder.Build();\r\n   }\r\n\r\n   public static MauiApp CreateMinimalMauiApp()\r\n   {\r\n      var builder = MauiApp.CreateBuilder();\r\n      builder.UseMauiApp&lt;App&gt;();\r\n      return builder.Build();\r\n   }\r\n}<\/code><\/pre>\n<p>To take advantage of the minimal setup, detect whether the app is being started for a widget or for the full app. On Android, in <code>MauiApplication<\/code> this can be achieved by checking the <code>ProcessInfo<\/code> of the current running app process.<\/p>\n<pre><code class=\"language-csharp\">[Application]\r\npublic class MainApplication : MauiApplication\r\n{\r\n   public MainApplication(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership) { }\r\n\r\n   protected override MauiApp CreateMauiApp()\r\n   {\r\n      bool isBackgroundOnly = IsBackgroundExecution();\r\n      return isBackgroundOnly ? MauiProgram.CreateMinimalMauiApp() : MauiProgram.CreateMauiApp();\r\n   }\r\n\r\n   private bool IsBackgroundExecution()\r\n   {\r\n      try\r\n      {\r\n         var activityManager = (ActivityManager?)GetSystemService(ActivityService);\r\n         if (activityManager == null)\r\n         {\r\n            return false;\r\n         }\r\n\r\n         var runningAppProcesses = activityManager.RunningAppProcesses;\r\n         if (runningAppProcesses == null)\r\n         {\r\n            return false;\r\n         }\r\n\r\n         foreach (var processInfo in runningAppProcesses)\r\n         {\r\n            if (processInfo.Pid == Process.MyPid())\r\n            {\r\n               bool isBackground = (int)processInfo.Importance &gt; (int)Importance.Visible;\r\n               return isBackground;\r\n            }\r\n         }\r\n      }\r\n      catch\r\n      {\r\n         \/\/ ignore errors and assume foreground\r\n      }\r\n\r\n      return false;\r\n   }\r\n}<\/code><\/pre>\n<p>On iOS, you can follow a similar approach in the <code>AppDelegate<\/code> by checking whether the app is being launched due to a silent push notification and initialize a minimal .NET MAUI app in that case.<\/p>\n<p><div class=\"alert alert-primary\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Note<\/strong><\/p>On Android, you can run a widget in a separate process with <code>Process = \":widget_process\"<\/code> in the <code>BroadcastReceiver<\/code> attribute, but this bypasses the .NET MAUI framework, preventing access to shared Preferences and other essentials.<\/div><\/p>\n<h2>Final Thoughts<\/h2>\n<p>Android widgets offer more options and provide direct access to your C# code, unlike iOS widgets. Approach this carefully and implement a solution that works reliably across devices, especially for cross-platform scenarios. Some Android device manufacturers restrict or alter the widget experience; this blog focuses on standard Android widgets.<\/p>\n<p>Before wrapping up, here are a few final tips:<\/p>\n<ul>\n<li>Test your widget on both new Android devices and at least one device running your minimum supported version.<\/li>\n<li>Always use the Android <code>Context<\/code> provided by the <code>AppWidgetProvider<\/code> or <code>BroadcastReceiver<\/code>; services should use their own context.<\/li>\n<li>Avoid heavy UI constructs for widgets; prefer simple layouts over adapter-based views to prevent flicker.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Build interactive Android widgets with .NET MAUI using RemoteViews, intents, and shared data.<\/p>\n","protected":false},"author":201971,"featured_media":59346,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7813,7233],"tags":[7238,7246,8121,8120,8110],"class_list":["post-59345","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-dotnet-android","category-maui","tag-net-maui","tag-android","tag-appwidgetprovider","tag-remoteviews","tag-widgets"],"acf":[],"blog_post_summary":"<p>Build interactive Android widgets with .NET MAUI using RemoteViews, intents, and shared data.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/59345","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/201971"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=59345"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/59345\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/59346"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=59345"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=59345"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=59345"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}