Make Your Own Azure DevOps Build Light

|

Click to watch the YouTube video: ESP8266 Azure DevOps build light

I had a colleague at a previous work place, which had made a Philips Hue light, light up red when our bamboo build of our Xamarin Apps was failing, light up green if everything was OK. This is a pretty good way to have a visual indicator in the entire team if something is being checked in which is failing our builds. I decided to give this a go as well. However, with a Micro Controller Unit (MCU) instead, since it costs much less than a Philips Hue setup and it will require less moving parts. Philips Hue would need something else on the network telling it which color to switch to. With the MCU I would just need that.

I have had spoken to a friend about making such a light. He has been dabbling a bit himself with home automation and MCUs himself. He told me he had the right stuff for me to try out and play with. So, he have me a WeMos D1 mini, a ESP8266 based MCU, a NeoPixel ring (a.k.a. W2812) with 8, individually controllable LEDs and a little box to house it in. He himself had made a remotely controllable night lamp out of similar parts.

So my journey began into the world of MCUs and how to control the LED ring I have had gotten. Just like with mobile Apps, there are so many possibilities. A vast amount of libraries, frameworks and operating systems for all sorts of purposes. I fell over something called PlatformIO, which allows me to do everything from Visual Studio Code! Writing the code, building it, deploying it, debugging it by stepping around in the code etc. Additionally it has a library manager for libraries that other people have written. Neat!

OK! Let us get started making our own build light. Getting the status from Azure DevOps can be done through their REST API calling the Builds API. In order to do this you need to generate a Personal Access Token, which you include in the header for the GET request. You can read more about how you authenticate in the REST API docs. In my case I have 4 build agents that can run at any given time. So I want the top 4 builds and see what the status is for them. This can be done by querying the following URL: https://dev.azure.com/{organization}/{project}/_apis/build/builds?$top={top}&api-version=5.0, where {organization} is the name of your organization, {project} is the ID of the project you want to query within and {top} being the count of items I want in return. You can decrease the scope further down to specific build definitions and more. However, project and organization are required parameters.

When quering this data from Azure DevOps I quickly realized that the returned payload was huge! My ESP8266 only has a couple of MB of RAM and deserializing that huge piece of payload on the MCU was not working at all. Right! Now what? Since I cannot tell the Azure DevOps query that I am only interested in the status of the last 4 builds and not in any of the other properties I had to introduce some middleware of some sort. Just needed something quick without too many bells and whistles and to do minimal work on the MCU itself.

Azure Functions to the rescue! I quickly whipped up a simple Azure Function that gets the status of the builds and returns the following to my MCU:

  • failing if any of the last 4 builds had failed
  • running if any of the last 4 builds are running
  • finished if all the last 4 builds have completed and succeeded

So getting the last 4 builds amounts to this little piece of code:

private static async Task<IEnumerable<Build>> GetLatestBuild(int top = 4)
{
    var client = EnsureHttpClient();
        
    var url = $"https://dev.azure.com/{organization}/{project}/_apis/build/builds?$top={top}&api-version=5.0";
    
    var response = await client.GetAsync(url).ConfigureAwait(false);
    using (response)
    {
        if (response.IsSuccessStatusCode)
        {
            var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
            using (responseStream)
            using (var streamReader = new StreamReader(responseStream))
            using (var jsonReader = new JsonTextReader(streamReader))
            {
                var serializer = new JsonSerializer();
                var results = serializer.Deserialize<Builds>(jsonReader);
                
                return results.Builds.Take(4);
            }
        }
    }
    
    return null;
}

From those builds I can now check the status with a little bit of LINQ gymnastics:

var anyFailed = lastBuild.Any(b => b.Status?.ToLowerInvariant() == "failed");
var anyRunning = lastBuild.Any(b => b.Result.ToLowerInvariant() == "inprogress");
var allFinished = lastBuild.All(b => b.Result.ToLowerInvariant() == "succeeded" && b.Status?.ToLowerInvariant() == "completed");

