Talking to Eric Smith about Software Craftsmanship

I talk to Eric Smith about Software Craftsmanship, reality, and getting your first job:

Advertisements

Story Siloed Architecture

Intro

Imagine implementing user stories as independent modules within an application.

  • What if we could implement each user story in complete isolation from the other user stories within our application?
  • What if we could test each user story in complete isolation from the other user stories within our application?
  • What if we could rewrite an implementation of a user story without any affects to the behavior of existing features within our application?
  • What if we could examine code coverage per implemented user story?
  • What if we could examine the maintainability of each implemented user story?
  • What if there were a framework in place where talented engineers could really lead by example?

User Stories as Silos

silo-1030x773[1]

Silo: a system, process, department, etc. that operates in isolation from others:

I am a disciple of Martin’s teachings. As a result, I decided to implement my interpretation of his Clean Architecture idea. I built Quote Builder three years ago and tried my hardest to conform to Microsoft’s user experience guidelines for their Metro design. That resulted in a third of the app’s ratings being one-star.

The client architecture of that version is below:

MetroArchitecture

The Clean Architecture approach that I built with Xamarin.Forms is the following:

XamarinArchitecture

A user-story module within the architecture is as follows:

XamarinSolutionExplorer

The Solution Explorer for the Windows Store app looks like the following:

MetroArchitecture_solutionExplorer

The Solution Explorer for Xamarin.Forms app looks like the following:

Xamarin_solutiion_explorer_architecture

The following table reflects code analysis results of the application including user story modules:

CodeMetrics

Retrospective on Story Siloed Architecture

When I rewrote Quote Builder for Xamarin.Forms, I created a project per user story. I also created a unit test project per user story. This resulted in about 45 projects within my solution. Thus, this gave me long load times (approximately 15 seconds) when opening the solution in Visual Studio 2015. I also found myself waiting for half a minute each time I would build my solution. As a result, I consolidated my unit test projects to just one project which reduced load-time by a third.

Teams implementing Story Siloed Architecture, need to respond to the organic growth of the software over time in which the application becomes proliferated with projects that take will eventually take a toll on a solution’s load-time and build processes. There are workarounds though. For example, multiple projects can be consolidated to one project based on the related concerns of user story modules. In addition, other solutions can be created per related set of modules. This will enable a solution to replace project dependencies with DLLs. Performing this substitution will reduce the time to load a solution, build it, and execute unit tests by orders of magnitude faster than relying on only projects.

I also like the side-effect of isolating user story modules. Hence, this architecture reduces the likelihood for the codebase to experience entropy. For example, the isolation of each user story module enables code analysis to be utilized more effectively. Hence we can now scrutinize each user story’s implementation which (because of this architecture) is now in complete isolation of other user stories. For example, we can now examine the maintainability index and code coverage from unit tests targeting a specific user story module. Thus, isolating user stories into independent modules for developers to pull from, will encourage developers to finally acknowledge ownership of their code’s quality because no other developer will modify their module without the owner’s review and approval. Another benefit is that user story modules that were poorly implemented can now be refactored or in some cases rewritten without having to rewrite a significant portion of the application. Also, developers that do hold themselves accountable for the quality of their code are  now enabled to lead by example. Hence, because their user story is isolated from the modules that other developers are tasked with, quality focused developers can now own the quality of their story and demonstrate how to write solid code with outstanding code coverage which other developers can reference and learn from.

I am not convinced that implementing Story Siloed Architecture is worth the overhead for a single developer project. I do believe that this practice will encourage developers to be more surgical when it comes to maintaining an application’s architecture due to isolated user story responsibilities.

In addition, I do believe that clean architecture makes software more maintainable over the long run. Developers can now zero-in on a user story module that’s most likely to host a reported bug. It also compliments Visual Studio’s CodeMap so that maintainers can identify dependencies of specific user-stories as the user-story module diagram conveyed.

Conclusion

In conclusion, I ported a Windows Store app to Xamarin.Forms using Story Siloed Architecture. I provided disclaimers about using this approach in regards to load-times with Visual Studio solutions and what can be done to resolve those concerns. I also identified the benefits of leveraging this pattern in regards to refactoring or rewriting a user story module as well as the increase in code quality.

NOTE:

Scott Nimrod is fascinated with Software Craftsmanship.

He loves responding to feedback and encourages people to share his articles.

He can be reached at scott.nimrod @ bizmonger.net

Xamarin.Forms: Camera Integration

