Skip to content

Commit

Permalink
- fix up fragment management
Browse files Browse the repository at this point in the history
  • Loading branch information
PureWeen committed Dec 13, 2022
1 parent 1418fdc commit 39277d8
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ protected override void Dispose(bool disposing)

if (disposing)
{
if (_currentFragment != null && !FragmentManager.IsDestroyed())
if (_currentFragment != null && !FragmentManager.IsDestroyed(Context))
{
FragmentTransaction transaction = FragmentManager.BeginTransactionEx();
transaction.RemoveEx(_currentFragment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ protected override void Dispose(bool disposing)

_page = null;

if (!_fragmentManager.IsDestroyed())
if (!_fragmentManager.IsDestroyed(_page?.Handler?.MauiContext?.Context))
{
FragmentTransaction transaction = _fragmentManager.BeginTransactionEx();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ public ShellFragmentContainer(ShellContent shellContent, IMauiContext mauiContex
public override AView OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
_page = ((IShellContentController)ShellContentTab).GetOrCreateContent();

var platformView =
_page.ToPlatform(_mauiContext, RequireContext(), inflater, ChildFragmentManager);
_page.ToPlatform(_mauiContext, RequireContext(), inflater, ChildFragmentManager);

return new ShellPageContainer(RequireContext(), (IPlatformViewHandler)_page.Handler, true)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ async Task SendHandlerUpdateAsync(
// This task completes once the handler calls INavigationView.Finished
await currentNavRequestTaskSource.Task;
}
catch
{
throw;
}
finally
{
if (Interlocked.Decrement(ref _waitingCount) == 0)
Expand Down
14 changes: 8 additions & 6 deletions src/Controls/src/Core/Platform/Android/FragmentContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@ internal class FragmentContainer : Fragment
AView _pageContainer;
ViewGroup _parent;

public FragmentContainer(IMauiContext mauiContext)
{
_mauiContext = mauiContext;
}

public FragmentContainer(Page page, IMauiContext mauiContext) : this(mauiContext)
public FragmentContainer(Page page, IMauiContext mauiContext)
{
_pageRenderer = new WeakReference(page);
_mauiContext = mauiContext;
Expand Down Expand Up @@ -57,6 +52,13 @@ public override AView OnCreateView(LayoutInflater inflater, ViewGroup container,
return null;
}

public override void OnDestroy()
{
base.OnDestroy();
if (Context.IsDestroyed())
Page?.Handler?.DisconnectHandler();
}

public override void OnResume()
{
if (_pageContainer == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using AndroidX.Fragment.App;
using AndroidX.ViewPager2.Adapter;
using AView = Android.Views.View;

namespace Microsoft.Maui.Controls.Platform
{
internal class MultiPageFragmentStateAdapter<[DynamicallyAccessedMembers(BindableProperty.DeclaringTypeMembers)] T> : FragmentStateAdapter where T : Page
{
MultiPage<T> _page;
readonly IMauiContext _context;
List<ItemKey> keys = new List<ItemKey>();

public MultiPageFragmentStateAdapter(
MultiPage<T> page, FragmentManager fragmentManager, IMauiContext context)
Expand All @@ -29,20 +36,125 @@ public override Fragment CreateFragment(int position)

public override long GetItemId(int position)
{
// https://github.com/dotnet/maui/issues/11529
// This should be updated to not use `GetHashCode`
return _page.Children[position].GetHashCode();
return GetItemIdByPosition(position).ItemId;
}

public override bool ContainsItem(long itemId)
{
foreach (var item in _page.Children)
return GetItemByItemId(itemId) != null;
}

ItemKey GetItemIdByPosition(int position)
{
CheckItemKeys();
var page = _page.Children[position];
for (var i = 0; i < keys.Count; i++)
{
ItemKey item = keys[i];
if (item.Page == page)
{
return item;
}
}

ItemKey itemKey = new ItemKey(page, (ik) => keys.Remove(ik));
keys.Add(itemKey);

return itemKey;
}

ItemKey? GetItemByItemId(long itemId)
{
CheckItemKeys();
for (var i = 0; i < keys.Count; i++)
{
ItemKey item = keys[i];
if (item.ItemId == itemId)
{
return item;
}
}

return null;
}

void CheckItemKeys()
{
for (var i = 0; i < keys.Count; i++)
{
ItemKey item = keys[i];

if (!_page.Children.Contains(item.Page))
{
// Disconnect will remove the ItemKey from the keys list
item.Disconnect();
}
}
}

class ItemKey
{
Page? _page;
Action<ItemKey>? _markInvalid;
bool _stableView;
object? _platformView;

public ItemKey(Page page, Action<ItemKey> markInvalid)
{
_page = page;
_markInvalid = markInvalid;
_page.HandlerChanging += OnHandlerChanging;
_page.HandlerChanged += OnHandlerChanged;
ItemId = AView.GenerateViewId();
}

public bool Disconnected => _page != null;
public Page? Page => _page;
public long ItemId { get; }
public void Disconnect()
{
if (item.GetHashCode() == itemId)
return true;
if (_page == null)
return;

_markInvalid?.Invoke(this);

if (_page != null)
{
_page.HandlerChanging -= OnHandlerChanging;
_page.HandlerChanged -= OnHandlerChanged;
_page = null;
}

_platformView = null;
}

public void SetToStableView()
{
_stableView = true;
}

void OnHandlerChanging(object? sender, HandlerChangingEventArgs e)
{
if (_stableView && _platformView != null)
Disconnect();
}

void OnHandlerChanged(object? sender, EventArgs e)
{
if (_page == null || _platformView != null)
{
if (sender is Page page)
page.HandlerChanged -= OnHandlerChanged;

return;
}

_platformView = _page.Handler?.PlatformView;

if (_platformView != null)
_page.HandlerChanged -= OnHandlerChanged;
}

return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ void RemoveTabs()
.GetNavigationRootManager()
.FragmentManager;

if (!fragmentManager.IsDestroyed())
if (!fragmentManager.IsDestroyed(_context?.Context))
{
_ = _context
.GetNavigationRootManager()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ protected override View CreatePlatformView()
return view;
}


void OnViewChildAdded(object? sender, ViewGroup.ChildViewAddedEventArgs e)
{
_stackNavigationManager?.CheckForFragmentChange();
}

StackNavigationManager CreateNavigationManager()
{
_ = MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class.");
Expand All @@ -35,6 +41,9 @@ protected override void ConnectHandler(View platformView)

platformView.ViewAttachedToWindow += OnViewAttachedToWindow;
platformView.ViewDetachedFromWindow += OnViewDetachedFromWindow;

if (platformView is FragmentContainerView fcw)
fcw.ChildViewAdded += OnViewChildAdded;
}

void OnViewDetachedFromWindow(object? sender, View.ViewDetachedFromWindowEventArgs e)
Expand All @@ -61,6 +70,9 @@ private protected override void OnDisconnectHandler(View platformView)
platformView.ViewDetachedFromWindow -= OnViewDetachedFromWindow;
platformView.LayoutChange -= OnLayoutChanged;

if (platformView is FragmentContainerView fcw)
fcw.ChildViewAdded -= OnViewChildAdded;

_stackNavigationManager?.Disconnect();
base.OnDisconnectHandler(platformView);
}
Expand Down
31 changes: 31 additions & 0 deletions src/Core/src/Platform/Android/ContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -419,5 +419,36 @@ public static Rect ToCrossPlatformRectInReferenceFrame(this Context context, int
return Rect.FromLTRB(0, 0,
deviceIndependentRight - deviceIndependentLeft, deviceIndependentBottom - deviceIndependentTop);
}

internal static bool IsDestroyed(this Context? context)
{
if (context == null)
return true;

if (context.GetActivity() is FragmentActivity fa)
{
if (fa.IsDisposed())
return true;

var stateCheck = AndroidX.Lifecycle.Lifecycle.State.Destroyed;

if (stateCheck != null &&
fa.Lifecycle.CurrentState == stateCheck)
{
return true;
}

if (fa.IsDestroyed)
return true;
}

return context.IsDisposed();
}

internal static bool IsPlatformContextDestroyed(this IElementHandler? handler)
{
var context = handler?.MauiContext?.Context;
return context.IsDestroyed();
}
}
}
9 changes: 8 additions & 1 deletion src/Core/src/Platform/Android/FragmentManagerExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

using System;
using Android.Content;
using AndroidX.Fragment.App;

namespace Microsoft.Maui.Platform
Expand Down Expand Up @@ -57,14 +58,20 @@ public static FragmentTransaction BeginTransactionEx(this FragmentManager fragme
return fragmentManager.BeginTransaction();
}

public static bool IsDestroyed(this FragmentManager? obj)
public static bool IsDestroyed(this FragmentManager? obj, Context? context)
{
if (obj == null)
return true;

if (context == null)
return true;

if (obj.IsDestroyed)
return true;

if (context.IsDestroyed())
return true;

return obj.IsDisposed();
}

Expand Down
10 changes: 6 additions & 4 deletions src/Core/src/Platform/Android/MauiContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,16 @@ internal static View ToPlatform(
{
if (view.Handler?.MauiContext is MauiContext scopedMauiContext)
{
// If the incoming mauiContext has the same activity and fragment manager
// then we just update the layourInflater for any children that will want to participate
// with this inflation flow
// If this handler becomes to a different activity then we need to
// recreate the view.
// If it's the same activity we just update the layout inflater
// and the fragment manager so that the platform view doesn't recreate
// underneath the users feet
if (scopedMauiContext.GetActivity() == context.GetActivity() &&
scopedMauiContext.GetFragmentManager() == childFragmentManager &&
view.Handler.PlatformView is View platformView)
{
scopedMauiContext.AddWeakSpecific(layoutInflater);
scopedMauiContext.AddWeakSpecific(childFragmentManager);
return platformView;
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/Core/src/Platform/Android/Navigation/MauiNavHostFragment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.Navigation;
using AndroidX.Navigation.Fragment;

namespace Microsoft.Maui.Platform
{
[Register("microsoft.maui.platform.MauiNavHostFragment")]
class MauiNavHostFragment : NavHostFragment
{
public StackNavigationManager? StackNavigationManager { get; set; }

public MauiNavHostFragment()
{
}

protected MauiNavHostFragment(nint javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ void SetContentView(IView? view)
{
if (view == null)
{
if (_viewFragment != null && !FragmentManager.IsDestroyed())
if (_viewFragment != null && !FragmentManager.IsDestroyed(_mauiContext.Context))
{
FragmentManager
.BeginTransaction()
Expand Down
Loading

0 comments on commit 39277d8

Please sign in to comment.