Then return the status depending of the status of these. For these statuses I want to light up the LEDs:

  • Red if returned status is failed
  • Blue if returned status is running
  • Green if returned status is finished

OK. Now I can get a small payload for the MCU to chew on. To communicate with the Azure Function, we need a HTTP client of some sort for this I am including the WiFiClientSecure.h headers, these come with with PlatformIO when targeting the ESP8266. The code for downloading the status is a little bit more involved than you might be used to from C# or other managed languages.

First you need to connect to the host, in my case: buildlight.azurewebsites.net. The port is 443 since it is HTTPS.

WiFiClientSecure client;

bool connect() {
  Serial.print("connecting to ");
  Serial.println(host);

  bool ok = client.connect(host, httpsPort);

  Serial.println(ok ? "Connected Http" : "Connection Http Failed!");
  return ok;
}

When the connection is all good we can then send our request. The request in this case is whatever comes after the host part of the url: /api/{FunctionName}?code={apiCode} where {FunctionName} is the name of the function you are calling and {code} the code generated in the Azure Portal for calling your function. So we are sending a request that looks like:

GET {request} HTTP/1.1
Host: buildlight.azurewebsites.net
User-Agent: Cheesebulb/1.0
Connection: close
void sendRequest() {
  client.print("GET ");
  client.print(request);
  client.println(" HTTP/1.1");
  client.print("Host: ");
  client.println(host);
  client.println("User-Agent: Cheesebulb/1.0");
  client.println("Connection: close");
  client.println();
}

Now we are only interested in the body of the response and not the headers, so we skip these:

void skipHeaders() {
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == '\r') {
      Serial.println("headers received");
      break;
    }
  }
}

Skipping these is reading until you get a carriage return \r. Now reading the rest will be the body of the request.

String status = client.readStringUntil('\n');

Now that we have the status let us shine some light!

The NeoPixels work by providing 5V to a 5V pin and ground to a GND pin. They usually have a DI and DO pin, which are how you tell them which color they should light. The DI pin is the input pin and the DO pin is to chain it to more pixels. So essentially you are only communicating through one pin. For the NeoPixels this happens by sending a 800Hz digital signal on that pin, which can address each individual pixel, neat! The more pixels, then longer it takes to update the pixels. However, in my case with 9 pixels, this does not matter at all! Before you start powering your NeoPixels, I suggest you read some general best practices about these. You can damage them if not handled correctly. In my case I added a 1000µF capacitor between the 5V and ground pin to eat up any surges. This is especially importan if you power your NeoPixels using a DC adapter. However, this also helps if you need to change a lot of pixels at the same time and the amount of current drawn by the pixels suddenly changes.

To control the NeoPixels through the MCU I use the NeoPixelBus library. I have connected my NeoPixel ring to the RX pin. You might notice there are a lot of other digital output pins on the ESP8266. However, they operate at 3.3V and the NeoPixel require a 5V signal. The RX pin on my module is different and is in normal circumstances used for serial input to the MCU. However the NeoPixelBus library supports sending data on this pin using Direct Memory Access (DMA) mode. It requires more memory, but uses significantly less CPU.

In any case, on the ESP8266 to change colors on the LEDs I use the following code.

const uint8_t PixelCount = 9;
const uint8_t PixelPin = 0; // doesn't matter what you put here

NeoPixelBus<NeoGrbFeature, NeoEsp8266Dma800KbpsMethod> strip(PixelCount, PixelPin);

// define some colors
RgbColor off(0, 0, 0);
RgbColor red(255, 0, 0);
RgbColor green(0, 255, 0);
RgbColor blue(0, 0, 255);

Setting the color off a pixel is just a matter of calling:

strip.SetPixelColor(pixelIndex, color);

So setting pixel 2 to green you call: strip.SetPixelColor(1, green), the index starts at 0.

If you are interested in seeing the rest of the code I wrote for this little project you can check out this gist with all of it.

Here is a beauty shot of the device.

picture of ESP8266 with a NeoPixel ring in a box

