[Android] Fix previewer exceptions with shell (#5955)
authorShane Neuville <shane94@hotmail.com>
Thu, 2 May 2019 10:26:42 +0000 (04:26 -0600)
committerRui Marinho <me@ruimarinho.net>
Thu, 2 May 2019 10:26:42 +0000 (11:26 +0100)
* shell preivewer

* nonappcompat hack

* - moove up null check

* internal IsDesignerContext

14 files changed:
Xamarin.Forms.Platform.Android/AppCompat/CarouselPageRenderer.cs
Xamarin.Forms.Platform.Android/AppCompat/MasterDetailContainer.cs
Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs
Xamarin.Forms.Platform.Android/AppCompat/Platform.cs
Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs
Xamarin.Forms.Platform.Android/ContextExtensions.cs
Xamarin.Forms.Platform.Android/Renderers/ContainerView.cs
Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs
Xamarin.Forms.Platform.Android/Renderers/ShellRenderer.cs
Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs
Xamarin.Forms.Platform.Android/Resources/Layout/FlyoutContent.axml
Xamarin.Forms.Sandbox/ShellPage.xaml [new file with mode: 0644]
Xamarin.Forms.Sandbox/ShellPage.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Sandbox/Xamarin.Forms.Sandbox.csproj

index 33b8cb0..374febe 100644 (file)
@@ -101,7 +101,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                {
                        base.OnElementChanged(e);
 
-                       var activity = (FormsAppCompatActivity)Context;
+                       var activity = (FormsAppCompatActivity)Context.GetActivity();
 
                        if (e.OldElement != null)
                                ((IPageController)e.OldElement).InternalChildren.CollectionChanged -= OnChildrenCollectionChanged;
index d279d5d..9670dc8 100644 (file)
@@ -26,7 +26,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
 
                public bool MarkedForDispose { get; internal set; } = false;
 
-               FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = ((FormsAppCompatActivity)Context).SupportFragmentManager);
+               FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = Context.GetFragmentManager());
 
                protected override void OnLayout(bool changed, int l, int t, int r, int b)
                {
index 961982d..2aa4356 100644 (file)
@@ -88,7 +88,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                        {
                                if (_platform == null)
                                {
-                                       if (Context is FormsAppCompatActivity activity)
+                                       if (Context.GetActivity() is FormsAppCompatActivity activity)
                                        {
                                                _platform = activity.Platform;
                                        }
@@ -122,7 +122,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                        }
                }
 
-               FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = ((FormsAppCompatActivity)Context).SupportFragmentManager);
+               FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = Context.GetFragmentManager());
 
                IPageController PageController => Element;
 
@@ -745,7 +745,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                void SetupToolbar()
                {
                        Context context = Context;
-                       var activity = (FormsAppCompatActivity)context;
+                       var activity = context.GetActivity();
 
                        AToolbar bar;
                        if (FormsAppCompatActivity.ToolbarResource != 0)
@@ -935,7 +935,6 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                                return;
 
                        Context context = Context;
-                       var activity = (FormsAppCompatActivity)context;
                        AToolbar bar = _toolbar;
                        ActionBarDrawerToggle toggle = _drawerToggle;
 
@@ -954,8 +953,9 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                                        toggle.SyncState();
                                }
 
-                               if (NavigationPage.GetHasBackButton(currentPage))
+                               if (NavigationPage.GetHasBackButton(currentPage) && !Context.IsDesignerContext())
                                {
+                                       var activity = (global::Android.Support.V7.App.AppCompatActivity)context.GetActivity();
                                        var icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext);
                                        icon.Progress = 1;
                                        bar.NavigationIcon = icon;
index 8ebca28..2436d9f 100644 (file)
@@ -369,8 +369,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
 
                void LayoutRootPage(Page page, int width, int height)
                {
-                       var activity = (FormsAppCompatActivity)_context;
-                       page.Layout(new Rectangle(0, 0, activity.FromPixels(width), activity.FromPixels(height)));
+                       page.Layout(new Rectangle(0, 0, _context.FromPixels(width), _context.FromPixels(height)));
                }
 
                Task PresentModal(Page modal, bool animated)
