Line-of-Business Skeleton for Xamarin App, Part 1: A Brick in the Wall
Developer is an analyst and solution designer who can model the business and technical parts

Line-of-Business Skeleton for Xamarin App, Part 1: A Brick in the Wall

Hello, developers and software designers :). I'm writing this article to open an idea about developing a skeleton for Xamarin applications with pluggable components and services. I've finished writing a technical document after we - as a team consist of @Basma Emad, @Norhan Gad and me - finished recently delivering a line-of-business mobile application to our client. We planned from the beginning to shape a skeleton inside this project that can help us later with the next projects, lets share it with you.

This is a technical article that demonstrates the architecture and designs used to implement the desired features. I tried to illustrate each piece of code, designs, thoughts and decisions visually in diagrams.

Project Profile

The project size is small, it took 60 working days and about 1300 hours as a total. Here are a few stats: 9000 line of code (without ui definition), 750 commit, 200 types/classes (40 interfaces included) and 60 namespace. The project deals with 6 business models, about 8 screens, 6 business services and about 18 technical and cross-cutting services, we will illustrate the following:

  • Authentication service (Azure AD)
  • Authorization service (based on role profile)
  • Session Management
  • Exception handler & Logging service (App Center)
  • Sync Manager (Offline mode)
  • Location service (Android & IOS)

Eco-System

Eco system components and parts

Mobile & Cloud Data Communication

Data flow between Mobile and Cloud database

Intercepting views here is for not mapping directly to the sync tables taking in consideration the performance, security and data transformation (projection) factors.

ERD Diagram

No alt text provided for this image

Screens Flow

No alt text provided for this image

Development Environment

  • Devices: Mac, iPad and Android tablet
  • Tools: Azure Subscription, SQL Server 2017, Visual Studio 2019, Visual Studio for Mac, TFS (Git) and SQLite Manager

Design Principles & Coding Style

  • Follow a unified naming convention
  • Follow MVVM pattern: No code behind in the Xaml pages, No UI namespace / logic in View Model Layer
  • Almost features are delivered after 2-3 cycle of reviewing
  • Apply SOLID: No if statement on user type by using role profile to not violate OCP principle, Use an abstracted IOC/DI with self-registration, Apply Separation of Concern
  • Develop a Skeleton for Maintainability: Define code contexts and areas
  • Use events, delegates and asynchronous logic

3rd Party Libraries / Packages

MvvmLightLibsStd10 (IoC container), Rg.Plugins.Popup (Page as Dialog), Xam.Plugin.Connectivity

Xamarin App Skeleton (The result)

No alt text provided for this image

Snippet from ViewModelLocator (IoC Definition)

public class ViewModelLocator : ILocator
{
        private static readonly Lazy<ViewModelLocator> lazy = new Lazy<ViewModelLocator>(() => new ViewModelLocator());

        public static ViewModelLocator Instance
        {
            get
            {
                var instance = lazy.Value;
                if (instance == null)
                {
                    throw new NotImplementedException("Locator not initialized!");
                }
                return instance;
            }
        }

        private static SimpleIoc _container;

        private ViewModelLocator()
        {
            _container = SimpleIoc.Default;
            _container.Register<ILocator>(() => Instance);

            RegisterServices();
            RegisterViewModels();
        }
}

Snippet from App.cs (Execution)

public static ILocator Locator { get; private set; }
public static Session CurrentSession { get; private set; }
public static User CurrentUser => CurrentSession?.CurrentUser;
public AEMViewModelBase CurrentPage => _navigationService?.CurrentViewModel as {Business}ViewModelBase;

protected override async void OnStart()
{
   LoggedIn += App_LoggedIn;
   LoggedOut += App_LoggedOut;
   Locator = ViewModelLocator.Instance;
   SetupAppCenter();
   ResolveServices();
   RegisterUserRoles();
   ListenToSync();
}

Unboxing Pages (UI)

No alt text provided for this image

snippet from base page xaml

<!--Header-->
<!--Content-->
<ContentView Grid.Row="1"
         Grid.ColumnSpan="4"
         Content="{Binding Source={x:Reference aemMasterPage}, Path=ContentPlaceHolder}"
         HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