With some basic programming knowledge and a MCU and some LEDs you can make this project on your own. I have only scratched the surface with what you can use a MCU for. I might return in the future with more fun projects. But for now, I can now see a nice visual status of my Xamarin App builds when I am at the office, without having to open a web page and check it there. In addition I have ordered a big white salt crystal which is going to rest over the box so it will act as both a build light and a piece of decoration for the office!

The cost of this little project is around $10, which is significantly less than getting started with Philips Hue or some other remotely controlled light.

Some Common ConstraintLayout Pitfalls and Mistakes

|

Recently I have been working a lot with converting nested layouts, into flattened layouts using ConstraintLayout as their root element.

If you do not know what ConstraintLayout is all about, in short terms, it is a layout that allows to position and size views in a flexible manner. It is in some ways similar to RelativeLayout. However, it gives you more power by allowing you to use percentages for sizes, chaining views together and much more.

Since I have been working with it quite a bit, I would try to highlight some common mistakes that people make when using ConstraintLayout. Also, help you avoid some pitfalls you can encounter on your. Hopefully, this will help you improve your own layouts.

Do not use match_parent for widths and heights

When you use ConstraintLayout, you are constraining your view dimensions using rules. These rules are a family of attributes that you apply on your view, starting with layout_constraint. There are various constraints, such as:

layout_constraintTop_toTopOf
layout_constraintStart_toStartOf

A common mistake is to use match_parent for layout_width and layout_height. However, what you instead should use is 0dp which means *match contstraints*, because ConstraintLayout the entire idea about this layout is that you apply constraints to size and position your views.

Instead of match_parent for width, you would instead use the following two rules:

app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"

Where parent is the encapsulating view, in this case it would be the ConstraintLayout. However, instead of writing parent it could be the ID of another view in the layout.

Now, match_parent will work and do what it does in other layouts. However, on some older versions of ConstraintsLayout it is not recommended to use it, since it is not entirely supported. Make sure you are using version 1.1 or newer.

What about wrap_content?

wrap_content is still valid for layout_width and layout_height and will be respected by ConstraintLayout.

A thing to note though. If you use wrap_content and for instance the two rules, from the match_parent example above like:

<View
    android:layout_width="wrap_content"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    ...

The view will not match the entire width of the parent. What you instead are describing here is that the view wraps its width, but should be positioned between the start and end of the parent. This is useful with couple of other attribute called layout_constraintHorizontal_bias and layout_constraintVertical_bias.

Let us say you want to postion a view at a third of the screens width. You would apply the following constraints like so:

<View
    android:layout_width="wrap_content"
    app:layout_constraintHorizontal_bias="0.3"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    ...

Pretty neat!

Broken chains

ConstraintLayout allows you to create a chain between multiple views. This can help you with things like:

  • Equally spreading chained views
+                                                      +
|    +----------+     +----------+     +----------+    |
|    |          |     |          |     |          |    |
|    |          +----->          +----->          |    |
<----+  View A  |     |  View B  |     |  View C  +---->
|    |          <-----+          <-----+          |    |
|    |          |     |          |     |          |    |
|    +----------+     +----------+     +----------+    |
+                                                      +

For this effect you need to apply layout_constraintHorizontal_chainStyle="spread" or layout_constraintHorizontal_chainStyle="spread_inside" on View A. The latter will not add spacing at the start of View A and at the end of View C. There is also a Vertical variant of this chain style.

  • Weigh views similarly to what LinearLayout can do
+                                   +
|+----------++---------------------+|
||          ||                     ||
||  Weight  +>        Weight       ||
<+   0.25   ||         0.75        +>
||          <+                     ||
||          ||                     ||
|+----------++---------------------+|
+                                   +

Apply layout_constraintHorizontal_weight or layout_constraintVertical_weight on all elements in the chain to add weights.

  • Packing together views
+                                                    +
|        +----------++----------++----------+        |
|        |          ||          ||          |        |
|        |          +>          +>          |        |
<--------+  View A  ||  View B  ||  View C  +-------->
|        |          <+          <+          |        |
|        |          ||          ||          |        |
|        +----------++----------++----------+        |
+                                                    +

