Showing posts with label development. Show all posts
Showing posts with label development. Show all posts

Friday, September 25, 2015

Creating an Image Viewer In Xamarin.Forms with NControls

This blog post is about how to create custom controls within the Xamarin.Forms platform using the NControl library (which you can read more about here).
The scope for this article is to show how to create a control that supports simple scaling and panning (create gesture controls is something we'll save for later.) and has a bindable Source property that can be used in a view to connect with an ImageSourceproperty in your viewmodel. 
What we'll do in this blog post is:
  • Create a new Cross Platform Xamarin.Forms project
  • Install NControl from NuGet
  • Create a new custom control (our Image Viewer control)
  • Add a bindable property for the Image source property in our control
  • Add an inner image in our control
  • Handle touch events to add panning

New Project

Create a new Cross Platform App in Xamarin Studio by opening the File > New Solution menu and follow the wizard so you end up with a solution containing an iOS, Android and shared project (and maybe a project for UI testing as well).

NuGet Packages

To build our control we need to add the NControl libraries using the NuGet package manager included in Xamarin Studio (I'll skip this part from this blog post) - make sure you install the NuGet package NControl in the Android, iOS and Shared PCL project.
Make sure you add the correct initialization of the library:

Android

In your MainActivity.cs file:
protected override void OnCreate (Bundle bundle)
{
    base.OnCreate (bundle);

    global::Xamarin.Forms.Forms.Init (this, bundle);
    NControlViewRenderer.Init ();

    LoadApplication (new App ());
}

iOS

In your AppDelegate.cs file
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init ();
    NControlViewRenderer.Init ();

    LoadApplication (new App ());

    return base.FinishedLaunching (app, options);
}
Use the ALT+ENTER keyboard shortcut to get Xamarin Studio to insert the correct using statements for the new lines you've added.

Create a new Control

Add a new class to your shared PCL project called ImageViewer.cs. This is the starting point for our container control. Make sure it inherits from NControlView:
using System;
using NControl.Abstractions;
using Xamarin.Forms;
using System.Linq;

namespace DraggableImageTest
{
    public class ImageViewer: ContentView
    {
    }
}

Adding a Bindable Property

Next up we need to give our control a property that can be bound to a view model to display images in the file ImageViewer.cs:
public static BindableProperty SourceProperty = 
    BindableProperty.Create<ImageViewer, ImageSource> (p => p.Source, null,
        BindingMode.TwoWay, propertyChanged: (bindable, oldValue, newValue) => {
            var ctrl = (ImageViewer)bindable;
            ctrl.Source = newValue;
        });

public ImageSource Source {
    get{ return (ImageSource)GetValue (SourceProperty); }
    set {
        SetValue (SourceProperty, value);
    }
}

Adding a Test Image

No lets add an image for testing! Find your favourite image and add it to the PCL project. Right-click on the file and set Build Actionto EmbeddedResource. This way the image will be included in the assembly as a resource you can load during runtime.
To be able to inspect and retreive a resource from our assembly we need to add the System.Reflection namespace to our using list:
using System.Reflection;
Now lets update our main page to show our control. Open the startup file in your project (usually the csharp file in your PCL project with the same name as the project itself):
public App ()
{
    // The root page of your application
    MainPage = new ContentPage {
        Content = new StackLayout {
            VerticalOptions = LayoutOptions.Center,
            Children = {
                new Label {
                    XAlign = TextAlignment.Center,
                    Text = "Image Container:"
                },
                new ContentView{
                    BackgroundColor = Color.Gray,
                    Padding = 25,
                    HeightRequest = 200,
                    Content = new ImageViewer{
                        Source = ImageSource.FromStream(() => {
                            var assembly = this.GetType().GetTypeInfo().Assembly;
                            return assembly.GetManifestResourceStream ("Your namespace.Your image filename");
                        }),
                    }                           
                }
            }
        }
    };
}
As you can see we're wrapping the image viewer in a ContentView to enable some padding and color to make our control stand out. 
Make sure you update the line with the code return assembly.GetManifestResourceStream ("Your namespace.Your image filename") with your own namespace and filename.

Add Inner Image Control

To display an image and be able to move and scale it, we'll add an inner Image control in our ImageViewer control. Declare a private field in your ImageViewer class:
private Image _image;
In the ImageViewer constructor we'll instantiate the Image view:
public ImageViewer ()
{
    IsClippedToBounds = true;

    _image = new Image ();
    _image.Scale = 2.0;
    _image.InputTransparent = true;

    Content = _image;
}
The key to get the displayed image to scale and pan inside our viewport is to set the IsClippedToBounds property of the ImageViewer control to true to hide everything outside our control's bounds.
Update the setter for the Source property to update the Image view:
public ImageSource Source {
    get{ return (ImageSource)GetValue (SourceProperty); }
    set {
        SetValue (SourceProperty, value);
        _image.Source = value;
    }
}
Now you should be able to run the app and see if it shows your image zoomed in and inside your control's bounds.

Add Panning

NControlView has support for touch handling, and as an exercise we'll add simple panning to our ImageViewer control.
Add a new field in the ImageViewer class that we can use to calculate deltas when handling touches:
private NGraphics.Point _startPoint;
Now override the TouchesBegan method in the ImageViewer class to begin handling touches:
public override bool TouchesBegan (System.Collections.Generic.IEnumerable<NGraphics.Point> points)
{
    _startPoint = points.FirstOrDefault ();
    _startPoint.X -= _image.TranslationX;
    _startPoint.Y -= _image.TranslationY;
    return true;
}
First of all we'll save the location when touches starts. Next we add the current translation for the image to be able to pan multiple times. Remember to return true in the TouchesBegan event to tell NControlView that you have handled the touch.
When touches moves on the screen, we'll just calculate the delta and update the image's translation:
public override bool TouchesMoved (System.Collections.Generic.IEnumerable<NGraphics.Point> points)
{
    var newPoint = points.FirstOrDefault ();
    var diff = new NGraphics.Point (newPoint.X - _startPoint.X, newPoint.Y - _startPoint.Y);

    _image.TranslationX = diff.X;
    _image.TranslationY = diff.Y;

    return true;
}
Read the current location, calculate the diff and update the image's translation. Easy?
Lastly we'll add TouchesCancelled and TouchesEnded as well:
public override bool TouchesCancelled (System.Collections.Generic.IEnumerable<NGraphics.Point> points)
{           
    return true;
}

public override bool TouchesEnded (System.Collections.Generic.IEnumerable<NGraphics.Point> points)
{           
    return true;
}
Run the app and see if you can pan the image around.

Next Step

The next step would be to create some kind of Gesture class where we can abstract away the inner logic of gestures to be able to implement zooming by pitching and double/tripple tapping to zoom in/out.

Code

Friday, August 21, 2015

Xamarin.Forms - Choosing the right architecture

One of the biggest issues with writing cross platform apps using Xamarin.Forms is that it is hard to know where to start and how to establish good patterns that are scalable enough to support the full app lifecycle from development to maintenance and new feature requests.

This blog post is based on some of the experiences I've had working with on different cross platform apps with Xamarin.Forms.

One of the biggest problems when writing cross platform code that should work on multiple platforms is that you have to choose an architecture and some patterns and stick with them to avoid ending up with code that is hard to maintain and hard to extend with new features.

The architecture and patterns you choose needs to be easy to understand and extensible enough to support future features.

Patterns
My most important principle is to build n app based on loose coupling through dependency injection and then to use the service locator, messaging bus and mvvm patterns to implement the functionality and user experience.

Combining this with unit testing enables me to develop new features quickly while keeping manual testing to a minimum.
I'm using TinyIoC for both dependency injection and as our service locator container and Xamarin.Forms' MessagingCenter as my message bus.
All dependencies on these components are abstracted through interfaces so that they can be swapped out if necessary.

Services for business logic
As a rule of thumb I always write my business logic in service classes that can be configured and injected in my view models. These classes are also testable using a unit testing framework like NUnit.
Server communication
Its common to have a dependency on a cloud service. Such a service is often exposed as a REST based API that can be accessed over HTTPS. Usually I wrap services like this into simple client classes that can communicate with the server and that can be used by my services (never use clients directly in your view models!).

To accomplish this I use the HttpClient library from Microsoft with the modernhttpclient NuGet package to get native speed and performance. For serialization I use Newtonsoft Json.
Persistence
If you have the need for local storage there are many options. I'm using SQLite.Net/SQLite.Net Async since it gives me full async/await support and a nice little ORM that can be used with c# classes. For simple storage needs you can always use a file based library.
Localization
I'm using regular resx files for localization - these work as expected under Xamarin Studio. (you might need to create the localization project in Visual Studio, I've had some issues creating them in Xamarin.Studio - this might have changed by now).
Binding it all together with Mvvm
The mvvm pattern is my default choice for wiring up the business logic and the user interface in my apps. Using ViewModels that transforms data from my services to properties and commands in my view models makes it really easy to get the user experience right. Xamarin.Forms views supports data binding to the view model's properties and commands.
There are several good Mvvm frameworks out there that can be recommended - I suggest you choose a small and simple one with source code.
Custom UX
I almost always have the need for some custom UX in all the apps I'm working on. In Xamarin.Forms you have some excellent extension points called custom renderers that act as bindings between the abstracted and native user elements.