</ContentView>
<!--Footer-->
<!--Loader-->

snippet from base page code behind demonstrates content place holder bindable property

public static readonly BindableProperty ContentPlaceHolderProperty = BindableProperty.Create(nameof(ContentPlaceHolder), typeof(View), typeof(AEMBasePage))

public View ContentPlaceHolder
{
    get { return (View)GetValue(ContentPlaceHolderProperty); }
    set { SetValue(ContentPlaceHolderProperty, value); }
}

Usage

<base:{Business}BasePage
    xmlns:base="clr-namespace:{ProjectName}.Mobile.Pages.Base.Business">

    <base:{Business}BasePage.ContentPlaceHolder>
        <!--Concrete Content-->
    </base:{Business}BasePage.ContentPlaceHolder>

</base:{Business}BasePage>

Unboxing ViewModel Layer & Observable Models

No alt text provided for this image

It is the layer that lies behind pages and each business view model inherit from a business VM base witch in turn inherit from technical VM base.

The observable models represent the models that carry the business data to be rendered in the UI. The validation is attribute-based placed on the properties and reflected in the UI.

No alt text provided for this image

Exception Handling & Logging

Snippet for one of the helper methods in ExceptionHelper class

public static async Task<Result> TryCatchAsync<Result>(Func<Task> action, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", Action catchDelegate = null, bool throwException=true)
{
    try
    {
         return await func();
    }
    catch (Exception ex)
    {
         _loggerService.Error(ex, new Dictionary<string, string>() { { "File", sourceFilePath }, { "Method", memberName } });
         catchDelegate?.Invoke();
         if (throwException)
                throw;
    }
}

Snippet from AppCenterLoggerService class that implements ILoggerService

public void Trace(string eventName, Dictionary<string, string> extraProperties = null)
{
    try
    {
        var currentSession = _sessionService.GetCurrentSession();
        if (currentSession == null)
            throw new Exception("Session not started to Trace !");

        var properties = extraProperties ?? new Dictionary<string, string>();
        properties.Add("Login Id", currentSession.Id.ToString());
        properties.Add("Time", DateTimeOffset.Now.ToString());
        properties.Add("UserId", currentSession.EmployeeInfo.NaaId);

        Analytics.TrackEvent(eventName, properties);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Trace.TraceError(ex.Message, ex.InnerException);
    }
}
   
public void Error(Exception exception, Dictionary<string, string> extraLogs = null)
{
    var properties = extraLogs ?? new Dictionary<string, string>();
    var currentSession = _sessionService.GetCurrentSession();
    if (currentSession != null)
    {
        properties.Add("UserId", currentSession.EmployeeInfo.NaaId);
        properties.Add("Login Id", currentSession.Id.ToString());
    }

    properties.Add("StackTrace", exception.StackTrace ?? string.Empty);
    properties.Add("InnerException",exception.InnerException?.ToString() ?? string.Empty);
    properties.Add("Exception Message", exception.Message ?? string.Empty);
    properties.Add("Exception Source",exception.Source ?? string.Empty);
    properties.Add("Time", DateTimeOffset.Now.ToString());

    Crashes.TrackError(exception, properties);
}

TryCatch Usage from App.cs

private async Task NavigateToLoginPage()
{
    await ExceptionHelper.TryCatchAsync(() =>
    {
        loginViewModel = Locator.Resolve<ILoginViewModel>();
        loginViewModel.LoginSuccessed = async (session) =>
        {
            CurrentSession = session;
            await _syncManager.Init(CurrentSession.CurrentUser.AssignedRole.SyncTables);
            await _syncManager.Sync();
            LoggedIn?.Invoke(this, EventArgs.Empty);

            await _navigationService.NavigateToHomePage(CurrentSession.CurrentUser.AssignedRole.HomeViewModelType);
            return true;
        };

        return _navigationService.NavigateToLoginPage();
    });
}

Next :

  • Xamarin Skeleton: Unboxing Services, Part 2: An Offline App
  • Xamarin UI Components, Part 3: Custom Data Grid & Wrap/Plugin 3rd party custom-dialog

To view or add a comment, sign in

More articles by Abdelrahman A.

Insights from the community

Others also viewed

Explore topics