Some of the variants can have bias applied to them to increase or decrease the effects on elements. Say you want to spread equally, except for the center view, which needs more space.

If you notice the small ascii drawings above, in between all of the views there is a directional arrow. In order to create a chain, you need to create constraints that position your views bi-directional. So if you have views A, B and C. A must have a constraint to a parent or another view and to B. In turn B must a constraint to A and to C. At last C must have a constraint to B and to another view or parent. If you forget to constraint bi-directinally, your chain is broken and the effect you want to achieve will not work.

So compared to RelativeLayout which does not allow to add circular positioning between views ConstraintLayout actually allows this in order to create these chains.

Setting Guideline percentages based on screen size and orientation

ConstraintLayout has a couple of virtual layouts, which are not visible on the screen at all. Guideline being one of the. Guidelines are useful for positioning start, end, top and bottom constraints on a view against it.

A guideline can be set at a fixed position using layout_constraintGuide_begin="100dp" which will position it at 100dp at the left or top of a view. Or you can alternatively use layout_constraintGuide_end="100dp", which will postion the Guideline at 100dp to the right or bottom of a view.

A guideline can also be positioned at a percentage of the width or height of the view with layout_constraintGuide_percent="0..1" where 0 is left, 0.5 is middle and 1 is right.

One cool thing is that you can use Android’s values folder structure to provide dimensions for different screen sizes for these guidelines. For instance on a phone you want to have a view fill most of the screen, while on a table you may want to to only fill half of the screen.

This is easy to set up for dimensions specified in dp. However, for the percentages, it is slightly different. Plain dimens on Android do not allow you to use floating point values.

Imagine you have a folder structure as follows:

Resources
    values
        dimens.xml
    values-sw600dp
        dimens.xml

You would put your normal phone resources into values/dimens.xml while you would put your tablet resources into values-sw600dp/dimens.xml.

Regular dimens for typed values like sp, dp, px can be added to a dimens.xml file like so:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <dimen name="guidelineBegin">16dp</dimen>
</resources>

This can be consumed for your Guideline like so:

<android.support.constraint.Guideline
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_begin="@dimen/guidelineBegin" />

The dimension added in values/dimens.xml will be substituted by the value in values-sw600dp/dimens.xml if the sw, smallest width is 600dp or more. There are other values folders you can use, which you can read more about in the Android App resources overview.

Dimensions which are float values, normally need to be followed by a typed value sp, dp, etc. In order to add percentages we can use for Guidelines, these need to be specified slightly differently like so:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <item name="fiftyPercent" type="dimen">0.5</item>
</resources>

These can still be consumed as normal dimensions like so:

<android.support.constraint.Guideline
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="@dimen/fiftyPercent" />

Should you use ConstraintLayout for all your layouts?

ConstraintLayout is very powerful and allows you to create complex layout, without a nested hell of views. Often it can be much more performant than a deeply nested layout. However, should you use it even for the simplest layouts? Obviously it really depends on the use case. However, it should be noted that all this power does not come for free. There is some overhead in calculating positions and sizes for views in your layout when using ConstraintLayout. Hence, in many cases using a flattened layout with RelativeLayout as root, may outperform ConstraintLayout. So, in the end it is a question of the complexity of the layout and how much of the power ConstraintLayout provides you want to utilize.

There are various benchmarks out there. Make sure the benchmark you read include both inflation-, layout- and measure-time. Also you may find that performance differs significantly between versions benchmarked as improvements find their way into ConstraintLayout over time. You can read more about the performance benefits on the Android Developers Blog.

Hopefully you found some of this information useful.

Xamarin and Java Processes (root)

|

I have seen this question pop up from time to time, where someone asks how to run a native process on Android. This is usually because they want to run something with root permissions, which could allow you to do various things, such as reading files you are usually not allowed. This could be reading VPN state from /data/misc/vpn/state. Blocking Advertisements by adding domains to /etc/hosts and much more.