Intro

In the last post, I wrote a tutorial on how to integrate Google Maps and GPS services into a Xamarin.Forms app. This post will focus on camera integration with Xamarin.Forms. In this post I will provide code (referenced from XF Labs) that will enable your Xamarin.Forms app to take and view photos.

The following image results from this tutorial when taking a picture with an Android device:

CamaraIntegration

Xamarin Labs

The code that I’m going to share was borrowed from a Xamarin.Forms Labs sample project. This sample can be found here.

Steps

  1. Ensure the following Nuget packages are installed on your Xamarin.Forms project and Android project:
  • Xamarin.Forms
  • XLabs.Forms
  • XLabs.IoC

2.   Update your MainActivity class to look like the following:

public class MainActivity : XFormsApplicationDroid
{
    bool _initialized = false;
 
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
 
        if (!_initialized) SetIoc();
 
        global::Xamarin.Forms.Forms.Init(this, bundle);
        Xamarin.FormsMaps.Init(this, bundle);
        LoadApplication(new App());
    }
 
    void SetIoc()
    {
        var resolverContainer = new SimpleContainer();
 
        var app = new XFormsAppDroid();
        app.Init(this);
 
        resolverContainer.Register<IDevice>(t => AndroidDevice.CurrentDevice)
            .Register<IDisplay>(t => t.Resolve<IDevice>().Display)
            .Register<IDependencyContainer>(resolverContainer)
            .Register<IXFormsApp>(app);
        Resolver.SetResolver(resolverContainer.GetResolver());
 
        _initialized = true;
    }
}

3.   Implement the following view-model:

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
 
    IMediaPicker _mediaPicker = null;
    readonly TaskScheduler _scheduler = TaskScheduler.FromCurrentSynchronizationContext();
 
    public ViewModel()
    {
        TakePictureCommand = new DelegateCommand(async obj => await TakePicture());
        SelectPictureCommand = new DelegateCommand(async obj => await SelectPicture());
    }
 
    ImageSource _imageSource = null;
    public ImageSource ImageSource
    {
        get { return _imageSource; }
        set
        {
            if (_imageSource != value)
            {
                _imageSource = value;
                OnPropertyChanged();
            }
        }
    }
 
    DelegateCommand _takePictureCommand = null;
    public DelegateCommand TakePictureCommand
    {
        get { return _takePictureCommand; }
 
        set
        {
            if (_takePictureCommand != value)
            {
                _takePictureCommand = value;
                OnPropertyChanged();
            }
        }
    }
 
    DelegateCommand _selectPictureCommand = null;
 
    public DelegateCommand SelectPictureCommand
    {
        get { return _selectPictureCommand; }
        set
        {
            if (_selectPictureCommand != value)
            {
                _selectPictureCommand = value;
                OnPropertyChanged();
            }
        }
    }
 
    void Setup()
    {
        if (_mediaPicker != null) return;
 
        var device = Resolver.Resolve<IDevice>();
 
        _mediaPicker = DependencyService.Get<IMediaPicker>();
        //RM: hack for working on windows phone? 
        if (_mediaPicker == null) _mediaPicker = device.MediaPicker;
    }
 
    async Task TakePicture()
    {
        Setup();
 
        ImageSource = null;
 
        await _mediaPicker.TakePhotoAsync(new CameraMediaStorageOptions { DefaultCamera = CameraDevice.Front, MaxPixelDimension = 400 }).ContinueWith(t =>
        {
            if (t.IsFaulted) { var s = t.Exception.InnerException.ToString(); }
 
            else if (t.IsCanceled) { var canceled = true; }
 
            else
            {
                var mediaFile = t.Result;
                ImageSource = ImageSource.FromStream(() => mediaFile.Source);
 
                return mediaFile;
            }
 
            return null;
        }, _scheduler);
    }
 
    async Task SelectPicture()
    {
        Setup();
 
        ImageSource = null;
        try
        {
            var mediaFile = await _mediaPicker.SelectPhotoAsync(new CameraMediaStorageOptions
            {
                DefaultCamera = CameraDevice.Front,
                MaxPixelDimension = 400
            });
            ImageSource = ImageSource.FromStream(() => mediaFile.Source);
        }
 
        catch (System.Exception ex) { Debug.WriteLine(ex.Message); }
    }
 
    void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

