How can I interact with my Xamarin Forms App from a referenced Xamarin Forms Class Library?

DFoulkDFoulk USMember ✭✭✭

I have the following projects:

  • Cross Platform App (Xamarin.Forms - PCL): "MyApp"
  • Class Library (Xamarin.Forms): "MyLibrary"

"MyApp" references the "MyLibrary" assembly. "MyLibrary" contains various methods that interface with our "in-house" Authentication WebAPI. I've managed to get the authentication working- but now I would like to affect our app's UI from within the class library...

How can I (for example) open an alert or change the Application.Current.MainPage from within "MyLibrary"?

I've tried the following:

MyApp - App.xaml.cs

using MyApp.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace MyApp
{
    public partial class App : Application
    {
        public MyLibrary.MyLibraryThing MyLibraryThing;

        public App()
        {
            MyLibraryThing = new MyLibrary.MyLibraryThing();

            InitializeComponent();

            SetMainPage();
        }

        public static void SetMainPage()
        {
            Current.MainPage = new MainPage();
        }

        protected override void OnStart()
        {
            MyLibraryThing.DoSomething();
        }
    }
}

MyLibrary - MyLibraryThing.cs

using Xamarin.Forms;

namespace MyLibrary
{
    public class MyLibraryThing()
    {
        Application myApp;

        public MyLibraryThing(Application application)
        {
            myApp = application;
        }

        public void DoSomething()
        {
            myApp.Current.MainPage = new ContentPage()
            {
                Content = new Label
                {
                    Text = "Created by MyLibrary.MyLibraryThing.DoSomething()!",
                    VerticalOptions = LayoutOptions.CenterAndExpand,
                    HorizontalOptions = LayoutOptions.CenterAndExpand
                }
            };
        }

        public async void DoSomethingElse()
        {
            await myApp.MainPage.DisplayAlert("Alert", "This alert was created by MyLibrary.MyLibraryThing.DoSomethingElse()!", "Cool");
        }
    }
}

The above produces the following exception:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> Java.Lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

How can I make my Application object accessible to my external class library?

Best Answer

Answers

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    now I would like to affect our app's UI from within the class library...

    I would scream "stop right there" - from a design point of view that is just very very wrong.

    Libraries shouldn't have a darned thing to do with UI. They don't even know if there is UI. They should be pure. They are logic and work.

    Your UI would be binded to the ViewModel. The ViewModel might use the the library. As a result of responses and state changes caused by authenticating and so on from the library, your UI can then update itself.

    But the library shouldn't try to be the God of the UI.

  • DFoulkDFoulk USMember ✭✭✭

    @ClintStLaurent said:

    now I would like to affect our app's UI from within the class library...

    I would scream "stop right there" - from a design point of view that is just very very wrong.

    Libraries shouldn't have a darned thing to do with UI. They don't even know if there is UI. They should be pure. They are logic and work.

    Your UI would be binded to the ViewModel. The ViewModel might use the the library. As a result of responses and state changes caused by authenticating and so on from the library, your UI can then update itself.

    But the library shouldn't try to be the God of the UI.

    That's a fair point... I believe my decision to build alerts into the class library was predicated on two things:
    1) This library will only be used by Xamarin Forms apps that require the same exact functionality
    2) I didn't really think about an alert being a "UI" element, thus I considered the alert to be "logic and work"

    What you said makes perfect sense though! I guess my intention here is/was to contain any and all things (including alerts and possibly pages) in this separate library. Maybe it didn't seem like a bad design decision because my Xamarin.Forms app is a PCL and it is aware of the UI and performs actions on the UI thread? I don't know...

    I will certainly review these design patterns- thank you for the recommendation!

  • DFoulkDFoulk USMember ✭✭✭

    I just wanted to add a note to this thread:

    Even without the "MyLibrary" class library, alerts cannot be opened from within a App.* method!

    So this error doesn't have anything to do with me trying to create said alert from inside the "MyLibrary" project:

    System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> Java.Lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Even without the "MyLibrary" class library, alerts cannot be opened from within a App.* method!

    Right. If you're using that default alert class. That has to be raised from a Page because a Page has control over the UI, and well, an alert is UI.

    Post people outgrow that class in about an hour. rg.plugins.popups let you create any kind of UI you like including pages, images, pickers... whatever... And not just be limited to that little system alert with text and a button.

  • DFoulkDFoulk USMember ✭✭✭

    @ClintStLaurent said:

    Even without the "MyLibrary" class library, alerts cannot be opened from within a App.* method!

    Right. If you're using that default alert class. That has to be raised from a Page because a Page has control over the UI, and well, an alert is UI.

    Post people outgrow that class in about an hour. rg.plugins.popups let you create any kind of UI you like including pages, images, pickers... whatever... And not just be limited to that little system alert with text and a button.

    Thanks for the referral, I could really use more robust alerts! I'll scope it out...

  • DFoulkDFoulk USMember ✭✭✭
    edited May 2017

    For anybody that is looking to launch an Alert or ActionSheet from an external Class Library:

    1. Pass the Application object to the external library's constructor as an argument or via a property
    2. Use Device.BeginInvokeOnMainThread() to create the Alert in the external library (see below)

      Device.BeginInvokeOnMainThread(async () =>
      {
          if (await Application.MainPage.DisplayAlert("Authentication Error", "You do not have permission to use this app!", "Try Again", "Exit"))
          {
                      // Try Again
          }
          else
          {
                      // Exit
          }
      });
      

    Device.BeginInvokeOnMainThread() can also be used in App.xaml.cs to display Alerts and ActionSheets. As @MichaelRumpler and @ClintStLaurent said, this is not necessarily a good pattern/design choice. However, if you have to make this happen- then the above method is the way to do it.

Sign In or Register to comment.