This blog post will show you how to start a Java process and read the exit code, and the output from the process.

Note: If you want to try this yourself, you will need to have a rooted device. I like to run LineageOS on my devices. They support a large amount of popular devices. On LineageOS you can install SU by grabbing it from the Extras web page and flashing using your recovery. This is up to the reader to do.

Linux Processes

Linux is the operating system Android is based on. A standard process on Linux will use

  • stdin to read input from the console (i.e. keyboard)
  • stdout to write to the console
  • stderr to write errors to the console

These are in fact file handles to /proc/<pid>/fd/{0,1,2}, where <pid> is your process id and 0, 1, 2 are stdin, stdout, stderr respectively. You should not worry about this as this is usually handled by the OS.

A process has an exit code. 0 means everything was OK, anything else than 0 means it probably failed.

Starting a Process On Android

On Android you can start a native Process using with Java.Lang.Runtime.GetRuntime().Exec(<command>), which will return a Java.Lang.Process. Alternatively (also preferred way) if you need more control you can use Java.Lang.ProcessBuilder, which allows you to redirect stdin, stdout, stderr. Its Start() method, similarly to Exec() gives you a Java.Lang.Process. This object reflects the Linux process and has streams for stdin, stdout and stderr. These are our way into reading what the process writes out.

Xamarin has some nice helpers for us in the Java.Lang.Process they expose. Instead of blocking when we want to wait for the process to finish, they have added WaitForAsync(), which is very nice. Also the streams for stdin, stdout and stderr are nice C# Streams that you can read and write to very easily. Namely InputStream, OutputStream and ErrorStream.

var builder = new ProcessBuilder("echo", "hello");
var process = builder.Start();
var exitCode = await process.WaitForAsync();

The example above runs the echo command, which just echoes what follows after. In this case “hello”. The WaitForAsync() method returns us a Task<int> with the exit code of the process.

Keep in mind that WaitForAsync() also can throw exceptions such as IOException and SecurityException depending on what you are doing.

Reading From The Process

When we start a Process, it should be considered that we start a process and execute some commands that redirect into this Process. Meaning most of the time, when we want to read the result from the commands we run, we will actually need to read from InputStream, rather than OutputStream which would be the usual stdout. However, since our Process is the one starting other Processes, then their output stream gets redirected into our input stream.

Anyways, reading the stream is super easy.

using (var outputStreamReader = new StreamReader(process.InputStream))
{
    var output = await outputStreamReader.ReadToEndAsync();
}

Bingo! If we ran this on the echo hello process we created further up, output would be hello in this case. Neat!

Running a Process as Root

Running a process as root is super easy. Just add su and -c to the commands you execute as follows.

var builder = new ProcessBuilder("su", "-c", "echo hello");

-c means that the root process su (super user aka. root) should run a specific command. Notice that echo and hello became one argument. This is because we are redirecting into su’s stdin. Apart from that everything else stays the same.

Most Android OS’s with root, will show a dialog when root is requested.

Root dialog

Reading Large Files

Reading large files by using cat to our Process stdin might be inefficient. You could consider using the ProcessBuilder to redirect the stdin directly to a file at a destination you have better access to.

var myFile = new File("/some/path");
builder.RedirectInput(myFile);

Alternatively you could do the same with the InputStream and read it to a file.

using (var inputStream = process.InputStream)
using (var fileStream = File.Create("/some/path"))
{
    await inputStream.CopyToAsync(fileStream);
}

Convenience Helper Method

Here is a little helper method for running simple commands and getting their result.

async Task<(int exitCode, string result)> RunCommand(params string[] command)
{
    string result = null;
    var exitCode = -1;
    try
    {
        var builder = new ProcessBuilder(command);
        var process = builder.Start();
        exitCode = await process.WaitForAsync();

        if (exitCode == 0)
        {
            using (var inputStreamReader = new StreamReader(process.InputStream))
            {
                result = await inputStreamReader.ReadToEndAsync();
            }
        }
        else if (process.ErrorStream != null)
        {
            using (var errorStreamReader = new StreamReader(process.ErrorStream))
            {
                var error = await errorStreamReader.ReadToEndAsync();
                result = $"Error {error}";
            }
        }
    }
    catch (IOException ex)
    {
        result = $"Exception {ex.Message}";
    }

    return (exitCode, result);
}

