Simple, robust workflow system for mobile (.NET PCL) based apps

We build a lot of mobile apps. And those apps are usually complex. They send stuff to servers, process things in order, handle network connectivity issues, app exit and resume etc. It’s a lot going on, and we found our apps were getting a bit too complex and we were handling the same issues every time we start a new project – there had to be a better way.

Well now there is – we made a super simple workflow called XWorkflow. It’s available as part of the (under construction) MSPL based Xamling-Core.

Xamling-Core on GitHub.
Xamling-Core Nuget Package (Xamarin Unified build coming soon – just finishing testing).
Source code from a talk on Workflow bits – with examples – on GitHub.

More examples are in Xamling-Core test suite (in the Big Windows tests, it’s a bit quicker to work in there, then run on Xamarin later).

Gist showing how to track flows in the UI.

Note on platform support

Most of Xamling-Core is portable, and indeed you can use those bits stand-alone. The actual MVVM and UI components are designed to work on Xamarin Forms, Windows Phone and Windows 8 style apps. At the moment the UI bits are working on Xamarin Forms – iOS and Windows 8 (we call that BigWindows – because soon it will be Windows 10 and yeah, anyway – no more metro…). Anyway, we’re working on Windows Phone and Android MVVM stuff very soon.

What can it do?

By our definition here, a workflow is a bunch of tasks that need to be performed in a certain order, with pass or fail results.

You configure a bunch of stages in order, compile the flow and start firing entities in to it! The core goal of this system is not a fancy feature set – but to be very simple and reliable.

Features of our workflow system are:

  • Resume if app quits unexpectadly (or indeed – expectadly).
  • Configurable retry. Try again if it fails up to your defined count. Tell the user if it fails for ever.
  • UI can restart a flow easily without having to keep track of what actually failed (just say, try again with a button). Great for generic UI’s that don’t know about your underlying model etc.
  • Great for integration with UI (so can easily report progress in some funky UI you make). Receive notifications when flow stages are updated.
  • Wait for network (stages can be configured to require network before starting).
  • Disconnected flow – like a long running server process that sends a push when completed, or UI based stages that need user input.
  • It’s hardy – crashes will not affect it.
  • It’s serialisable – you can send WF state to the server for diagnosis.
  • Stages are pass or fail, and each stage can have friendly UI text for each result.
  • You can merge stages in to other stages – so merge your save item to server flow in to a create new item flow.
  • It’s portable. This will work on any .NET platform that supports Portable Class Libraries. It’s designed for mobile, but we’ve used it all over the place!
  • Much more!

Note on Entities

This workflow system works only with entities in the Xamling-Core entity system. It’s pretty easy – implement the interface IEntity and then call the .Set() extension method on them… they will then be available in the Xamling-Core entity management system – that’s another entire post in itself 🙂

See examples in the sample talk code here for entity manager usage. It’s pretty simple. I’ll do a better post on it another time.

A simple usage of this system might be to upload data to a server and wait for result (even if network doesnt come back until after the next app launch!).

Configuration

The flow stage set up process is fluent.

 
await _hub
      .AddFlow(Workflows.DownloadEntity, "Download")
      .AddStage(
            EntryDownloadStages.DownloadFromServer,
            "DownloadingTripodEntry",
            "FailedToDownloadEntry",
            _entryProcessService.RefreshEntryFromServer,
            false,
            true,
            2
          )
                .Complete();

Here we configure a new flow. This one downloads something from the server (when it calls _entityProcessService.RefreshEntryFromServer.

First you add a new flow giving it a flowId and a friendly name. You can use this flowId later to access it from other places in your code.

 
public XFlow AddFlow(string flowId, string friendlyName)

Next you configure the steps. Give each step a friendly id, a success UI text, fail UI text, the method to run, is disconnected, requires network, number of retries and a special fail function if needed.

We often pass in strings that the UI will use to look up the localised version of the text.

 
 public XFlow AddStage(string stageId, string processingText, string failText, Func<Guid, Task<XStageResult>> function,
            bool isDisconnectedProcess = false, bool requiresNetwork = false, int retries = 0, Func<Guid, Task<XStageResult>> failFunction = null)

You can do other stuff like merge flows

 
.Merge(_hub.GetFlow(Workflows.DownloadTripodForEntity)

Remember to call .Complete() when you’re done!

Our tip is to make your flows small, and merge them in to larger flows. Compose them…

Starting a flow

Getting an entity in to the flow is simple using hte extension method. Of course you can do it the long way (check out EntityDataExtensions.cs)

 
using XamlingCore.Portable.Data.Extensions
...
await thisEntity.StartWorkflow(Workflows.ProcessNotificationMessage);

Off it goes!

As I said the Workflow systems uses the EntityManager system (which is another part of Xamling-Core). The core principal of that system is that once an entity is set, it always gets the same item given the same Id. It’s also serialized to the local storage, so you can get it later even if the app restarts. So the WF system uses only Ids to transport data around… making the entire thing super simple internally.

Call backs to your code look something like this

public async Task RefreshEntryFromServer(Guid entryId)
{
var entry = await _entryEntityManager.Get(entryId);
if (entry == null)
{
return new XStageResult(false, entryId);
}
---

As you can see – it’s up to you to get the entity and ensure it’s good to go.

A successful result would be returned like this

 
return new XStageResult(true, entryId);

You can control a fair bit from those XStageResult returns, you can even change the Id of the entity… just in case the server gave you a new Id when it did it’s bit.

public XStageResult(bool isSuccess, Guid id, string extraText = null, bool completeNow = false, string exception = null)

It obviously say if the operation was successful or not, can provide some more text for the UI, can terminate the flow early (successful result, but terminate now) and provide any exception details – either for the UI or to send up to the server as part of diagnosis (keeping in mind the entire workflow state is serialised).

Work in progress

We’re still just adding in some features and making it better, but so far it’s pretty stable and seems to be working well… It’s being used in the Project Tripod for iPhone code to upload images to the sever, wait for processing, then download the result again after a push notification.

If you need some help getting it going or have any other ideas and feedback please let me know! Remember to check through the examples at the top and to keep an eye out for more posts coming soon.

Jordan.

Leave a comment