@@ -457,9 +456,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                        {
                                if (changed)
                                {
-                                       var activity = (FormsAppCompatActivity)Context;
-
-                                       _modal.Layout(new Rectangle(0, 0, activity.FromPixels(r - l), activity.FromPixels(b - t)));
+                                       _modal.Layout(new Rectangle(0, 0, Context.FromPixels(r - l), Context.FromPixels(b - t)));
                                        _backgroundView.Layout(0, 0, r - l, b - t);
                                }
 
index d844500..cbfaf01 100644 (file)
@@ -73,7 +73,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                        }
                }
 
-               FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = ((FormsAppCompatActivity)Context).SupportFragmentManager);
+               FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = Context.GetFragmentManager());
                bool IsBottomTabPlacement => (Element != null) ? Element.OnThisPlatform().GetToolbarPlacement() == ToolbarPlacement.Bottom : false;
 
                public Color BarItemColor
@@ -231,7 +231,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
                {
                        base.OnElementChanged(e);
 
-                       var activity = (FormsAppCompatActivity)Context;
+                       var activity = Context.GetActivity();
 
                        if (e.OldElement != null)
                                ((IPageController)e.OldElement).InternalChildren.CollectionChanged -= OnChildrenCollectionChanged;
index 868b74c..b34f260 100644 (file)
@@ -5,6 +5,7 @@ using Android.Util;
 using Android.Views.InputMethods;
 using AApplicationInfoFlags = Android.Content.PM.ApplicationInfoFlags;
 using AActivity = Android.App.Activity;
+using AFragmentManager = Android.Support.V4.App.FragmentManager;
 
 namespace Xamarin.Forms.Platform.Android
 {
@@ -96,5 +97,49 @@ namespace Xamarin.Forms.Platform.Android
 
                        return null;
                }
+
+               internal static Context GetThemedContext(this Context context)
+               {
+                       if (context == null)
+                               return null;
+
+                       if (context.IsDesignerContext())
+                               return context;
+
+                       if (context is global::Android.Support.V7.App.AppCompatActivity activity)
+                               return activity.SupportActionBar.ThemedContext;
+
+                       if (context is ContextWrapper contextWrapper)
+                               return contextWrapper.BaseContext.GetThemedContext();
+
+                       return null;
+               }
+
+               internal static bool IsDesignerContext(this Context context)
+               {
+                       if (context == null)
+                               return false;
+                       
+                       if ($"{context.ToString()}".Contains("com.android.layoutlib.bridge.android.BridgeContext"))
+                               return true;
+
+                       if (context is ContextWrapper contextWrapper)
+                               return contextWrapper.BaseContext.IsDesignerContext();
+
+                       return false;
+               }
+
+               public static AFragmentManager GetFragmentManager(this Context context)
+               {
+                       if (context == null)
+                               return null;
+
+                       var activity = context.GetActivity();
+
+                       if (activity is global::Android.Support.V4.App.FragmentActivity fa)
+                               return fa.SupportFragmentManager;
+
+                       return null;
+               }
        }
 }