Usage would be something like.

var (exitCode, result) = await RunCommand("su", "-c", "cat /data/misc/vpn/state");

This would return with the exitCode 0 if everything went well, and the string result would contain something like.

ppp0
10.0.0.42/32
0.0.0.0/0
109.226.17.2 144.117.5.51

Nice! Now you should be an expert in native processes and how to launch them from your Android App. It is up to you the reader as an exercise to figure out how OutputStream works. Enjoy!

MvvmCross Binding Target

|

I had some colleagues, who where a bit confused about what target is when creating bindings. Lets first establish what Source and Target means when talking about a binding:

  • Target, the Property on your View you are binding
  • Source, the Property in your ViewModel your View binds to

So Target will be any public property on your View, that you want to bind. Examples of these are Text, ItemsSource, SelectedItem and many more. Keep in mind that any public property when using MvvmCross on a View can be bound in OneWay mode.

To confuse things a bit, MvvmCross uses something we call TargetBinding, to create a definition of how a View can bind, when wanting modes other than OneWay. This means, if you want to bind something in TwoWay mode, there needs to exist a TargetBinding which describes how to achieve this. Internally in a TargetBinding, what usually happens is that it simply subscribes EventHandlers to relevant events, in order to notify when something has changed on the View in order to feed them back to the ViewModel. You can read more about how to create your own TargetBinding in the MvvmCross documentation about Custom Data Binding.

The Source in your ViewModel, will also need to be a public property. Similarly to every other MVVM framework, if you implement INotifyPropertyChanged on your ViewModel and remember to fire the NotifyPropertyChanged event when updating your properties, the MvvmCross binding engine will figure out how to feed the value to the Target.

Binding Expressions

OK! Now, we have established Target, Source and TargetBinding. Now, let us look a bit into MvvmCross binding expressions.

Swiss/Tibet (Android AXML, iOS string bindings etc.)

Android bindings and some string binding descriptions you can find for MvxTableViewCell and the likes use Swiss/Tibet binding expressions, which is a description language specific to MvvmCross.

<SomeView
  local:MvxBind="ViewProperty ViewModelProperty" />

SomeView in this case is the bindable object, the View. ViewProperty is the Target, ViewModelProperty is the Source.

When binding MvxTableViewCell, the bindable object, will be the cell itself. Hence, the public properties you bind to need to be on the cell.

XAML

In XAML binding expressions as per usual XAML bindings will look something as follows.

<SomeView ViewProperty="{Binding ViewModelProperty}" />

SomeView in this case is the bindable object, the View. ViewProperty is the Target, ViewModelProperty is the Source.

When using MvvmCross XAML BindingEx extensions, which uses Swiss/Tibet, where you can alternatively bind like so.

<SomeView mvx:Bi.nd="ViewProperty ViewModelProperty" />

Fluent Bindings

MvvmCross also provides something called Fluent Bindings to create type strong binding expressions in code behind. For these to work, the place you create the fluent binding, needs to be a derivative of IMvxBindingContextOwner, which means the type will have a BindingContext property, which the Fluent Binding will use to find the Source.

These Fluent Bindings usually look something as follows.

var set = this.CreateBindingSet<ContextOwnerType, ViewModelType>();
set.Bind(someView)
  .For(view => view.ViewProperty)
  .To(viewModel => viewModel.ViewModelProperty);

The ContextOwnerType is usually a MvxViewController, MvxTableViewCell or MvxActivity. This is typically the type containing the views you want to bind to.

When using the BindingSet.Bind() method, it expects the Target as argument, typically the actual View you want to bind. I have seen a couple of cases where someone tries to use the ContextOwnerType instead like so.