If I have smaller needs like drawing some shapes and/or lines I usually add the NControl library to my project. It lets me create custom controls using the NGraphics abstraction layer. The NControls library is written and maintained by me, you can read more about it in a blog post here.
Analytics
To get knowledge about what your app's users are doing, you should use some kind of library for app analytics. If you can find a library that also handles exceptions and crash reports, you will thank yourself for being wise at an early stage - having stack traces and exception reports from your user's crashes helps finding solutions to strange errors. 

Remember to create an abstraction layer for tracking and error reporting so that you can plug in your own favorite library.

General tips
  • Try to keep the level of customization at a minimum - it is expensive and time consuming and adds to your maintenance costs
  • Follow your patterns - don't be tempted to write business logic in your view models or views
  • Refactor often

Know your platforms - nothing beats having the knowledge about how stuff works on the underlying platforms when it comes to building new functionality and maintaining the old features.

Tuesday, August 05, 2008

Process.WaitForExit() never completes

It's been over a year since the last time I blogged.... Sorry for that, but it's been a busy year. I've decided to pick up again and try to post some tech-related random thoughts once in a while.

Today I just wanted to share a bug I found that might be interesting for some of you.

I had a small function that started a process and waited for it to finish it's work. The code looked something like this:

Process P = new Process();
P.StartInfo.UseShellExecute = false;
P.StartInfo.RedirectStandardOutput = true;
P.StartInfo.RedirectStandardError = true;
P.StartInfo.FileName = pathToProgram;

