Top

Tailwind Traders: Retrospective

Tailwind Traders: Retrospective

As we’ve been doing the last years, have developed the Xamarin scenario Microsoft used during their Connect(); 2019 event. This year the fake company is named Tailwind Traders, and it’s supposed to offer a variety of products ranging from working tools to house furniture.

Goals & premises

The apps would be made using Xamarin.Forms 4, a preview version bundling Shell, targeting Android & iOS. We, as a team, came up with a small list of goals & premises:

  • No MVVM framework, just vanilla Xamarin.Forms: we wanted everyone looking through the code understand how Xamarin.Forms works, without any MVVM framework which would hide how it’s underlying done

  • “Clean” C# and XAML codebase: anyone having a look to our code should feel everything consistent, and we introduced for the very first time something would help us keep XAML tidied up

Once and again we tried to remember the following: this demo is thought for Microsoft showing users how Xamarin.Forms must be used, and we have the high responsibility of being the ones in charge of that. We wanted to avoid small and quick solutions in favor of more stable and robust ones.

Source code organization

Although we sticked with features-alike organization until later on, we ended up having a mix of features and a more classical approach. We came into this because our Common feature was growing so much it ended up being like Desktop folder 🙂

Final directory structure. Notice how Features keep existing, accompanied by more classical folders like Controls or Effects.

MVVM born by need

Maybe it’s a personal feeling lately but don’t use to like adding a MVVM framework on top of Xamarin.Forms: I tend to think it overcomplicates things, and puts me further of the actual device. Instead, starting from scratch, let the framework evolve by itself as times goes by. We always use to start with a BaseViewModel, and its linked BaseContentPage, but that’s fine for the very first sprint.

Our framework doesn’t have a navigation service. We started navigating through App‘s top Navigation property, and it’s being sufficient for us. If we eventually needed to unit test anything related, Xamarin.Forms.Mocks would do the job.

AsyncCommand has probably being one of our fails: we created such to avoid continuous new Command(async () => { ... }) but, in the other side, we did the same in only one place. When we did the final retrospective, this was a point we wanted to improve, and I’ve already seen good implementations at Planet Xamarin for sure will have a look in our next project. Why are they needed? Because having an exception, which was somewhat common when navigating within commands, was masked because of the async void signature. Lesson learned.

TaskHelper was a great benefit. I think have implemented such in some form once and again but, the one made by our workmate Sergio Escalada, feels a good companion for the near future. Its main idea borns into protecting network calls and managing errors in a simple but elegant way. Network calls can fail because of a variety of reasons, and we may want to react in different ways with the same error. TaskHelper does just that.

protected async Task<Result<T>> TryExecuteWithLoadingIndicatorsAsync<T>(
    Task<T> operation,
    Func<Exception, Task<bool>> onError = null) =>
    await TaskHelper.Create()
        .WhenStarting(() => IsBusy = true)
        .WhenFinished(() => IsBusy = false)
        .TryWithErrorHandlingAsync(operation, onError);

Method offered by BaseViewModel which avoids worrying about IsBusy management.

Retrying through Visual States

When sprints went by we noticed something weren’t taking care of: retrials. What happens if any web call fails at the home page, would we want our users to navigate back and forth again to reload data, close the app and reopen it again maybe? We needed an option to retry.

Having such option invited us to give a try to Visual States: if anything fails we show a button for retrying, that simple. After iterating some implementations we ended up with those BaseStateAware* classes. Each VM now defines its owns states which get connected with the page. Although the solution was handy, we didn’t end up liking how verbose states are in XAML. You need a bunch of lines to express a single change of a property. It simply feels doesn’t scale on large projects, and we may look for a different approach in a future app.

XAML

For the XAML side we’ve followed this small guideline I published recently. It uses to happen us guidelines aren’t fully followed when time goes by, and we were a mix of Windows and macOS environments. We wanted something like StyleCop for XAML. And one workmate recommended us XAML Styler, which our mate forked to make it more CLI friendly. In the last sprints we turned such into a post-build script because now we wouldn’t build anything if XAML wasn’t fine. Even I enabled such as an external tool in Visual Studio for Mac, which triggers by an easy key-binding.

Uncommon shadows

While working on the home feature we noticed shadows weren’t that easy to implement. While such can be somewhat feasible through Android’s Material controls, iOS is pure flatness. However, we needed to have the same Design on both. Then, we discovered PlatformConfigurations (yeah, nothing new…), which quickly let us drop the exact same shadows wherever we wanted:

