Search code examples
c#xamarin.formsxamarin.androidcustom-renderer

How to pass values from custom PageRenderer to Shared Xaml Page code behind in Xamarin


I have a Xamarin page in which I had to use Android native page renderer in order to support platform specific API.

BasePage.xaml passes control to MyPage.xaml with Navigation.PushAsync()

XAML page : MyPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Views.MyPage" Title="My Page">
    <ContentPage.Content>
    </ContentPage.Content>
</ContentPage>

Android Custom page renderer for the above is something like below.

[assembly: ExportRenderer(typeof(MyPage), typeof(MyPageRenderer))]
namespace MyApp.Droid.Renderers
{
    public class MyPageRenderer : PageRenderer
    {
        private Context _localContext;
        private global::Android.Views.View view;

        private Activity activity;
        public event EventHandler ItemAdded;

        public MyPageRenderer(Context context) : base(context)
        {
            _localContext = context;
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || Element == null)
            {
                return;
            }

            try
            {
                SetupUserInterface();
                SetupEventHandlers();
                AddView(view);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(@"ERROR: ", ex.Message);
            }
        }

        private void SetupUserInterface()
        {
            activity = this.Context as Activity;
            view = activity.LayoutInflater.Inflate(Resource.Layout.axml_layout, this, false);

        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);
            var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
            var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);

            view.Measure(msw, msh);
            view.Layout(0, 0, r - l, b - t);
        }

        private void SetupEventHandlers()
        {
            //blah blah
        }

        private void ButtonTapped(object sender, EventArgs e)
        {

            //do something
            //Here Navigate back to page which triggered this with outcome parameter or some event 
            ItemAdded(this, EventArgs.Empty);

        }


    }
}

My intention is send back control to MyPage.xaml.cs or BasePage.xaml.cs from MyPageRenderer with outcome of ButtonTapped.I am using event ItemAdded and handle it in code behind of that page. I can not access ItemAdded event which is in android specific renderer only from shared project.

I have to update ViewModel of BasePage so that I update the content of the items there when MyPage has been popped after adding new item by back button.

Problem: I can access MyPage and BasePage but can not access renderer method and variables from Shared project because Android project depends on shared not vice versa.

I have to do something like below which is working for non-native render page BasePage:

    var myPage = new MyPage();
    myPage.ItemAdded += OnItemAdded;
    await Navigation.PushAsync(myPage);

MyPage:

public event EventHandler ItemAdded;
.
.

void SomeMethod(){

ItemAdded(this, EventArgs.Empty);

}

Question: How do we pass control from NativeRenderer back to Xamarin Forms shared code?

I know we can pass control to MainActivity class but I want to pass control to BasePage.xaml.cs which I did not get from documentation. If anyone has worked on PageRenderer please suggest.


Solution

  • in "MyPage" Class

      public class MyPage : ContentPage
        {
            public void RaiseSomeButtonClicked() => OnSomeButtonClickeded();
    
            private void OnSomeButtonClicked()
            {
                //by using aggregators you can publish any event and subscribe it in you BasePage.xaml.cs 
                ((App)App.Current).Container.Resolve<IEventAggregator>()
                    .GetEvent<SomeButtonClickedEvent>().Publish(new SomeButtonClickedEvent());
            }
        }
    

    in "MyPageRenderer" Class :

    public class MyPageRenderer : PageRenderer
        {
            MyPage myPage;
            //...
            protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
            {
                myPage = (MyPage)e.NewElement;
                //...
            }
            private void ButtonTapped(object sender, EventArgs e)
            {
    
                //do something
                myPage.RaiseSomeButtonClicked();
    
            }
        }
    

    in "BasePage.xaml.cs", subscribe this event.

    public partial class BasePage : ContentPage
    {
        private readonly SubscriptionToken _SomeButtonClickedEventSubscription;
    
        public BasePage()
        {
            InitializeComponent();
    
            _SomeButtonClickedEventSubscription = eventAggregator.Value.GetEvent<SomeButtonClickedEvent>().SubscribeAsync(async e =>
        {
            //anything you want to do when button clicked!
    
        }, threadOption: ThreadOption.UIThread, keepSubscriberReferenceAlive: true);
        }
    }
    

    You should define Your event class in this way:

    public class SomeButtonClickedEvent : PubSubEvent<SomeButtonClickedEvent>
        {
            //you can define parameters here, if the event needs to pass a parameter.
        }