4.   Implement following 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"
             xmlns:local="clr-namespace:Pickup;assembly=Pickup"
             x:Class="Pickup.TakePhotoView">
 
  <ContentPage.BindingContext>
    <local:ViewModel />
  </ContentPage.BindingContext>
  
  <StackLayout>
    <Button Text="Take Picture" Command="{Binding TakePictureCommand}" />
    <Button Text="Select Image from Picture Library" Command="{Binding SelectPictureCommand}" />
    <Button Text="Select Video from Picture Library " Command="{Binding SelectVideoCommand}" />
    <Image Source="{Binding ImageSource}" VerticalOptions="CenterAndExpand" />
    <Entry Text="{Binding VideoInfo}" VerticalOptions="CenterAndExpand" />
  </StackLayout>
         </ContentPage>

Conclusion

In conclusion, I shared some code with that will enable a developer to integrate a camera on an Android device with a Xamarin.Forms app.

Xamarin.Forms: Integrating Maps and Location Services

Intro

Within Xamarn.Forms, we may find ourselves having to implement a feature that’s dependent on a map and/or GPS. In order to implement this for Android and provide other controls to manage related services, we must implement a native Android page within our Xamarin.Forms app. In this post I will demonstrate how to load a native android page that contains a map that leverages GPS to identify a phones location.

Steps

1. Get access to the Google Maps API

2. Add a new class to your Xamarin.Forms project and call it “CustomPage”.
Solution Explorer:

SolutionExplorer_customPage

Class:

using Xamarin.Forms;
 
namespace Pickup
{
    public class CustomPage : ContentPage { }
}

2. In your Xamarin.Forms project, add a XAML file.

SolutionExplorer_xamlFile

Implement the following XAML:

<?xml version="1.0" encoding="utf-8" ?>
<local:CustomPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps" xmlns:local="clr-namespace:Pickup;assembly=Pickup" x:Class="Pickup.MapPage" />

3. Add a Layout folder to your Android project as well as an axml file.

SolutionExplorer_axmlFile

4. Implement the axml file as the following:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
  <View android:layout_height="0dp" android:layout_width="fill_parent" android:layout_weight="1" />
  
  <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/map" android:layout_width="200dp" android:layout_height="200dp" class="com.google.android.gms.maps.MapFragment" />

  <TextView android:id="@+id/textView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="top"/>
  
  <View android:layout_height="0dp" android:layout_width="fill_parent" android:layout_weight="1" />
  
  <Button android:id="@+id/button_code" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Locate Me!" />
</LinearLayout>

5. Add a new class named LocationService under your Android project:

LocationService

Provide the following implementation:

    using static Bizmonger.Patterns.MessageBus;

    public class LocationService : Java.Lang.Object, ILocationListener
    {
        LocationManager _locationManager = null;

        public void GetLocation()
        {
            _locationManager = Application.Context.GetSystemService(Context.LocationService) as LocationManager;

            var locationCriteria = new Criteria();

            locationCriteria.Accuracy = Accuracy.Coarse;
            locationCriteria.PowerRequirement = Power.Medium;

            var locationProvider = _locationManager.GetBestProvider(locationCriteria, true);

            if (locationProvider != null)
            {
                _locationManager.RequestLocationUpdates(locationProvider, 2000, 1, this);
            }
        }

        public void OnLocationChanged(Location location) =>
            Publish(Messages.LOCATION_CHANGED, new Core.Location(location.Latitude, location.Longitude));

        public void OnProviderDisabled(string provider)
        {
            throw new NotImplementedException();
        }

        public void OnProviderEnabled(string provider)
        {
            throw new NotImplementedException();
        }

        public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras)
        {
            throw new NotImplementedException();
        }
    }

6. Add a custom renderer to your Android project by adding a new class.
SolutionExplorer_maprenderer

7. Implement the renderer with the following code:

using static Bizmonger.Patterns.MessageBus;

[assembly: ExportRenderer(typeof(CustomPage),
                          typeof(MapPageRenderer))]
namespace Pickup.Droid
{
    public class MapPageRenderer : PageRenderer
    {
        Core.Location _location;
        Android.Views.View _view;
        GoogleMap _map;

