Xamarin.Forms Floating Action Button

Xamarin.Forms Floating Action Button

person holding space gray iphone x

This article is a review of the Xamarin.Forms NuGet package, Floating Action Button (FAB). Here, we’ll be discussing what the library is, and how to use MVVM binding with Prism.Forms’ Navigation Service, and then finish up with some of the pros/cons of the library. For the full project, check out my GitHub page, Learn.FloatingActionButton, which this article references.

I stumbled upon the Floating Action Button while responding to an inquiry made on the Xamarin Slack in regards to the forums thread, “Can I maintain a single static navigation service from Prism.Forms“. If you’re wondering, I’m, “User29117“.

What is it?

The Floating Action Button provides a graphical sub-menu for your Android and iOS apps. You can choose to persist it throughout the entire application or on a single View if you’d prefer. This NuGet package shows potential and if you’d like to assist the author, contribute to his project’s GitHub.

Floating Action Button in action!

How to Navigate

To get started you’ll need to add a couple of lines to your Android’s MainActivity.cs and/or iOS’ AppDelegate.cs file(s).

Android – MainActivity.cs

// Android - MainActivity.cs
using Xamarin.RisePlugin.Droid.Floatingactionbutton;
namespace App2.Droid
{
  [Activity( ... )]
  public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
  {
    ...

    public override void SetContentView(Android.Views.View view)
    {
      RootView.View = (Android.Widget.RelativeLayout)view;
      base.SetContentView(view);
    }
  }
}

iOS – AppDelegate.cs

// iOS - AppDelegate.cs
using Foundation;
using UIKit;
using Xamarin.RisePlugin.IOS.Floatingactionbutton;

namespace App2.iOS
{
  [Register("AppDelegate")]
  public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
  {
    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
      global::Xamarin.Forms.Forms.Init();

      // Initialize right after Xamarin.Forms' init
      COAFloatingactionbutton.Init();

      LoadApplication(new App());

      return base.FinishedLaunching(app, options);
    }
  }
}

Wire-in MVVM

Since this library was built for Click Events and not MVVM data bindings, we’re going to do a little work around in the View’s .xaml.cs file so we can talk directly to the ViewModel using Prism.Forms.

This project consists of 2 Views, our entry point, “MainPage.xaml” and “ContentPage2.xaml” which we will nagivate to using Prism’s Navigation Service. To go back to the MainPage, we just simply press the back button that the OS provides us with.

Displaying and Binding Overview

Upon navigating to the page, we will use override the OnAppearing() method and call Wireup() to create the FAB SubItems and also get our ViewModel’s Binding (_viewModel).

When navigating away from the page, we need to clear the FAB’s SubViews. To do so, we override the OnDisappearing() method. If you don’t do this, you’ll create additional SubItem buttons when we navigate back to it.

Let’s get coding!

MainViewModel.cs

In the ViewModel, we’re going to wire-up the navigation to our sub-page using Prism’s DelegateCommand and also create the property, Message.

using App2.Views;
using Prism.Commands;
using Prism.Navigation;

namespace App2.ViewModels
{
  public class MainPageViewModel : ViewModelBase
  {
    private string _message;

    public MainPageViewModel(INavigationService nav) : base(nav)
    {
      Title = "Main Page";
    }

    public DelegateCommand CmdNavigatePage2 => new DelegateCommand(OnNavigatePage2);

    private async void OnNavigatePage2()
    {
      await NavigationService.NavigateAsync(nameof(ContentPage2));
    }

    public string Message
    {
      get => _message;
      set => SetProperty(ref _message, value);
    }
  }
}

MainPage.xaml

Next, we’re going to create a simple set of Labels in our MainPage.xaml. Remember, we want to bind to the ViewModel’s “Message” property.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             mc:Ignorable="d"
             Title="{Binding Title}"
             x:Class="App2.MainPage">

  <StackLayout>
    <!-- Place new controls here -->
    <Label Text="Floating Action Button"
           HorizontalOptions="Center"
           FontAttributes="Bold" />
    
    <Label Text="using MVVM and Prism.Forms!"
           HorizontalOptions="Center"
           FontAttributes="Bold" />
    
    <Label Text="- - - - - -"
           HorizontalOptions="Center" />

    <Label Text="{Binding Message}"
           HorizontalOptions="Center" />
  </StackLayout>
</ContentPage>

MainPage.xaml.cs

And finally, we create and initialize our Floating Action Button as well as setting capturing our Page’s ViewModel that it is binded to. Keep in mind, Prism performs an auto-wire to the ViewModel so we don’t have to manually set it. YAY, PRISM!

In our Wireup() method, we first create out main ActionButtonView called, btn, followed by a series of ActionButtonView which act as our sub menu items. Next, we listen in for the Click and LongClick event handlers. Followed by, attaching them via, “COAFloatingactionbutton.Current.SubViews.Add(btnSubXXX);

And finally, we wrap it up with configuring our main action button’s style and attaching to its event handler, “Btn_ClickActionButton” for showing and hiding the sub-buttons when pressed.

using System;
using System.ComponentModel;
using App2.ViewModels;
using Xamarin.Forms;
using Xamarin.RisePlugin.Floatingactionbutton;
using Xamarin.RisePlugin.Floatingactionbutton.Enums;