index 73b5492..3ef2c39 100644 (file)
@@ -50,7 +50,7 @@ namespace Xamarin.Forms.Platform.Android
 
                        if (disposing)
                        {
-                               _renderer.Dispose();
+                               _renderer?.Dispose();
                                _renderer = null;
                                _view = null;
                                _context = null;
index 63581a5..5ab3e2a 100644 (file)
@@ -38,13 +38,48 @@ namespace Xamarin.Forms.Platform.Android
         protected virtual void LoadView(IShellContext shellContext)
         {
             var context = shellContext.AndroidContext;
-            var coordinator = LayoutInflater.FromContext(context).Inflate(Resource.Layout.FlyoutContent, null);
-            var recycler = coordinator.FindViewById<RecyclerView>(Resource.Id.flyoutcontent_recycler);
-            var appBar = coordinator.FindViewById<AppBarLayout>(Resource.Id.flyoutcontent_appbar);
+            var coordinator = (ViewGroup)LayoutInflater.FromContext(context).Inflate(Resource.Layout.FlyoutContent, null);
+                       var recycler = coordinator.FindViewById<RecyclerView>(Resource.Id.flyoutcontent_recycler);
+                       var appBar = coordinator.FindViewById<ViewGroup>(Resource.Id.flyoutcontent_appbar);
 
-            _rootView = coordinator;
+                       _rootView = coordinator;
 
-            appBar.AddOnOffsetChangedListener(this);
+                       if((recycler == null || appBar == null) && !context.IsDesignerContext())
+                       {
+                               if (recycler == null)
+                                       throw new ArgumentNullException("flyoutcontent_recycler", "Unable to find layout for flyoutcontent_recycler");
+
+                               // PREVIEWER HACK for some reason previewer pulls this out as a FrameLayout and ignores the internal resources
+                               if (appBar == null)
+                                       throw new ArgumentNullException("flyoutcontent_appbar", "Unable to find layout for flyoutcontent_recycler");
+
+                       }
+
+
+                       try
+                       {
+                               // PREVIEWER HACK for some reason previewer can't find the resources for the recycler or the appBar
+                               if (recycler == null)
+                                       recycler = (RecyclerView)coordinator.GetChildAt(1);
+                               if (appBar == null)
+                                       appBar = (AppBarLayout)coordinator.GetChildAt(0);
+                       }
+                       catch
+                       {
+                               // PReviewer hack.
+                               // appcompat and non appcompat initialize this whole thing differently so the above are for appcompat the below are for non
+                       }
+
+                       // PREVIEWER HACK for some reason previewer can't find the resources for the recycler or the appBar
+                       if (recycler == null)
+                               recycler = coordinator.FindViewById<RecyclerView>(context.Resources.GetIdentifier("flyoutcontent_recycler", "id", context.PackageName));
+
+                       // PREVIEWER HACK for some reason previewer pulls this out as a FrameLayout and ignores the internal resources
+                       if (appBar == null)
+                               appBar = coordinator.FindViewById<ViewGroup>(context.Resources.GetIdentifier("flyoutcontent_appbar", "id", context.PackageName));
+
+
+                       (appBar as AppBarLayout)?.AddOnOffsetChangedListener(this);
 
             int actionBarHeight = (int)context.ToPixels(56);
 
@@ -68,11 +103,14 @@ namespace Xamarin.Forms.Platform.Android
             var metrics = context.Resources.DisplayMetrics;
             var width = Math.Min(metrics.WidthPixels, metrics.HeightPixels);
 
-            TypedValue tv = new TypedValue();
-            if (context.Theme.ResolveAttribute(global::Android.Resource.Attribute.ActionBarSize, tv, true))
-            {
-                actionBarHeight = TypedValue.ComplexToDimensionPixelSize(tv.Data, metrics);
-            }
+                       using (TypedValue tv = new TypedValue())
+                       {
+                               if (context.Theme.ResolveAttribute(global::Android.Resource.Attribute.ActionBarSize, tv, true))
+                               {
+                                       actionBarHeight = TypedValue.ComplexToDimensionPixelSize(tv.Data, metrics);
+                               }
+                       }
+
             width -= actionBarHeight;
 
             coordinator.LayoutParameters = new LP(width, LP.MatchParent);
index f6d8263..030ac25 100644 (file)
@@ -36,7 +36,10 @@ namespace Xamarin.Forms.Platform.Android
                VisualElementTracker IVisualElementRenderer.Tracker => null;
 
                AView IVisualElementRenderer.View => _flyoutRenderer.AndroidView;
-               ViewGroup IVisualElementRenderer.ViewGroup => _flyoutRenderer.AndroidView as ViewGroup;
+
+               // Used by Previewer
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public ViewGroup ViewGroup => _flyoutRenderer.AndroidView as ViewGroup;
 
                SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
                {
@@ -59,7 +62,9 @@ namespace Xamarin.Forms.Platform.Android
                {
                }
 
-               void IVisualElementRenderer.UpdateLayout()
+               // Used by Previewer
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public void UpdateLayout()
                {
                        var width = (int)AndroidContext.ToPixels(Element.Width);
                        var height = (int)AndroidContext.ToPixels(Element.Height);
@@ -151,7 +156,7 @@ namespace Xamarin.Forms.Platform.Android
 
                protected Context AndroidContext { get; }
                protected Shell Element { get; private set; }
-               private FragmentManager FragmentManager => ((FormsAppCompatActivity)AndroidContext).SupportFragmentManager;
+               FragmentManager FragmentManager => AndroidContext.GetFragmentManager();
 
                protected virtual IShellObservableFragment CreateFragmentForPage(Page page)
                {
@@ -202,9 +207,7 @@ namespace Xamarin.Forms.Platform.Android
                protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
                {
                        if (e.PropertyName == Shell.CurrentItemProperty.PropertyName)
-                       {
                                SwitchFragment(FragmentManager, _frameLayout, Element.CurrentItem);
-                       }
 
                        _elementPropertyChanged?.Invoke(sender, e);
                }
@@ -223,13 +226,18 @@ namespace Xamarin.Forms.Platform.Android
 
                        ((IShellController)shell).AddAppearanceObserver(this, shell);
 
-                       SwitchFragment(FragmentManager, _frameLayout, shell.CurrentItem, false);
+                       // Previewer Hack
+                       if(AndroidContext.GetActivity() != null)
+                               SwitchFragment(FragmentManager, _frameLayout, shell.CurrentItem, false);
                }
 
                IShellItemRenderer _currentRenderer;
 
                protected virtual void SwitchFragment(FragmentManager manager, AView targetView, ShellItem newItem, bool animate = true)
                {
+                       if (AndroidContext.IsDesignerContext())
+                               return; 
+
                        var previousRenderer = _currentRenderer;
                        _currentRenderer = CreateShellItemRenderer(newItem);
                        _currentRenderer.ShellItem = newItem;
@@ -266,10 +274,10 @@ namespace Xamarin.Forms.Platform.Android
 
                void UpdateStatusBarColor(ShellAppearance appearance)
                {
-                       var activity = ((FormsAppCompatActivity)AndroidContext);
-                       var window = activity.Window;
-                       var decorView = window.DecorView;
-                       var resources = activity.Resources;
+                       var activity = AndroidContext.GetActivity();
+                       var window = activity?.Window;
+                       var decorView = window?.DecorView;
+                       var resources = AndroidContext.Resources;
 
                        int statusBarHeight = 0;
                        int resourceId = resources.GetIdentifier("status_bar_height", "dimen", "android");
@@ -285,19 +293,23 @@ namespace Xamarin.Forms.Platform.Android
                                navigationBarHeight = resources.GetDimensionPixelSize(resourceId);
                        }
 
-                       // we are using the split drawable here to avoid GPU overdraw.
-                       // All it really is is a drawable that only draws under the statusbar/bottom bar to make sure
-                       // we dont draw over areas we dont need to. This has very limited benefits considering its
-                       // only saving us a flat color fill BUT it helps people not freak out about overdraw.
-                       if (appearance != null)
-                       {
-                               var color = appearance.BackgroundColor.ToAndroid(Color.FromHex("#03A9F4"));
-                               decorView.SetBackground(new SplitDrawable(color, statusBarHeight, navigationBarHeight));
-                       }
-                       else
+                       // TODO Previewer Hack
+                       if (decorView != null)
                        {
-                               var color = Color.FromHex("#03A9F4").ToAndroid();
-                               decorView.SetBackground(new SplitDrawable(color, statusBarHeight, navigationBarHeight));
+                               // we are using the split drawable here to avoid GPU overdraw.
+                               // All it really is is a drawable that only draws under the statusbar/bottom bar to make sure
+                               // we dont draw over areas we dont need to. This has very limited benefits considering its
+                               // only saving us a flat color fill BUT it helps people not freak out about overdraw.
+                               if (appearance != null)
+                               {
+                                       var color = appearance.BackgroundColor.ToAndroid(Color.FromHex("#03A9F4"));
+                                       decorView.SetBackground(new SplitDrawable(color, statusBarHeight, navigationBarHeight));
+                               }
+                               else
+                               {
+                                       var color = Color.FromHex("#03A9F4").ToAndroid();
+                                       decorView.SetBackground(new SplitDrawable(color, statusBarHeight, navigationBarHeight));
+                               }
                        }
                }
 
index 42fe361..482eb01 100644 (file)
@@ -236,23 +236,21 @@ namespace Xamarin.Forms.Platform.Android
                        var backButtonHandler = Shell.GetBackButtonBehavior(page);
                        toolbar.SetNavigationOnClickListener(this);
 
-                       var activity = (FormsAppCompatActivity)context;
-
                        if (backButtonHandler != null)
                        {
-                               await UpdateDrawerArrowFromBackButtonBehavior(context, toolbar, drawerLayout, backButtonHandler, activity);
+                               await UpdateDrawerArrowFromBackButtonBehavior(context, toolbar, drawerLayout, backButtonHandler);
                        }
                        else
                        {
-                               await UpdateDrawerArrow(context, toolbar, drawerLayout, activity);
+                               await UpdateDrawerArrow(context, toolbar, drawerLayout);
                        }
                }
 
-               protected virtual async Task UpdateDrawerArrow(Context context, Toolbar toolbar, DrawerLayout drawerLayout, FormsAppCompatActivity activity)
+               protected virtual async Task UpdateDrawerArrow(Context context, Toolbar toolbar, DrawerLayout drawerLayout)
                {
-                       if (_drawerToggle == null)
+                       if (_drawerToggle == null && !context.IsDesignerContext())
                        {
-                               _drawerToggle = new ActionBarDrawerToggle((Activity)context, drawerLayout, toolbar,
+                               _drawerToggle = new ActionBarDrawerToggle(context.GetActivity(), drawerLayout, toolbar,
                                        R.String.Ok, R.String.Ok)
                                {
                                        ToolbarNavigationClickListener = this,
@@ -267,7 +265,7 @@ namespace Xamarin.Forms.Platform.Android
                        if (CanNavigateBack)
                        {
                                _drawerToggle.DrawerIndicatorEnabled = false;
-                               using (var icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext))
+                               using (var icon = new DrawerArrowDrawable(context.GetThemedContext()))
                                {
                                        icon.SetColorFilter(TintColor.ToAndroid(Color.White), PorterDuff.Mode.SrcAtop);
                                        icon.Progress = 1;
@@ -288,7 +286,7 @@ namespace Xamarin.Forms.Platform.Android
                        _drawerToggle.SyncState();
                }
 
-               protected virtual async Task UpdateDrawerArrowFromBackButtonBehavior(Context context, Toolbar toolbar, DrawerLayout drawerLayout, BackButtonBehavior backButtonHandler, FormsAppCompatActivity activity)
+               protected virtual async Task UpdateDrawerArrowFromBackButtonBehavior(Context context, Toolbar toolbar, DrawerLayout drawerLayout, BackButtonBehavior backButtonHandler)
                {
                        var behavior = backButtonHandler;
                        var command = behavior.Command;
@@ -307,7 +305,7 @@ namespace Xamarin.Forms.Platform.Android
 
                        if (CanNavigateBack && icon == null)
                        {
-                               icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext);
+                               icon = new DrawerArrowDrawable(context.GetThemedContext());
                                (icon as DrawerArrowDrawable).Progress = 1;
                        }
 
index 2a3b386..a6f4f5a 100644 (file)
@@ -9,13 +9,13 @@
                >
 
        <android.support.design.widget.AppBarLayout
-                       android:id="@+id/flyoutcontent.appbar"
+                       android:id="@+id/flyoutcontent_appbar"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
 
        <android.support.v7.widget.RecyclerView
-               android:id="@+id/flyoutcontent.recycler"
+               android:id="@+id/flyoutcontent_recycler"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior" />
diff --git a/Xamarin.Forms.Sandbox/ShellPage.xaml b/Xamarin.Forms.Sandbox/ShellPage.xaml
new file mode 100644 (file)
index 0000000..e0cb676
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<Shell xmlns="http://xamarin.com/schemas/2014/forms"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             x:Class="Xamarin.Forms.Sandbox.ShellPage">
+
+    <ShellContent Title="test">
+        <ContentPage></ContentPage>
+    </ShellContent>
+</Shell>
\ No newline at end of file
diff --git a/Xamarin.Forms.Sandbox/ShellPage.xaml.cs b/Xamarin.Forms.Sandbox/ShellPage.xaml.cs
new file mode 100644 (file)
index 0000000..6979ea5
--- /dev/null
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms.Sandbox
+{
+       [XamlCompilation(XamlCompilationOptions.Compile)]
+       public partial class ShellPage : Shell
+       {
+               public ShellPage()
+               {
+                       InitializeComponent();
+               }
+       }
+}
\ No newline at end of file
index dca9fb7..cbae474 100644 (file)
     <DebugType>pdbonly</DebugType>
     <DebugSymbols>true</DebugSymbols>
   </PropertyGroup>
+
+  <ItemGroup>
+    <None Include="ShellPage.xaml">
+      <Generator>MSBuild:Compile</Generator>
+    </None>
+  </ItemGroup>
   
   <ItemGroup>
     <!-- A reference to the entire .NET Framework is automatically included -->