        public MapPageRenderer()
        {
            Subscribe(Messages.LOCATION_CHANGED, obj =>
                {
                    _location = obj as Core.Location;

                    LatLng location = new LatLng(_location.Latitude, _location.Longitude);
                    CameraPosition.Builder builder = CameraPosition.InvokeBuilder();
                    builder.Target(location);
                    builder.Zoom(18);
                    CameraPosition cameraPosition = builder.Build();
                    CameraUpdate cameraUpdate = CameraUpdateFactory.NewCameraPosition(cameraPosition);

                    var activity = this.Context as Activity;
                    MapFragment mapFrag = (MapFragment)activity.FragmentManager.FindFragmentById(Resource.Id.map);
                    _map = mapFrag.Map;
                    _map?.MoveCamera(cameraUpdate);

                    var textview = _view.FindViewById<Android.Widget.TextView>(Resource.Id.textView);
                    textview.Text = $"{_location.Latitude} , {_location.Longitude}";
                });
        }

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

            var activity = this.Context as Activity;
            _view = activity.LayoutInflater.Inflate(Resource.Layout.MapLayout, this, false);

            var mapFragment = activity.FragmentManager.FindFragmentById<MapFragment>(Resource.Id.map);

            var codeButton = _view.FindViewById<Android.Widget.Button>(Resource.Id.button_code);
            codeButton.Click += OnButtonTapped;

            AddView(_view);
        }

        void OnButtonTapped(object sender, EventArgs e)
        {
            var locationService = new LocationService();
            locationService.GetLocation();
        }

        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);
        }
    }

8. When you run your app, you should see something like this:

defaultMap

9. Click the “Locate Me!” button and observe the map’s navigation to your location.

app_screenshot

Conclusion

In conclusion, we may find ourselves having to implement a feature that’s dependent on a map and/or GPS. In order to implement this for Android and provide other controls to manage related services, we must implement a native Android page within our Xamarin.Forms app. In this post I demonstrated how to load a native android page that contains a map that leverages GPS to identify a phones location.

NOTE:

Scott Nimrod is fascinated with Software Craftsmanship.

He loves responding to feedback and encourages people to share his articles.

He can be reached at scott.nimrod @ bizmonger.net

Xamarin.Forms: Displaying a Native Android Page

Intro

Within Xamarn.Forms, we may find ourselves having to show a custom page per device platform (i.e. IOS, Android, etc.) In my case, I needed to display a content page that had a map along with some other controls. In this post I will demonstrate how to load a native android page within a Xamarin.Forms app.

Steps

  1. Add a new class to your Xamarin.Forms project and call it “CustomPage”.

Solution Explorer:

SolutionExplorer_customPage

Class:

using Xamarin.Forms;
 
namespace Pickup
{
    public class CustomPage : ContentPage { }
}

2. In your Xamarin.Forms project, add a XAML file.

SolutionExplorer_xamlFile

Implement the following XAML:

<?xml version="1.0" encoding="utf-8" ?>
<local:CustomPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
xmlns:local="clr-namespace:Pickup;assembly=Pickup" 
x:Class="Pickup.MapPage" />

3. Add a Layout folder to your Android project as well as an axml file.

SolutionExplorer_axmlFile

4. Implement the axml file as the following:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
  <View android:layout_height="0dp" android:layout_width="fill_parent" android:layout_weight="1" />
  
  <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/map" android:layout_width="200dp" android:layout_height="200dp" class="com.google.android.gms.maps.MapFragment" />

  <TextView android:id="@+id/textView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="top"/>
  
  <View android:layout_height="0dp" android:layout_width="fill_parent" android:layout_weight="1" />
  
  <Button android:id="@+id/button_code" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Locate Me!" />
</LinearLayout>

5. Add a custom renderer to your Android project by adding a new class.
SolutionExplorer_maprenderer

6. Implement the renderer with the following code:

[assembly: ExportRenderer(typeof(CustomPage),
                          typeof(MapPageRenderer))]
namespace Pickup.Droid
{
    public class MapPageRenderer : PageRenderer
    {
        Android.Views.View _view;

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
        {
            base.OnElementChanged(e);
 
            var activity = this.Context as Activity;
            _view = activity.LayoutInflater.Inflate(Resource.Layout.MapLayout, this, false);
 
            AddView(_view);
        }
 
        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);
        }
    }
}

7. When you run your app, you should see something like this:

defaultMap

Conclusion

In conclusion, we may find ourselves having to show a custom page with Xamarin.Forms per device platform (i.e. IOS, Android, etc.) In my case, I needed to display a content page that had a map along with some other controls. In this post I demonstrated how to load a native android page within a Xamarin.Forms app.

NOTE:

Scott Nimrod is fascinated with Software Craftsmanship.

He loves responding to feedback and encourages people to share his articles.

He can be reached at scott.nimrod @ bizmonger.net