namespace App2
{
  // Learn more about making custom code visible in the Xamarin.Forms previewer
  // by visiting https://aka.ms/xamarinforms-previewer
  [DesignTimeVisible(false)]
  public partial class MainPage : ContentPage
  {
    private MainPageViewModel _viewModel;
    public MainPage()
    {
      InitializeComponent();
    }

    protected override void OnDisappearing()
    {
      base.OnDisappearing();

      // Close and clear since we're transiting away.
      COAFloatingactionbutton.Current.Close();
      COAFloatingactionbutton.Current.SubViews.Clear();
    }

    protected override void OnAppearing()
    {
      Wireup();

      // Must wire-up binding here for Prism's AutowireViewModel to kick in.
      _viewModel = (MainPageViewModel)this.BindingContext;
    }

    private void Wireup()
    {
      var btn = new ActionButtonView() { BackgroundColor = Color.Aqua, SelectedColor = Color.Brown, HeightRequest = 50, Icon = "logo.png" };

      btn.Margin = new Thickness(20, 10, 10, Device.RuntimePlatform == Device.Android ? 160 : 10);
      var btnSub1 = new ActionButtonView() { BackgroundColor = Color.IndianRed, HeightRequest = 50, SelectedColor = Color.Pink, Icon = "tab_feed.png" };
      var btnSub2 = new ActionButtonView() { BackgroundColor = Color.Orange, HeightRequest = 50, SelectedColor = Color.Pink, Icon = "logo.png" };
      var btnSub3 = new ActionButtonView() { BackgroundColor = Color.OrangeRed, HeightRequest = 50, SelectedColor = Color.Pink, Icon = "logo.png" };
      var btnSub4 = new ActionButtonView() { BackgroundColor = Color.Maroon, HeightRequest = 50, SelectedColor = Color.Pink, Icon = "logo.png" };
      var btnSub5 = new ActionButtonView() { BackgroundColor = Color.Lime, HeightRequest = 50, SelectedColor = Color.Pink, Icon = "logo.png" };
      // var btnSub6 = new ActionButtonView() { BackgroundColor = Color.LightGreen, SelectedColor = Color.Pink, Icon = "logo.png" };

      btnSub1.Click += BtnSub1_Click;
      btnSub2.Click += BtnSub2_Click;
      btnSub3.Click += BtnSub3_Click;
      btnSub4.Click += BtnSub4_Click;
      btnSub5.Click += BtnSub5_Click;

      btnSub1.LongClick += BtnSub1_LongClick;

      COAFloatingactionbutton.Current.SubViews.Add(btnSub1);
      COAFloatingactionbutton.Current.SubViews.Add(btnSub2);
      COAFloatingactionbutton.Current.SubViews.Add(btnSub3);
      COAFloatingactionbutton.Current.SubViews.Add(btnSub4);
      COAFloatingactionbutton.Current.SubViews.Add(btnSub5);

      btn.Click += Btn_ClickActionButton;
      COAFloatingactionbutton.Current.ActionOrientation = StackActionOrientation.Center;
      COAFloatingactionbutton.Current.OpeningType = ActionOpeningType.Circle;
      COAFloatingactionbutton.Current.CircleAngle = 150;
      COAFloatingactionbutton.Current.MainButtonView = btn;
      COAFloatingactionbutton.Current.Open();
    }

    private void BtnSub1_LongClick(object sender, EventArgs e)
    {
      // Initiate long-click
      _viewModel.Message = "Long-Pressed Button 1";
      COAFloatingactionbutton.Current.ActionOrientation = StackActionOrientation.Center;
    }

    private void BtnSub1_Click(object sender, EventArgs e)
    {
      _viewModel.Message = "Clicked Button 1";
      COAFloatingactionbutton.Current.ActionOrientation = StackActionOrientation.Center;
    }

    private void BtnSub2_Click(object sender, EventArgs e)
    {
      _viewModel.Message = "Clicked Button 2";
      COAFloatingactionbutton.Current.ActionOrientation = StackActionOrientation.Right;
    }

    private void BtnSub3_Click(object sender, EventArgs e)
    {
      _viewModel.Message = "Clicked Button 3";
      COAFloatingactionbutton.Current.ActionOrientation = StackActionOrientation.Left;
    }

    private void BtnSub4_Click(object sender, EventArgs e)
    {
      _viewModel.Message = "Clicked Button 4";

      // Wire-up to ViewModel
      MainPageViewModel viewModel = (MainPageViewModel)this.BindingContext;
      if (viewModel.CmdNavigatePage2.CanExecute())
      {
        viewModel.CmdNavigatePage2.Execute();
      }
    }

    private void BtnSub5_Click(object sender, EventArgs e)
    {
      _viewModel.Message = "Clicked Button 5";
    }

    private async void Btn_ClickActionButton(object sender, EventArgs e)
    {
      if (!COAFloatingactionbutton.Current.IsSubShowing)
        await COAFloatingactionbutton.Current.ShowSubView();
      else
        await COAFloatingactionbutton.Current.HideSubView();
    }
  }
}

Pros and Cons

In short, there are use cases for this easy-to-use library. So far I’ve found it to be robust, efficient, and useful.

Now the flip-side, the library currently do not support adding the Floating Action Button directly in your XAML and perform Bindings from there. But none the less, it is open-source and all you have to do is create a Pull Request for the author to review.

I always support reaching out to project authors. It’s a great way to help grow the open source community and keep great projects like this alive.