public class MyTableViewCell : MvxTableViewCell
{
    ...
    private UILabel _someView;

    private void CreateBindings()
    {
        this.DelayBind(() => 
        {
            var set = this.CreateBindingSet<MyTableViewCell, SomeViewModel>();
            set.Bind(this)
                .To(cell => cell._someView.Text)
                .To(viewModel => viewModel.SomeProperty);
            set.Apply();
        });
    }
}

Now, you may know that in order for MvvmCross to be able to bind something it needs to be a public property. So the above binding expression will not work, since _someView is not a public property. Another problem with that binding description is that MvxPropertyInfoTargetBindingFactory will attempt to create the binding using by trying to find the ViewProperty on the Target. This means if you use set.Bind(ViewContainer).For(vc => vc.SomeView.Prop) it will attempt to find Prop on ViewContainer and not SomeView, which ViewContainer contains.

So the conclusion is, when you use Fluent Bindings, what you use as Target needs to be the actual View you want to bind to or you will need to expose what you want to bind as a public property. So to fix the binding above you can do it by either:

  1. Exposing the Text property on _someView as a public property and bind to that
  2. Change the binding from using this to _someView

So for the first suggestion it will look like:

public class MyTableViewCell : MvxTableViewCell
{
    ...
    private UILabel _someView;

    public string Text 
    { 
        get => _someView.Text;
        set => _someView.Text = value;
    }

    private void CreateBindings()
    {
        this.DelayBind(() => 
        {
            var set = this.CreateBindingSet<MyTableViewCell, SomeViewModel>();
            set.Bind(this)
                .To(cell => cell.Text)
                .To(viewModel => viewModel.SomeProperty);
            set.Apply();
        });
    }
}

For the second suggestion it will look like:

public class MyTableViewCell : MvxTableViewCell
{
    ...
    private UILabel _someView;

    private void CreateBindings()
    {
        this.DelayBind(() => 
        {
            var set = this.CreateBindingSet<MyTableViewCell, SomeViewModel>();
            set.Bind(_someView)
                .To(view => view.Text)
                .To(viewModel => viewModel.SomeProperty);
            set.Apply();
        });
    }
}

One caveat with the first solution is. Since, the target is MvxTableViewCell we are using the default public property One-Way bindings. This means if UILabel was UITextField instead and we wanted to get changes when someone edits the UITextField in our ViewModel, this would not work, since there does not exist any TargetBinding classes, which describe this behavior for MvxTableViewCell. Hence, you will need to add your own for MvxTableViewCell or you will need to use the actual UITextField as Target.

In short terms, Target in a binding matters a lot!

Thank you for reading, if you want to read a bit more about bindings, value converters and value combiners in MvvmCross. Take a look at the official documentation about this topic.

Constraint Layout and Proguard

|

I have made an App which uses RelativeLayout and LinearLayout a lot. I wanted to convert some of these layouts to use ConstraintLayout, which in many cases simplifies a layout. Also when using LinearLayout with weights it can improve performance as well.

My App also uses Google Play Services and other libraries which increase the DEX count by a lot. Hence, I need to have multi-dex and Proguard enabled, which can complicate things a bit.

As a reader you may know, Xamarin does not provide Proguard rules which are merged with the default rules, similar to what a native Android dev might be used to from Android Studio, gradle and the ecosystem around that. So you have to add rules yourself to prevent Proguard from removing code that you are actually using.

In the case of ConstraintLayout I added the following rules in my proguard.cfg file in the app project, to have it survive Proguard’s stripping process.

# support constraint
-dontwarn android.support.constraint.**
-keep class android.support.constraint.** { *; }
-keep interface android.support.constraint.** { *; }
-keep public class android.support.constraint.R$* { *; }

These rules can be applied to other namespaces that you might find that Proguard is stripping out. Usually this will be reflected in your App, through an exception thrown at runtime telling you some class cannot be found in the Dex List.

You can read more about Proguard in the Xamarin Android documentation and how to configure it for your App.