[net11.0][Android] Implement handler based Shell architecture replacing legacy renderers#34758
[net11.0][Android] Implement handler based Shell architecture replacing legacy renderers#34758Tamilarasan-Paranthaman wants to merge 49 commits into
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34758Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34758" |
There was a problem hiding this comment.
Pull request overview
Implements a new handler-based Shell architecture for Android (opt-in via UseAndroidShellHandlers) and refactors shared Android navigation/tab/flyout infrastructure to reduce duplication with existing MAUI handlers/managers.
Changes:
- Added
RuntimeFeature.UseAndroidShellHandlers(defaultfalse) and MSBuild plumbing to enable the feature switch. - Introduced shared Android components/layouts (e.g.,
MauiDrawerLayout,TabbedViewManager,shellitemlayout.axml,shellsectionlayout.axml) and refactoredFlyoutViewHandler/TabbedPageManagerto use them. - Updated Shell compatibility components and test projects to validate the new Android Shell handlers in CI (plus a UI test stabilization tweak).
Reviewed changes
Copilot reviewed 40 out of 40 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Core/src/RuntimeFeature.cs | Adds UseAndroidShellHandlers runtime feature switch. |
| src/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt | Public API updates for new tab abstractions/enums. |
| src/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt | Public API updates for new tab abstractions/enums. |
| src/Core/src/PublicAPI/net/PublicAPI.Unshipped.txt | Public API updates for new tab abstractions/enums. |
| src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt | Public API updates for new tab abstractions/enums. |
| src/Core/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt | Public API updates for new tab abstractions/enums. |
| src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt | Public API updates for new tab abstractions/enums. |
| src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt | Public API updates for new tab abstractions/enums. |
| src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt | Public API updates including MauiDrawerLayout and handler signature changes. |
| src/Core/src/Primitives/TabBarPlacement.cs | Introduces TabBarPlacement enum. |
| src/Core/src/Platform/Android/Resources/values/styles.xml | Adds Android style for Shell TabLayout. |
| src/Core/src/Platform/Android/Resources/Layout/shellsectionlayout.axml | New Android layout used by ShellSection handler. |
| src/Core/src/Platform/Android/Resources/Layout/shellitemlayout.axml | New Android layout used by ShellItem handler. |
| src/Core/src/Platform/Android/Navigation/StackNavigationManager.cs | Adds navigation request queueing and Shell integration for per-tab containers. |
| src/Core/src/Platform/Android/MauiDrawerLayout.cs | New shared DrawerLayout wrapper used by FlyoutView/Shell. |
| src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs | Refactors FlyoutViewHandler to use MauiDrawerLayout. |
| src/Core/src/Core/ITabbedView.cs | Expands ITabbedView to support shared tab management surface. |
| src/Core/src/Core/ITab.cs | Adds ITab abstraction for tab items. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/XFIssue/ShellSearchHandlerItemSizing.cs | Stabilizes screenshot capture via retry/tolerance. |
| src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj | Enables UseAndroidShellHandlers for UI test host app. |
| src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj | Enables UseAndroidShellHandlers for device tests. |
| src/Controls/src/Core/TabbedPage/TabbedPage.cs | Implements expanded ITabbedView surface on TabbedPage. |
| src/Controls/src/Core/Shell/ShellContent.cs | Propagates title updates to support handler-based tab title refresh. |
| src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt | Adds public handler types/methods for Android Shell handlers. |
| src/Controls/src/Core/Platform/Android/TabbedViewManager.cs | New shared manager for ViewPager2 + BottomNavigationView/TabLayout behavior. |
| src/Controls/src/Core/Platform/Android/TabbedPageManager.cs | Refactors TabbedPageManager to delegate tab UI logic to TabbedViewManager. |
| src/Controls/src/Core/Platform/Android/ITabbedViewSource.cs | Adds internal adapter interface to supply tab data without IView. |
| src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs | Conditionally registers Android Shell handlers based on runtime feature. |
| src/Controls/src/Core/Handlers/Shell/ShellTabbedViewAdapters.Android.cs | Adds Shell adapters bridging ShellItem/ShellSection to tab source model. |
| src/Controls/src/Core/Handlers/Shell/ShellHandler.Tizen.cs | Adds stub mappers to satisfy shared mapper entries. |
| src/Controls/src/Core/Handlers/Shell/ShellHandler.cs | Extends property mapper for Android/Tizen/Windows handler scenarios. |
| src/Controls/src/Core/Handlers/Shell/ShellHandler.Android.cs | New Android ShellHandler implementation built on MauiDrawerLayout. |
| src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs | Compatibility updates for toolbar/search behavior and back icon progress handling. |
| src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarAppearanceTracker.cs | Adds null-guard in SetAppearance. |
| src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellSearchViewAdapter.cs | Adds JNI ctor + null guard for filter publish. |
| src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutTemplatedContentRenderer.cs | Avoids double-updates when running under new handler path; exposes update methods. |
| src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRecyclerAdapter.cs | Adds additional null/dispose safety. |
| src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets | Maps MSBuild property to runtime feature switch. |
b6433e8 to
0c8fa75
Compare
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
99080f6 to
603ddbc
Compare
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
c7bc2c4 to
d695579
Compare
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
3b5baeb to
7265fdf
Compare
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
92f63a0 to
c6326f5
Compare
|
Verdict: NEEDS_CHANGES (confidence: high) Re-review at HEAD Consensus findings (synthesized, 2 inline):
Dropped (1): gemini's Multi-model review (gpt-5.5 · opus-4.8 · opus-4.6 · gemini-3.1-pro). Comments only — not a formal approval. |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description
This PR introduces handler-based Shell architecture for Android, replacing the legacy renderer-based approach (
ShellRenderer,ShellFlyoutRenderer,ShellItemRenderer,ShellSectionRenderer) with new handler classes that reuse standard MAUI platform components. The new architecture follows the same patterns used byFlyoutViewHandler,NavigationViewHandler, andTabbedPageManager, achieving the long-standing architectural goal of building Shell from the same building blocks as non-Shell features.Platform: Android only. iOS/MacCatalyst continue to use legacy
ShellRenderer.Motivation
The legacy Shell renderers on Android are monolithic, tightly coupled, and duplicate functionality that already exists in standard MAUI handlers:
ShellFlyoutRendererextendsDrawerLayoutdirectly (IS-A relationship) — duplicatesFlyoutViewHandlerdrawer logicShellItemRenderermanages its ownBottomNavigationView— duplicatesTabbedPageManagertab logicShellSectionRenderermanages its own toolbar, navigation stack, and content tabs — duplicatesNavigationViewHandlerandTabbedPageManagerpatternsThe new handler architecture:
MauiDrawerLayout— same shared component asFlyoutViewHandlerTabbedViewManager— shared tab management for both Shell andTabbedPagenavigationlayout.axml,shellitemlayout.axml,shellsectionlayout.axml)Shell → ShellHandler,ShellItem → ShellItemHandler,ShellSection → ShellSectionHandlerArchitecture Overview
Handler Hierarchy
Key Design Decisions
Customization Surface
The handler architecture preserves the full customization surface from the legacy renderer path. All factory methods, virtual hooks, and shared classes are accessible to developers.
Protected Virtual Methods (New Handler Hooks)
ShellItemHandlerOnTabReselected(ShellSection)ShellItemHandlerOnSectionChanged(ShellSection, bool)ShellItemHandlerCreateMoreBottomSheet(Action<int, BottomSheetDialog>, List<...>)ShellSectionHandlerOnCreateNavigationAnimation(Context, bool, bool)ShellStackNavigationManager→StackNavigationManager.OnCreateNavigationAnimation()(base ispublic virtual, also benefitsNavigationPage)ShellHandlerIShellContextfactory methodsprotected virtualmethods:CreateTrackerForToolbar(),CreateTabLayoutAppearanceTracker(),CreateBottomNavViewAppearanceTracker(),CreateToolbarAppearanceTracker(),CreateShellFlyoutContentRenderer(),CreateShellFlyoutRenderer(),CreateFragmentForPage(),CreateShellItemTransition()Shared Classes (Not Internalized)
ShellToolbarTrackerpublic class— used by both renderer and handler pathsprotected virtualmethods (navigation icons, search, toolbar items, back button)ShellFlyoutRecyclerAdapterGenerateItemList()—protected virtualIShellContextfactoriesPublic Fragment Classes
All wrapper fragments are top-level
publicclasses in their own files, following the one-class-per-file convention used by the legacy Shell classes. Developers can subclass them to overrideOnCreateView,OnResume,OnViewCreated, etc.ShellItemWrapperFragmentShellItemWrapperFragment.Android.csShellSectionWrapperFragmentShellSectionWrapperFragment.Android.csShellContentNavigationFragmentShellContentNavigationFragment.Android.csFragment Architecture
Shell handlers use wrapper fragments to integrate with Android's
FragmentManager:All fragment classes are public and in their own files. All have default constructors for Android's
Fragment.instantiate()reflection requirement, with null guards inOnCreateView()for graceful restoration handling.New Files
Core Layer (
src/Core/)Controls Layer (
src/Controls/)protected virtualfactory methods.protected virtualhooks:OnTabReselected,OnSectionChanged,CreateMoreBottomSheet.protected virtualhook:OnCreateNavigationAnimation.Modified Files
Handler Registration
Rollout Infrastructure
Shared Component Extraction
public virtual OnCreateNavigationAnimation()for custom page transitions.Core Interfaces
Compatibility Layer Fixes
protected virtualmethods preserved.Virtual View Changes
PublicAPI
Updated
PublicAPI.Unshipped.txtfiles across all TFMs for new public types and interface members:MauiDrawerLayout,MauiDrawerLayout.FlyoutLayoutModeITab,ITabbedViewexpanded membersTabBarPlacementenumShellHandler,ShellItemHandler,ShellSectionHandlerhandler typesShellItemWrapperFragment,ShellSectionWrapperFragment,ShellContentNavigationFragmentfragment classesOnTabReselected,OnSectionChanged,CreateMoreBottomSheet,OnCreateNavigationAnimationShared Infrastructure Summary
The PR achieves the core architectural goal — Shell now uses the same building blocks as non-Shell features:
Default-On With Opt-Out Mechanism
The Shell on Android now supports both the legacy renderer-based approach and the new handler-based approach, controlled by the
UseAndroidShellHandlersMSBuild property (following the same pattern asUseMaterial3). Handler mode is now the default; settingUseAndroidShellHandlers=falsekeeps the legacy renderer path.How to Opt Out (Stay On Renderer Path)
Or via command line:
dotnet build -p:UseAndroidShellHandlers=falseFlow
UseAndroidShellHandlersMSBuild property → build targets →AppHostBuilderExtensionsconditionally registers handlers or legacyShellRenderer.What Changes By Default
Testing
Controls.DeviceTests.csprojrelies on the runtime default (handler path).Controls.TestCases.HostApp.csprojrelies on the runtime default (handler path).UseAndroidShellHandlers=falseat pipeline/build invocation time.Migration Guidance For Existing Custom Shell Renderers
This section is specifically for apps that already subclass legacy Android Shell renderers and want to enable
UseAndroidShellHandlers=true.Old -> New Mapping
ShellRenderersubclassShellHandlersubclassShellItemRenderersubclassShellItemHandlersubclassShellSectionRenderersubclassShellSectionHandlersubclassCreateTrackerForToolbar()ShellHandler.CreateTrackerForToolbar()(protected virtual)CreateBottomNavViewAppearanceTracker()ShellHandler.CreateBottomNavViewAppearanceTracker()(protected virtual)CreateTabLayoutAppearanceTracker()ShellHandler.CreateTabLayoutAppearanceTracker()(protected virtual)CreateToolbarAppearanceTracker()ShellHandler.CreateToolbarAppearanceTracker()(protected virtual)CreateShellItemRenderer()ShellHandler.CreateShellItemRenderer()(protected virtual)CreateShellSectionRenderer()ShellHandler.CreateShellSectionRenderer()(protected virtual)CreateFragmentForPage()ShellHandler.CreateFragmentForPage()(protected virtual)ShellItemRenderer.OnTabReselected()ShellItemHandler.OnTabReselected()(protected virtual)ShellItemRenderer"More" overflow customizationShellItemHandler.CreateMoreBottomSheet()(protected virtual)Adapter Wrapping Pattern (Important)
In the handler architecture,
CreateShellItemRenderer()/CreateShellSectionRenderer()still return compatibility interfaces, but runtime behavior is bridged through handler adapters.Animation API Translation
SetupAnimation(ShellNavigationSource, FragmentTransaction, Page)OnCreateNavigationAnimation(Context, bool isPopping, bool enter)What changed:
Android.Views.Animations.Animation?instead of mutatingFragmentTransaction.FragmentTransactioncustomization is not exposed on this hook.ShellNavigationSourcedoes not map 1:1; push/pop intent maps toisPopping+enter.Issues Fixed
Fixes #32985