P.StartInfo.Arguments = programArguments;
P.Start();

P.WaitForExit();
P.Close();

Since the process sometimes takes a while to finish, I wanted to be able to watch what was going on, so I decided to read from stderr/stdinput to see if something went wrong. To do this, I made sure that the RedirectStandardError properties of the process was set to true (see code above), and inserted the following line to check if the application had encountered any errors:

P.WaitForExit();
string err = P.StandardError.ReadToEnd();

P.Close();

After this code had been used for a while, I noticed that once in a while the application was hanging when performing the above process. The problem only occured when a lot of data was written to standarderror.

I debugged the problem, and found out that omitting reading from StandardError removed the problem. I thought that this seemed like a strange problem, and continued to search for a solution.

After reading the documentation, I finally came up with the information that revealed the problem. The following remark in the msdn library for the RedirectStandardError property says:

"The Process component communicates with a child process using a pipe. If a child process writes enough data to the pipe to fill the buffer, the child will block until the parent reads the data from the pipe. This can cause deadlock if your application is reading all output to standard error and standard output, for example, using the following C# code."

So instead of asking the process to fill the buffer and wait for me to empty it so that the call to WaitForExit() would block, reading the contents of the buffer (P.StandardError.ReadToEnd()) before calling WaitForExit() solved the problem and made the solution a success.

Conclusion: Read the documentation. And make sure you read the remarks.