<Style x:Key="DefaultShadowfullButtonStyle"
  BasedOn="{StaticResource DefaultButtonStyle}"
  TargetType="Button">
  <Setter Property="CornerRadius">32</Setter>
  <Setter Property="HeightRequest">64</Setter>
  <Setter Property="android:Button.UseDefaultShadow">true</Setter>
  <Setter Property="ios:VisualElement.IsShadowEnabled">true</Setter>
  <Setter Property="ios:VisualElement.ShadowColor">Black</Setter>
  <Setter Property="ios:VisualElement.ShadowOffset" Value="10,10" />
  <Setter Property="ios:VisualElement.ShadowOpacity">0.2</Setter>
</Style>

Example style at Styles.xaml.

Shell goodness

Defining the global navigation is something I consider of really high importance, I see it in the same way as corridors within houses: it’s not valuable having a bedroom if there’s no corridor to get to it. I think Shell has come to help us with this specific task, among others. While we were working on this app Shell was in its early stages and most of the documentation came from the spec itself and samples at Xamarin.Forms repo —oh, and David Ortinau’s Gastropods 🙂

We thought it as another feature along its VM, and this helped us achieve custom navigations as the ones each MenuItem implied —since we didn’t find a better aproach by that time. It wasn’t easy for us, for me more specifically, understanding how Shell abstracts navigations —i.e. common one, tabbed, etc.— but it was in the end worth to rely on top of.

Other interesting topics

Feature toggling

Connect(); demos use to compound a bunch of repos which users need to clone and wake up in order to have the full scenario under their environments. This app, indeed, needs a backend from one of those. While we were developing we had one instance at Plain Concepts but, eventually, it stopped working —because our backend mates were iterating the project, for instance.

Some years ago we used to fake every single service within the app, injecting such while working in Debug and, under Release, the real ones. For this app we took one step further, thanks to Juan Antonio Cano, which proposed feature toggling: “I want a fake authentication while being on Relase”, for example. If you see the Settings will find all those toggles, and also allow to run the app without any external dependency —in terms of APIs.

Simulating network fails

Talking about fakes, we did found a good companion for simulating network losses. It helps us, while attacking fake APIs (really straightforward to achieve thanks to Refit, by the way), throwing HttpRequestExceptions each 1 out of N times, consumed as follows:

public async Task<ProductDTO> GetDetailAsync([Header("Authorization")] string authorizationHeader, string id) =>
    await FakeNetwork.ReturnAsync(FakeProducts.Fakes.FirstOrDefault(product => product.Id.ToString() == id));

Fake GetDetailAsync() call from FakeProductsAPI.

Logging

App Center was also one of the services to showcase —and our prefered option nowadays when working on our apps for customers. Here there’s no big magic, but the simple ILoggingService interface helped us switch between device’s log on Debug and App Center on Release, to get rid of mostly every Debug.WriteLine() call. Let’s say we want to move all of the logging into a text file: we just need a new service implementation and voilá.

Giving value back

During the entire project we stumbled upon needs which were satisfied by simply adding NuGet packages. In some of them we found a possible bug, or maybe a different need for our context, and tried to give back to the community through public Pull Requests (PRs). Some of them are:

  • CSharp.OperationResult: I wrote here why ended up looking for syntactic sugar like this for a better semantic while working with errors, and made a small PR to move this lib into .NET Standard 2.0

  • Sharpnado.Presentation.Forms: we went through a crash in Android with its HorizontalList which turned to be easy to fix

  • XamlStyler: already discussed above, here‘s the fork we used

CI/CD

One of the key aspects of App Center which I most enjoy is setting builds up —I just remember how it was 3-4 years ago and we’ve definitelly evolved! Appart from the post-clone script for XAML Styler, we have another one for passing Unit Tests. Our template was a verbatim from one at Microsoft where test projects were located through a regular expression, built and run through NUnit console.

One of the updates we made into such project made builds immediately fail —I think we turned the CSPROJ into the new format and upgraded the target framework, but am not 100% sure honestly. Whatever, NUnit console was no longer working for us. How life’s sometimes, the easier solution’s the correct one, and the verbose and complex script turned into one single command: dotnet test PathToYourProject/UnitTests.csproj -c Release

Tests run at App Center in one of our latest merges.

Conclussions

The entire code base is just there for you to play with. We’d like to know your thoughts after going through it, which pieces you’d do in that other way or, if any, those you find can help in future projects. All of us have learned by reading others code, and get estimulated by their findings. Tailwind Traders for Android & iOS is our result after years working with Xamarin and still see room for improvement in an already really powerful environment.

Thanks for reading,

—Marcos

Marcos Cobeña Morián

Software Development Engineer

No Comments

Post a Comment