<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Marius Muntean</title>
    <description>The latest articles on DEV Community by Marius Muntean (@mariusmuntean).</description>
    <link>https://dev.to/mariusmuntean</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F157893%2F4532c8b1-085e-4dfa-b813-401951094006.jpeg</url>
      <title>DEV Community: Marius Muntean</title>
      <link>https://dev.to/mariusmuntean</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mariusmuntean"/>
    <language>en</language>
    <item>
      <title>Operationalize TensorFlow Models With ML.NET</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Thu, 17 Aug 2023 22:50:35 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/operationalize-tensorflow-models-with-mlnet-3m76</link>
      <guid>https://dev.to/mariusmuntean/operationalize-tensorflow-models-with-mlnet-3m76</guid>
      <description>&lt;p&gt;Let’s have a look at how to use a pre-trained TensorFlow model with ML.NET to make landmark predictions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the Model
&lt;/h2&gt;

&lt;p&gt;First we’re going to go ahead and pick a pre-trained model. There are multiple good sources for pre-trained models like &lt;a href="https://huggingface.co/models"&gt;Hugging Face&lt;/a&gt; and &lt;a href="https://tfhub.dev/s?subtype=module,placeholder"&gt;tfhub&lt;/a&gt;. We’re going to use a model from tfhub that predicts north American landmarks from images — &lt;a href="https://tfhub.dev/google/on_device_vision/classifier/landmarks_classifier_north_america_V1/1"&gt;https://tfhub.dev/google/on_device_vision/classifier/landmarks_classifier_north_america_V1/1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Transform and Optimize the Model
&lt;/h2&gt;

&lt;p&gt;Theoretically we could start using the model as it is, but we can do better. The TensorFlow model isn’t easy to use on all possible platforms like Linux, macOS and Windows, and on all CPU architectures like ARM64.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://onnx.ai/"&gt;ONNX&lt;/a&gt; is a format for representing machine learning models in a portable way. Additionally, ONNX models can be easily optimized and thus become smaller and faster.&lt;/p&gt;

&lt;p&gt;The easiest way to transform the downloaded TensorFlow model to an ONNX model is to use the tool tf2onnx from &lt;a href="https://github.com/onnx/tensorflow-onnx"&gt;https://github.com/onnx/tensorflow-onnx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the instructions to install it (use a &lt;a href="http://mcr.microsoft.com/devcontainers/python:1-3.10-bullseye"&gt;dev container with python 3.10&lt;/a&gt; to keep your machine clean) and then run this command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m tf2onnx.convert --opset 16 --tflite lite-model_on_device_vision_classifier_landmarks_classifier_north_america_V1_1.tflite --output lite-model_on_device_vision_classifier_landmarks_classifier_north_america_V1_1.onnx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should now have a file called &lt;em&gt;lite-model_on_device_vision_classifier_landmarks_classifier_north_america_V1_1.onnx&lt;/em&gt; that contains the optimized ONNX model. The file size shrank from 50.9MB to 42.7MB. Nice!&lt;/p&gt;

&lt;p&gt;If you happen to start with an ONNX model that you still want to optimize, then you can use the official ONNX optimizer tool &lt;a href="https://github.com/onnx/optimizer"&gt;https://github.com/onnx/optimizer&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the ONNX Model Available to ML.NET
&lt;/h2&gt;

&lt;p&gt;In this step we’re telling ML.NET what the inputs and outputs of the model are and we’re packaging the model in a way that ML.NET can work with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inputs and Outputs
&lt;/h3&gt;

&lt;p&gt;The documentation already tells us that the “Inputs are expected to be 3-channel RGB color images of size 321 x 321, scaled to [0, 1]”, and that the output is “A vector of 99424 similarity scores”.&lt;/p&gt;

&lt;p&gt;We need to find out the exact input and output tensor names. A tool like &lt;a href="https://github.com/lutzroeder/netron"&gt;Netron&lt;/a&gt; makes this super easy. Open the original .tflite and/or the ONNX model in Netron and click the &lt;em&gt;Model Properties&lt;/em&gt; button in the lower left corner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u4mEeWw3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/3904/1%2AwTUaFCPSLOVlhx27QHhTfA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u4mEeWw3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/3904/1%2AwTUaFCPSLOVlhx27QHhTfA.png" alt="Netron shows the model inputs and outputs" width="800" height="926"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For our model the input is called &lt;strong&gt;&lt;em&gt;uint8_image_input&lt;/em&gt;&lt;/strong&gt; and the output is called &lt;strong&gt;&lt;em&gt;transpose_1&lt;/em&gt;&lt;/strong&gt;. We’ll make a note of those.&lt;/p&gt;

&lt;h3&gt;
  
  
  Package Model for ML.NET
&lt;/h3&gt;

&lt;p&gt;Create a new console application, add these package references and restore them&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;PackageReference Include="Microsoft.ML" Version="2.0.1" /&amp;gt;
&amp;lt;PackageReference Include="Microsoft.ML.ImageAnalytics" Version="2.0.1" /&amp;gt;
&amp;lt;PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.15.1" /&amp;gt;
&amp;lt;PackageReference Include="Microsoft.ML.OnnxTransformer" Version="2.0.1" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, to make our life easier and the code tidier, let’s define a few constants and types for the model input and output.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static class LandmarkModelSettings
{
    public const string OnnxModelName = "lite-model_on_device_vision_classifier_landmarks_classifier_north_america_V1_1.onnx";
    public const string Input = "uint8_image_input";
    public const string Output = "transpose_1";

    public const string MlNetModelFileName = "landmark_classifier_onnx.zip";
    public const string LabelFileName = "landmarks_classifier_north_america_V1_label_map.csv";
}

public class LandmarkInput
{
    public const int ImageWidth = 321;
    public const int ImageHeight = 321;

    public LandmarkInput(Stream imagesStream)
    {
        Image = MLImage.CreateFromStream(imagesStream);
    }

    [ImageType(width: ImageWidth, height: ImageHeight)]
    public MLImage Image { get; }
}

public class LandmarkOutput
{
    [ColumnName(LandmarkModelSettings.Output)]
    public float[] Prediction { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The code is relatively self explanatory. What I’d like to point out is that on the tfhub page, where we’ve downloaded the model from, there’s also a .csv file with the labels for each prediction result. I saved it locally with the name from the constant &lt;strong&gt;&lt;em&gt;LabelFileName&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now we’re ready to describe the inputs and outputs from ML.NET, we’re also loading the ONNX model and saving it in ML.NET’s own format&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Configure ML model
var mlCtx = new MLContext();

var pipeline = mlCtx
    .Transforms
    // Adjust the image to the required model input size
    .ResizeImages(
        inputColumnName: nameof(LandmarkInput.Image),
        imageWidth: LandmarkInput.ImageWidth,
        imageHeight: LandmarkInput.ImageHeight,
        outputColumnName: "resized"
    )
    // Extract the pixels form the image as a 1D float array, but keep them in the same order as they appear in the image.
    .Append(mlCtx.Transforms.ExtractPixels(
        inputColumnName: "resized",
        interleavePixelColors: true,
        outputAsFloatArray: false,
        outputColumnName: LandmarkModelSettings.Input)
    )
    // Perform the estimation
    .Append(mlCtx.Transforms.ApplyOnnxModel(
            modelFile: "./" + LandmarkModelSettings.OnnxModelName,
            inputColumnName: LandmarkModelSettings.Input,
            outputColumnName: LandmarkModelSettings.Output
        )
    );

// Save ml model
var transformer = pipeline.Fit(mlCtx.Data.LoadFromEnumerable(new List&amp;lt;LandmarkInput&amp;gt;()));

mlCtx.Model.Save(transformer, null, LandmarkModelSettings.MlNetModelFileName);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let’s walk through the code.&lt;/p&gt;

&lt;p&gt;First we’re telling ML.NET to resize any image it receives to the size that the downloaded model expects; in this case 321x321 pixels. The resized image should be placed in the “resized” column. From the “resized” column we’re extracting the image pixels into a 1D array of floats and we’re outputting that data into the column &lt;em&gt;**transpose_1 *&lt;/em&gt;*because that’s what the model expects. In the last step we’re invoking the model to make the prediction.&lt;/p&gt;

&lt;p&gt;Finally, the model is saved with the name &lt;strong&gt;&lt;em&gt;landmark_classifier_onnx.zip&lt;/em&gt;&lt;/strong&gt;. It now shrank even more to 39.6MB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Load the ML.NET Model and Make a Prediction
&lt;/h3&gt;

&lt;p&gt;Before we continue, we should make sure that the ML.NET model actually works as expected. For this we’re loading the model from the &lt;em&gt;**landmark_classifier_onnx.zip *&lt;/em&gt;*file and we’re feeding it a .jpg file with the statue of liberty. The prediction should contain multiple entries, but the one with the highest probability should be our statue of liberty.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Load ml model
var mlCtx2 = new MLContext();
var loadedModel = mlCtx2.Model.Load(LandmarkModelSettings.MlNetModelFileName, out var _);
var predictionEngine = mlCtx2.Model.CreatePredictionEngine&amp;lt;LandmarkInput, LandmarkOutput&amp;gt;(loadedModel);

// Predict 
var sw = new Stopwatch();
sw.Start();
await using var imagesStream = File.Open("Landmarks/Statue_of_Liberty_7.jpg", FileMode.Open);
var prediction = predictionEngine.Predict(new LandmarkInput(imagesStream));
Console.WriteLine($"Prediction took: {sw.ElapsedMilliseconds}ms");

// Labels start from the second line and each contains the 0-based index, a comma and a name.
var labels = await File.ReadAllLinesAsync(LandmarkModelSettings.LabelFileName)
    .ContinueWith(lineTask =&amp;gt;
    {
        var lines = lineTask.Result;
        return lines
            .Skip(1)
            .Select(line =&amp;gt; line.Split(",").Last())
            .ToArray();
    });

// Merge the prediction array with the labels. Produce tuples of landmark name and its probability.
var predictions = prediction.Prediction
        .Select((val, index) =&amp;gt; (index, probabiliy: val))
        .Where(pair =&amp;gt; pair.probabiliy &amp;gt; 0.55f)
        .Select(pair =&amp;gt; (name: labels[pair.index], pair.probabiliy))
        .GroupBy(pair =&amp;gt; pair.name)
        .Select(group =&amp;gt; (name: group.Key, probability: group.Select((p) =&amp;gt; p.probabiliy).Max()))
        .OrderByDescending(pair =&amp;gt; pair.probability)
    ;

// Output
var predictionsString = string.Join(Environment.NewLine, predictions.Select(pair =&amp;gt; $"name: {pair.name}, probability: {pair.probability}"));
Console.WriteLine(string.Join(Environment.NewLine, predictionsString));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you might have noticed, I had a picture named &lt;strong&gt;&lt;em&gt;Statue_of_Liberty_7.jpg&lt;/em&gt;&lt;/strong&gt; in my &lt;strong&gt;&lt;em&gt;Landmarks&lt;/em&gt;&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;What’s custom for this model is that the prediction contains duplicates, i.e. the output array of floats contains multiple entries for the same landmark. The tfhub documentation page says to just use that prediction of a landmark that has the highest probability. Depending on the model that you chose, you might not need to do this and simply assigning a label to each position from the output might be enough.&lt;/p&gt;

&lt;p&gt;On my M1 Pro Macbook Pro the output looks like this&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prediction took: 108ms
name: Liberty Island, probability: 0,9176943
name: New York Harbor, probability: 0,798547
name: Liberty State Park, probability: 0,7981717
name: The Terminal Tower Residences, probability: 0,6972256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Congrats 🎉, you’re ready to use the model! Read on if you want to integrate it in an AspNet.Core application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expose it as a Web API
&lt;/h2&gt;

&lt;p&gt;Create a new AspNet.Core Web API project and add the following package references&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.9"/&amp;gt;
&amp;lt;PackageReference Include="Microsoft.Extensions.ML" Version="2.0.1" /&amp;gt;
&amp;lt;PackageReference Include="Microsoft.ML" Version="2.0.1" /&amp;gt;
&amp;lt;PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.15.1" /&amp;gt;
&amp;lt;PackageReference Include="Microsoft.ML.OnnxTransformer" Version="2.0.1" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The last ones are necessary only at runtime. If you skip them you’ll get errors like&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.&lt;br&gt;
 — -&amp;gt; System.IO.FileNotFoundException: Could not load file or assembly ‘Microsoft.ML.OnnxTransformer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51’. The system cannot find the file specified.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;File name: ‘Microsoft.ML.OnnxTransformer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51’&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An unhandled exception has occurred while executing the request.&lt;br&gt;
 System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.&lt;br&gt;
 — -&amp;gt; System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.&lt;br&gt;
 — -&amp;gt; System.TypeInitializationException: The type initializer for ‘Microsoft.ML.OnnxRuntime.NativeMethods’ threw an exception.&lt;br&gt;
 — -&amp;gt; System.DllNotFoundException: Unable to load shared library ‘onnxruntime’ or one of its dependencies. In order to help diagnose loading problems, consider setting the DYLD_PRINT_LIBRARIES environment variable: &lt;br&gt;
 dlopen(&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You might expect that loading the model looks exactly like we did before when we made our first prediction, but Microsoft recommends something different. Since the &lt;strong&gt;&lt;em&gt;PredictionEngine&lt;/em&gt;&lt;/strong&gt; isn’t thread safe and expensive to create, we should use a &lt;em&gt;**PredictionEnginePool *&lt;/em&gt;*—&lt;a href="https://learn.microsoft.com/en-us/dotnet/machine-learning/how-to-guides/serve-model-web-api-ml-net#register-predictionenginepool-for-use-in-the-application"&gt;link to the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Make sure to have the constants, input and output types available and add this line in to the Program.cs&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services
    .AddPredictionEnginePool&amp;lt;LandmarkInput, LandmarkOutput&amp;gt;()
    .FromFile(Path.Combine(
        Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
        LandmarkModelSettings.MlNetModelFileName
    ));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It add a &lt;strong&gt;&lt;em&gt;PredictionEnginePool&lt;/em&gt;&lt;/strong&gt; for our input and output types for the ML.NET model from the specified path.&lt;/p&gt;

&lt;p&gt;You can organize your code as is best for your project, but I added a dedicated singleton service that loads the labels and sorts them&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal class NorthAmericanLabelProvider : INorthAmericanLabelProvider
{
    private Lazy&amp;lt;string[]&amp;gt;? _lazyLabels;

    public string[] GetLabels()
    {
        _lazyLabels ??= new Lazy&amp;lt;string[]&amp;gt;(() =&amp;gt;
        {
            var labelFilePath = Path.Combine(
                Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
                LandmarkModelSettings.LabelFileName);
            var labelLines = File.ReadAllLines(labelFilePath);
            return labelLines.Skip(1)
                .Select(line =&amp;gt; line.Split(","))
                .Select(lineTokens =&amp;gt; (Index: int.Parse(lineTokens[0]), LandmarkName: lineTokens[1]))
                .OrderBy(tuple =&amp;gt; tuple.Index)
                .Select(tuple =&amp;gt; tuple.LandmarkName)
                .ToArray();
        });

        return _lazyLabels.Value;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It reads the content of the file, parses and sorts it only once using a Lazy.&lt;/p&gt;

&lt;p&gt;Next, I created a service called &lt;strong&gt;&lt;em&gt;NorthAmericanLandmarkPredictor&lt;/em&gt;&lt;/strong&gt; that does the actual prediction. It makes use of the &lt;strong&gt;&lt;em&gt;PredictionEnginePool&lt;/em&gt;&lt;/strong&gt; that we’re registered earlier and of the &lt;strong&gt;&lt;em&gt;INorthAmericanLabelProvider&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal class NorthAmericanLandmarkPredictor : INorthAmericanLandmarkPredictor
{
    private readonly PredictionEnginePool&amp;lt;LandmarkInput, LandmarkOutput&amp;gt; _predictionEnginePool;
    private readonly INorthAmericanLabelProvider _northAmericanLabelProvider;

    public NorthAmericanLandmarkPredictor(PredictionEnginePool&amp;lt;LandmarkInput, LandmarkOutput&amp;gt; predictionEnginePool, INorthAmericanLabelProvider northAmericanLabelProvider)
    {
        _predictionEnginePool = predictionEnginePool;
        _northAmericanLabelProvider = northAmericanLabelProvider;
    }

    public List&amp;lt;LandmarkPrediction&amp;gt; PredictLandmark(Stream imageStream)
    {
        var labels = _northAmericanLabelProvider.GetLabels();

        // Make prediction
        // Post process prediction - the output contains duplicates, so we should group by label and take the entry with the highest probability.
        // Docs - https://tfhub.dev/google/on_device_vision/classifier/landmarks_classifier_north_america_V1/1
        var landmarkOutput = _predictionEnginePool.Predict(new LandmarkInput(imageStream));
        return landmarkOutput.Prediction
            .Zip(labels, (probability, landmarkName) =&amp;gt; (LandmarkName: landmarkName, Probability: probability))
            .GroupBy(tuple =&amp;gt; tuple.LandmarkName)
            .Select(group =&amp;gt; new LandmarkPrediction(
                group.Key,
                group.MaxBy(tuple =&amp;gt; tuple.Probability).Probability
            ))
            .OrderByDescending(prediction =&amp;gt; prediction.Probability)
            .ToList();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, in a controller we can inject the &lt;strong&gt;&lt;em&gt;INorthAmericanLandmarkPredictor&lt;/em&gt;&lt;/strong&gt; and make predictions from uploaded images&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ApiController]
[Route("[controller]")]
public class LandmarkPredictionController : ControllerBase
{
    private readonly INorthAmericanLandmarkPredictor _northAmericanLandmarkPredictor;

    public LandmarkPredictionController(INorthAmericanLandmarkPredictor northAmericanLandmarkPredictor)
    {
        _northAmericanLandmarkPredictor = northAmericanLandmarkPredictor;
    }

    [HttpPost("NorthAmerica")]
    public async Task&amp;lt;List&amp;lt;LandmarkPrediction&amp;gt;&amp;gt; Get(IFormFile image)
    {
        var prediction = _northAmericanLandmarkPredictor.PredictLandmark(image.OpenReadStream());
        return prediction;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the built in Swagger UI this looks like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uYizcmoP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/5816/1%2AoxFPeZ-vnVJmUYyjP4ND6Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uYizcmoP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/5816/1%2AoxFPeZ-vnVJmUYyjP4ND6Q.png" alt="Statue of Liberty Prediction Results" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That’s all folks!&lt;/p&gt;

&lt;p&gt;Now you’re ready to operationalize many different ml models with ML.NET and expose them in a nice Web API.&lt;/p&gt;

&lt;p&gt;You can find the whole source code at &lt;a href="https://github.com/mariusmuntean/Operationalize.ML.NET"&gt;https://github.com/mariusmuntean/Operationalize.ML.NET&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is under MIT license so you’re free to use it at your heart’s content.&lt;/p&gt;

&lt;p&gt;Hit me up on Twitter/X if you’d like to buy me a coffee 😁&lt;/p&gt;

</description>
      <category>tensorflow</category>
      <category>onnx</category>
      <category>mlnet</category>
      <category>aspnetcore</category>
    </item>
    <item>
      <title>Structure that MAUI App!</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Mon, 27 Feb 2023 20:38:41 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/structure-that-maui-app-3hdl</link>
      <guid>https://dev.to/mariusmuntean/structure-that-maui-app-3hdl</guid>
      <description>&lt;h2&gt;
  
  
  Premise
&lt;/h2&gt;

&lt;p&gt;I recently found myself giving DotNet MAUI a whirl. The goal was to build a desktop app that connects to RabbitMQ, then sends and receives messages that were protobuf serialized. I got to the goal fast and efficiently enough.&lt;/p&gt;

&lt;p&gt;Here’s the source code &lt;a href="https://github.com/mariusmuntean/ProtoRabbit" rel="noopener noreferrer"&gt;https://github.com/mariusmuntean/ProtoRabbit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is how it looks like on Windows&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ke3demw8jdn7vem0g4i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ke3demw8jdn7vem0g4i.png" alt="ProtoRabbit." width="800" height="969"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;What I didn’t like was that the main page of the app was a mess, tons of little UI elements, databound to the viewmodel. The viewmodel itself wasn’t much better. It had code to create connections, handle sending and receiving messages and so on. “There must be a way to structure this” I thought, and there was. Read on to find out what I settled on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step #1 — Custom controls
&lt;/h2&gt;

&lt;p&gt;I first split up the code in three &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/handlers/create?view=net-maui-7.0" rel="noopener noreferrer"&gt;custom controls&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;ConnectionControl&lt;/em&gt; — Creates a new connection to RabbitMQ.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;SendMessageControl&lt;/em&gt; — Sends messages to an exchange.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;SubscribeAndReceiveMessageControl&lt;/em&gt; — Displays messages.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This cleaned up the main page so much that it only contained a &lt;em&gt;Grid&lt;/em&gt; layout and the three custom controls. Nice and simple, have a look.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vvv3oyq7x6wpup3ochd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vvv3oyq7x6wpup3ochd.png" alt="MainPage and three custom controls." width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step #2 — Nested ViewModels
&lt;/h2&gt;

&lt;p&gt;Now it was the &lt;em&gt;MainPageViewModel’s&lt;/em&gt; turn. I almost never used nested viewmodels in the past, except for displaying collections of objects. But this is exactly what helped here to tame the complexity.&lt;/p&gt;

&lt;p&gt;Basically, each custom control got a dedicated viewmodel and the &lt;em&gt;MainPageViewModel&lt;/em&gt; is their parent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;ConnectionViewModel&lt;/em&gt; — Established and stops the RabbitMQ connection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;SendMessageViewModel&lt;/em&gt; — Defines what message templates can be used and sends protobuf serialized messages to RabbitMQ.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;SubscribeAndReceiveMessageViewModel&lt;/em&gt; — Manages “subscriptions” to a certain message type from a certain Exchange in RabbitMQ.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how the &lt;em&gt;MainPageViewModel&lt;/em&gt; looks like after extracting the three sub-viewmodels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feu2i9xql5bzg8m4c0wuj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feu2i9xql5bzg8m4c0wuj.png" alt="MainPageViewModel and three “child” viewmodels." width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fin
&lt;/h2&gt;

&lt;p&gt;That’s all I had to say about this topic this time.&lt;/p&gt;

&lt;p&gt;As always feel free to browse my GitHub repo and play with the code. It’s all MIT licensed, so go nuts with it.&lt;/p&gt;

&lt;p&gt;If you enjoyed this post, follow me to stay up to date.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Redis as a Database — Data Migration With RedisOM, RedisGears and Redlock</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Sun, 22 Jan 2023 18:28:02 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/redis-as-a-database-data-migration-with-redisom-redisgears-and-redlock-37o6</link>
      <guid>https://dev.to/mariusmuntean/redis-as-a-database-data-migration-with-redisom-redisgears-and-redlock-37o6</guid>
      <description>&lt;p&gt;Systems evolve and so does their data. Instead of making our code endlessly complex just to be able to work with old data models and new ones, we can simply &lt;em&gt;massage&lt;/em&gt; the existing data into a new shape to better fit the newest domain models. This is the definition of data migration I’m going with today.&lt;/p&gt;

&lt;p&gt;In this article I’m using &lt;a href="https://github.com/mariusmuntean/Visualizer" rel="noopener noreferrer"&gt;Visualizer&lt;/a&gt;, my pet project, to show how to update existing data to conform to a new data models.&lt;/p&gt;

&lt;p&gt;I’m going to make use of another Redis module, called &lt;a href="https://redis.com/modules/redis-gears/" rel="noopener noreferrer"&gt;RedisGears&lt;/a&gt; to perform the data transformation itself. Another cool thing that I’m going to use is &lt;a href="https://redis.io/docs/manual/patterns/distributed-locks/" rel="noopener noreferrer"&gt;Redlock&lt;/a&gt;, a distributed lock algorithm for Redis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I talked about &lt;strong&gt;Visualizer&lt;/strong&gt; in my &lt;em&gt;Redis as a Database&lt;/em&gt; series, so I encourage you to read the other articles as well. The gist of it is that &lt;strong&gt;Visualizer&lt;/strong&gt; ingests tweets directly from Twitter, stores them in Redis and offers a GraphQL API to search and retrieve them and has multiple GraphQL subscriptions to get live updates.&lt;/p&gt;

&lt;p&gt;You don’t really need read up on &lt;strong&gt;Visualizer&lt;/strong&gt; to understand this article, but if you’re enjoying this one, then you‘d enjoying the others as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Data Model
&lt;/h2&gt;

&lt;p&gt;We’re not going to look at the entire data model of the application. Instead, we’re going to look at how a single tweet is modeled and stored.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
You may notice the &lt;em&gt;Document&lt;/em&gt; attribute. That’s because I’m using &lt;a href="https://developer.redis.com/develop/dotnet/redis-om-dotnet/getting-started/" rel="noopener noreferrer"&gt;Redis OM&lt;/a&gt; to store and retrieve data. The experience is similar to using Entity Framework.

&lt;p&gt;More importantly, the class defines properties for a unique tweet ID; the actual text of the tweet; the username of the author and the tweet language.&lt;/p&gt;

&lt;p&gt;All these properties have either the &lt;em&gt;Indexed&lt;/em&gt; or the &lt;em&gt;Searchable&lt;/em&gt; attributes, making the tweets easily and efficiently searchable in Redis.&lt;/p&gt;

&lt;p&gt;This is how it looks in Redis&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zms2cwnx17kx9j26i0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zms2cwnx17kx9j26i0r.png" alt="TweetModel data in RedisInsight-v2" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Target Data Model
&lt;/h2&gt;

&lt;p&gt;My intention is to extend the data model with a property that contains the ‘sentiment’ of the tweet. Think machine learning based sentiment analysis.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Please note the new property called &lt;em&gt;Sentiment&lt;/em&gt;. It is indexed as well, for performant searching and contains a hint for Redis OM (which uses &lt;a href="https://www.nuget.org/packages/Newtonsoft.Json/" rel="noopener noreferrer"&gt;Newtonsoft.JSON&lt;/a&gt; under the hood) how serialization should be handled.

&lt;p&gt;The &lt;em&gt;TweetSentiment&lt;/em&gt; itself is just an enum.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Migration Strategy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Schema
&lt;/h3&gt;

&lt;p&gt;So now the &lt;em&gt;TweetModel&lt;/em&gt; has the &lt;em&gt;Sentiment&lt;/em&gt; property. We need to tell Redis about this.&lt;/p&gt;

&lt;p&gt;The way I’m doing it in &lt;strong&gt;Visualizer&lt;/strong&gt; is by droping the existing Redis OM (or rather &lt;a href="https://redis.io/docs/stack/search/" rel="noopener noreferrer"&gt;RediSearch&lt;/a&gt;) index and recreating an updated version of it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
I’m making use of a &lt;em&gt;IHostedService&lt;/em&gt; from Asp.Net, which runs at startup, but before the application accepts network requests.

&lt;p&gt;The first part is done. Our data model is now aware of the &lt;em&gt;Sentiment *property and so is the RediSearch module. Whenever a new tweet is added, its *Text&lt;/em&gt; and &lt;em&gt;Lang&lt;/em&gt; properties can be passed to an ML model to perform a sentiment analysis and the result can be stored in the &lt;em&gt;Sentiment&lt;/em&gt; property. Thanks to the updated index, queries that include the &lt;em&gt;Sentiment&lt;/em&gt; should be blazindly fast &lt;a href="https://www.google.com/search?q=primeagen+blazingly+fast" rel="noopener noreferrer"&gt;😉&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data
&lt;/h3&gt;

&lt;p&gt;At this point, &lt;strong&gt;new&lt;/strong&gt; tweets are stored together with their estimated &lt;em&gt;Sentiment&lt;/em&gt;. They’re also searchable. But what about those tens of thousands of tweets that are already in Redis? Those were stored before the new fancy sentiment analysis feature was released, and thus have no value in their &lt;em&gt;Sentiment&lt;/em&gt; property.&lt;/p&gt;

&lt;p&gt;We might try to programatically load every tweet that has no &lt;em&gt;Sentiment&lt;/em&gt; value, then compute and store it. This would be slow though and probably increase the monthly cloud bill due to all the data that needs to be shoved back and forth. If only there were a way to do this more efficiently.&lt;/p&gt;

&lt;p&gt;Here is where &lt;a href="https://oss.redis.com/redisgears/" rel="noopener noreferrer"&gt;RedisGears&lt;/a&gt; comes into play. It allows us to run arbitrary Python code (no C# yet 😔) directly on Redis.&lt;/p&gt;

&lt;p&gt;My approach is inspired from &lt;a href="https://dbup.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;DbUp&lt;/a&gt;. In my application I’m going to have a folder where I place all my data migration scripts. The scripts are numbered such that the order of their execution is clear. Whenever a script is executed successfully, its name is permanently recorded. Scripts are only executed when the application starts up and only those scripts are executed whose name was not previously recorded.&lt;/p&gt;

&lt;p&gt;I think that the data should be migrated before the schema migration is performed. So, right before starting the Asp.Net application I’m triggering the data migration like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
The extension method is there just to keep the &lt;em&gt;Program.cs *clean and looks like this&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Instead of doing everything in the extension method, I’m resolving *IDataMigrationService&lt;/em&gt; to benefit from dependency injection&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Instead of pasting the entire implementation of the &lt;em&gt;IDataMigrationService&lt;/em&gt; here, I want to ease you into it.

&lt;p&gt;First have a look at the data migration project and the folder with data migration scripts&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vdpa3ihkbvafqdri6zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vdpa3ihkbvafqdri6zm.png" alt="Data migration project" width="796" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the extension method class, the numbered scripts and the DataMigratorService class.&lt;/p&gt;

&lt;p&gt;Now, the &lt;em&gt;DataMigratorService&lt;/em&gt; needs to determine which of the available scripts have been executed already and which are new ones. To get the list of available scripts it just looks into the &lt;em&gt;DataMigration&lt;/em&gt; directory and enumerates the files. They all have their build action set to &lt;em&gt;Content&lt;/em&gt;. The already executed scripts can be found in a set (data structure) in Redis, under the &lt;em&gt;PerformedMigrationsKey&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Then, it returns the new scripts, sorted alphanumerically in an ascending order.

&lt;p&gt;Next, if any new scripts were detected, one by one they have to be executed and their name has to be stored in the Redis set from before.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
The new scripts have been executed and the application can now proceed to start. There’s one small problem though. Depending on the deployment strategy, multiple instances of the application might start up simultaneously and thus try to perform the data migration concurrently. If the data migration scripts aren’t idempotent (that’s up to you), then you’ll have unexpected/corrupted data.

&lt;p&gt;To account for this, I’m making use of &lt;a href="https://redis.io/docs/manual/patterns/distributed-locks/" rel="noopener noreferrer"&gt;RedLock&lt;/a&gt;, a distributed lock algorithm that is guaranteed to be deadlock free.&lt;/p&gt;

&lt;p&gt;The first thing that the &lt;em&gt;DataMigratorService&lt;/em&gt; tries to do it to acquire a named distributed lock. I named mine &lt;em&gt;PerformedMigrationsLock&lt;/em&gt;. It will continuously retry to acquire the lock and when it succeeds, it gives the lock an expiry time. The expiry time is pushed ahead as long as the application lives and guarantees that the lock is released even when the service crashes and fails to explicitly release the lock..&lt;/p&gt;

&lt;p&gt;Thus, &lt;em&gt;MigrateData()&lt;/em&gt; looks like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
&lt;strong&gt;How do the scripts look like?&lt;/strong&gt;

&lt;p&gt;My script to populate the &lt;em&gt;Sentiment&lt;/em&gt; field of the stored json documents looks like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Most &lt;strong&gt;RedisGears&lt;/strong&gt; python scripts start with a &lt;em&gt;GearsBuilder&lt;/em&gt; instance. On that instance you define the actual transformations that shall be performed on each loaded key and a pattern that decides which keys to even consider.

&lt;p&gt;Notice that I’m using random values to populate the &lt;em&gt;Sentiment&lt;/em&gt; field. You might compute the values for your fields based on other fields or actually use an ML model to perform the transformation. E.g. you could make use of &lt;a href="https://github.com/BayesWitnesses/m2cgen" rel="noopener noreferrer"&gt;m2cgen&lt;/a&gt; to transform trained models to pure python code and load them in &lt;strong&gt;RedisGears&lt;/strong&gt; to be executed in a &lt;em&gt;GearsBuilder&lt;/em&gt;* instance. Another option is to pull out the big guns and go straight to &lt;a href="https://redis.com/modules/redis-ai/" rel="noopener noreferrer"&gt;RedisAI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip&lt;/strong&gt;: if you’re following a zero downtime deployment strategy, you will have old and new service instances running in parallel. The new ones might perform schema and data migrations while starting up. For this reason it is best to do a release where you only add new fields to your data model. After all old service instances have been replaced with new ones, you can perform a new deployment in which you remove unused fields from the data model, if necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You saw how I’m doing the schema and data transformations in &lt;strong&gt;Visualizer&lt;/strong&gt;. My project is open source so if you like looking at code more than reading an article here you go &lt;a href="https://github.com/mariusmuntean/Visualizer" rel="noopener noreferrer"&gt;https://github.com/mariusmuntean/Visualizer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope that the concepts are generic and can be transferred to other projects without too much work. Let me know how you do data migration in Redis and how I can improve this article. The best way to get in touch with me is Twitter (my handle is @MunteanMarius) and GitHub issues.&lt;/p&gt;

&lt;p&gt;Give the article a ❤️ and follow me for more Redis content.&lt;/p&gt;

</description>
      <category>data</category>
      <category>education</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Redis as a Database — Live Leaderboards With PubSub and GraphQL Subscriptions</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Sun, 11 Dec 2022 19:06:00 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/redis-as-a-database-live-leaderboards-with-pubsub-and-graphql-subscriptions-16n5</link>
      <guid>https://dev.to/mariusmuntean/redis-as-a-database-live-leaderboards-with-pubsub-and-graphql-subscriptions-16n5</guid>
      <description>&lt;p&gt;Say you’re tracking the score of the players in your game. To each player name you associate a score. I’d be nice to always know who the top 50 or so players are and to get updates if the ranking changes.&lt;/p&gt;

&lt;p&gt;To implement such a feature, we can make use of Redis’ Sorted Set data structure, Pub/Sub and GraphQL subscriptions.&lt;/p&gt;

&lt;p&gt;This post dovetails with a previous one, called &lt;a href="https://medium.com/better-programming/redis-as-a-database-live-data-updates-with-pubsub-and-graphql-subscriptions-efafa665e173" rel="noopener noreferrer"&gt;**Redis as a Database — Live Data Updates With PubSub and GraphQL Subscriptions&lt;/a&gt;**, so I highly recommend you check that one out first.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I’m using &lt;strong&gt;Visualizer, **my pet project that I’ve talked about it in my previous posts about Redis, to show how to implement a leaderboard . The gist of it is that **Visualizer&lt;/strong&gt; ingests tweets from Twitter’s v2 API and stores them locally, in a Redis instance. Using the many capabilities of Redis, &lt;strong&gt;Visualizer&lt;/strong&gt; offers a set of nice features, including a leaderboard (though in &lt;strong&gt;Visualizer&lt;/strong&gt; it call it differently).&lt;/p&gt;

&lt;p&gt;Algorithm: continuously track the top of the leaderboard, compare the current version with the previous one and publish an update in case that there are differences.&lt;/p&gt;

&lt;p&gt;Find the backend code &lt;a href="https://github.com/mariusmuntean/Visualizer" rel="noopener noreferrer"&gt;here&lt;/a&gt; and the frontend code &lt;a href="https://github.com/mariusmuntean/VisualizerFrontend" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ranked Hashtags
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Visualizer&lt;/strong&gt;, instead of a game leaderboard, I’m tracking all hashtags that are used in the ingested tweets. Hashtags are ranked according to their frequency, i.e. the more they’re used the higher their rank will be. The frontend can subscribe to the top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags with a GraphQL subscription. When there is a change in the top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags, i.e. the rank of some hashtags changes, the frontend will receive an update with the current top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1c2wxcuw6274h2ux7is.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1c2wxcuw6274h2ux7is.png" alt="Visualizer — Partial architecture" width="800" height="1078"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both &lt;strong&gt;Visualizer API&lt;/strong&gt; and &lt;strong&gt;Visualizer Ingestion&lt;/strong&gt; are involved in offering an up to date list of the top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags.&lt;/p&gt;

&lt;p&gt;Whenever the frontend subscribes to the top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags via a GraphQL subscription, &lt;strong&gt;Visualizer API&lt;/strong&gt; keeps track of the subscription amount and the subscription object.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Meanwhile, &lt;strong&gt;Visualizer Ingestion&lt;/strong&gt; might be ingesting tweets. Every encountered hashtag is first added to the &lt;em&gt;hashtags&lt;/em&gt; Sorted Set which automatically ranks it and then the hashtag is published with its new rank in the &lt;em&gt;HashtagRanked&lt;/em&gt; Pub/Sub channel.

&lt;p&gt;Back in &lt;strong&gt;Visualizer API&lt;/strong&gt;, every time a ranked hashtag message arrives in the &lt;em&gt;HashtagRanked *channel, every subscriber is updated if necessary. For each subscriber, the key to the Sorted Set containing the previous top *&lt;/em&gt;&lt;em&gt;n&lt;/em&gt;** hashtags is computed. Then, each &lt;em&gt;PreviousRankedHashtagsForAmount:n&lt;/em&gt; Sorted Set is compared with the current top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags from the &lt;em&gt;hashtags&lt;/em&gt; Sorted Set. In case a discrepancy between the current top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags and the previous top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags is detected, then the current top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags are published to the GraphQL subscription and the &lt;em&gt;PreviousRankedHashtagsForAmount:&lt;/em&gt;&lt;em&gt;n *&lt;/em&gt;&lt;em&gt;Sorted Set **is overwritten with the current value.&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
**Note&lt;/em&gt;*: there may be a more efficient way of checking if an update has to be sent out to the subscribers using ZDIFF/ZDIFFSTORE (see all &lt;a href="https://redis.io/commands/?group=sorted-set" rel="noopener noreferrer"&gt;sorted set command&lt;/a&gt;), but I haven’t tried. Let me know if you’re interested in that.&lt;/p&gt;

&lt;p&gt;To finish the backend part, let’s have a look at the GraphQL subscription definition.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Subscribing to &lt;em&gt;topRankedHashtags&lt;/em&gt; is handled in &lt;em&gt;GetRankedHashtagsObservable(…)&lt;/em&gt; where _t*weetHashtagService.GetTopRankedHashtagsObservable(amount)* is called, which creates or returns an existing subscription for the requested amount (see the first code gist).

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Visualizer Frontend&lt;/strong&gt; uses the excellent Apollo client together with the &lt;a href="https://github.com/dotansimha/graphql-code-generator#readme" rel="noopener noreferrer"&gt;graphql-codegen&lt;/a&gt; to make my life easier when working with a graphQL API.&lt;/p&gt;

&lt;p&gt;First, the frontend retrieves the current top &lt;strong&gt;&lt;em&gt;n&lt;/em&gt;&lt;/strong&gt; hashtags so that it can display something.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
But instead of manually calling this query, graphql-codegen generates a nice little hook that I can call&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Next, the frontend subscribes to &lt;em&gt;topRankedHashtags.&lt;/em&gt;&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Which looks like this with the generated hook&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
From here on nothing super interesting happens. Whenever new data is available from the subscription, the &lt;em&gt;useTopRankedHashtagsChangedSubscription&lt;/em&gt; hook triggers a re-render and the current data is shown in a word cloud — permalink to the whole component &lt;a href="https://github.com/mariusmuntean/VisualizerFrontend/blob/8db3b9f1770b9ea74a60ac88fedc4a89c0301e71/src/components/rankedHashtagsChanged/liveRankedHashtags.tsx#L130" rel="noopener noreferrer"&gt;https://github.com/mariusmuntean/VisualizerFrontend/blob/8db3b9f1770b9ea74a60ac88fedc4a89c0301e71/src/components/rankedHashtagsChanged/liveRankedHashtags.tsx#L130&lt;/a&gt;

&lt;p&gt;So the essence of a feature like a live updating leaderboard is to leverage Redis’ Sorted Sets to get an up to date view of the data and compare it to the previous state.&lt;/p&gt;

&lt;p&gt;Thanks for reading. If you liked this post give it a ❤️ and follow me to stay up to date with Redis.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>graphql</category>
      <category>realtime</category>
      <category>react</category>
    </item>
    <item>
      <title>Redis as a Database — Live Data Updates with PubSub and GraphQL Subscriptions</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Sun, 13 Nov 2022 19:37:50 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/redis-as-a-database-live-data-updates-with-pubsub-and-graphql-subscriptions-22mp</link>
      <guid>https://dev.to/mariusmuntean/redis-as-a-database-live-data-updates-with-pubsub-and-graphql-subscriptions-22mp</guid>
      <description>&lt;p&gt;My pet project, which I named &lt;strong&gt;Visualizer&lt;/strong&gt; and that I use to learn more about Redis, can start ingesting tweets from Twitter’s &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/volume-streams/introduction"&gt;volume stream&lt;/a&gt; or from their &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/filtered-stream/introduction"&gt;filtered stream&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While it is doing so, &lt;strong&gt;Visualizer&lt;/strong&gt; can produce a live stream of all the hashtags that are currently encountered in the ingested tweets and it ranks them too.&lt;/p&gt;

&lt;p&gt;In this post I want to go over those parts of the system that handle new tweets, extract and publish the ranked hashtags and finally push them to the frontend to create that nice word cloud.&lt;/p&gt;

&lt;p&gt;If you just want to see the code, here’s the GitHub &lt;a href="https://github.com/mariusmuntean/Visualizer"&gt;link for the backend&lt;/a&gt; and here’s the &lt;a href="https://github.com/mariusmuntean/VisualizerFrontend"&gt;link to the frontend&lt;/a&gt;. Btw. this is my third post on using Redis, so make sure to read the others too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualizer Partial Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Visualizer&lt;/strong&gt; is made up of three components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OTsw8aC8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AX-r8qcz_rGe9_ZKEQlNckA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OTsw8aC8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AX-r8qcz_rGe9_ZKEQlNckA.png" alt="Visualizer — Partial Architecture" width="880" height="799"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visualizer Ingestion&lt;/strong&gt; — An ASP.NET Core 6 service that handles the stream of tweets and stores them in Redis as indexed documents (in JSON format). It also publishes each encountered hashtag together with its rank, i.e. a count of how many times it was ever encountered by &lt;strong&gt;Visualizer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visualizer API&lt;/strong&gt; — Another ASP.NET Core 6 service that offers access to the stored tweet data and to the live stream of ranked hashtags. &lt;a href="https://graphql.org/"&gt;GraphQL&lt;/a&gt; is another technology that I love and its &lt;a href="https://graphql.org/blog/subscriptions-in-graphql-and-relay/"&gt;subscriptions&lt;/a&gt; are ideal to push the stream of ranked hashtags to any consumer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visualizer Frontend&lt;/strong&gt; — A React Typescript + &lt;a href="https://vitejs.dev/"&gt;ViteJs&lt;/a&gt; SPA to show of &lt;strong&gt;Visualizer&lt;/strong&gt;’s features.&lt;/p&gt;

&lt;p&gt;In a future post I’ll talk more about my reasons for this architecture, so follow me no to miss it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing Ranked Hashtags
&lt;/h2&gt;

&lt;p&gt;As I mentioned, &lt;strong&gt;Visualizer Ingestion&lt;/strong&gt; gets tweets from Twitter’s API v2 and besides persisting them it also publishes any encountered hashtag together with its rank.&lt;/p&gt;

&lt;p&gt;The tweet data retrieval isn’t very interesting. Suffice it to say that I’m using &lt;a href="https://linvi.github.io/tweetinvi/dist/streams-v1.1/streams-introduction.html"&gt;Tweetinvi&lt;/a&gt; for this.&lt;/p&gt;

&lt;p&gt;For every extracted hashtag a few things happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Store and Rank&lt;/strong&gt;: I’m storing every hashtag in a &lt;a href="https://redis.io/docs/data-types/sorted-sets/#:~:text=A%20Redis%20sorted%20set%20is,Leaderboards."&gt;Redis Sorted Set&lt;/a&gt;. This Redis data structure has the expected time and space characteristics of a set, with the addition that adding an element multiple times will increase its rank each time. This is excellent for my use case as I don’t have to do much besides pumping all the hashtags in my sorted set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the code to get an instance of &lt;em&gt;IDatabase.&lt;/em&gt; It is probably best to add it to your DI container.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Afterwards I’m adding the hashtag to a sorted set and I’m telling Redis to increment the rank of this hashtag by 1. Notice that I get the new rank as a response.&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Publish Hashtag with Rank&lt;/strong&gt;: After I got the new rank of the hashtag I’m publishing it. Here I’m making use of &lt;a href="https://redis.com/redis-best-practices/communication-patterns/pub-sub/"&gt;Redis’ Pub/Sub&lt;/a&gt; which allows me to publish string messages to a channel. This effectively decouples &lt;strong&gt;Visualizer Ingestion **from **Visualizer API&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, you’ll need an &lt;em&gt;ISubscriber&lt;/em&gt; instance. Like most reusable objects, I’m adding it to my DI container.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
With that subscriber I can then publish my ranked hashtag to a channel&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Notice that I’m using a &lt;em&gt;RankedHashtag&lt;/em&gt; DTO to represent the hashtag and its rank and that I’m serialising it to a JSON string before publishing. Also notice that I’m using a constant for the channel name. That’s because the constants live in a shared project in the same solution and are reused by &lt;strong&gt;Visualizer API&lt;/strong&gt;.
&lt;h2&gt;
  
  
  Consuming Ranked Hashtags
&lt;/h2&gt;

&lt;p&gt;The stream of ranked hashtags is consumed by &lt;strong&gt;Visualizer API&lt;/strong&gt;. It does so by subscribing to the same channel that &lt;strong&gt;Visualizer Ingestion&lt;/strong&gt; uses to publish each hashtag with its current rank.&lt;/p&gt;

&lt;p&gt;In a singleton service, I’m registering a handler for new hashtags in that stream&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
In the handler, each channel message is deserialised to an instance of &lt;em&gt;RankedHashtag *and passed on to any GraphQL subscriber&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
The *_rankedHashtagStream&lt;/em&gt; that you see there is a plain Reactive Extensions ReplaySubject with a buffer of 1&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
It immediately broadcasts any notification to its subscribers. But who are its subscribers, I hear you asking? Its the GraphQL API users that subscribed to the ranked hashtags; they are the subscribers.

&lt;p&gt;The way that’s done in &lt;strong&gt;Visualizer&lt;/strong&gt; is quite specific to my needs and to the library that I’m using, called &lt;a href="https://github.com/graphql-dotnet/graphql-dotnet"&gt;GraphQl-Dotnet&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Please notice that the GraphQL &lt;em&gt;rankedHashtag&lt;/em&gt; subscription takes an argument called &lt;em&gt;sampleIntervalSec&lt;/em&gt; with a default value of 0. That value is used to determine if a subscriber should receive all the ranked hashtags as soon as they come (commonly referred to as real-time, but not in its strict sense), or if the subscriber should receive another stream of ranked hashtags which is obtained by sampling the “real-time” stream every &lt;em&gt;sampleIntervalSec&lt;/em&gt; seconds.
&lt;h2&gt;
  
  
  Visualizer Frontend
&lt;/h2&gt;

&lt;p&gt;This might be the least interesting part of the system because it only deals with data retrieval and rendering. It uses &lt;a href="https://www.apollographql.com/docs/react/"&gt;Apollo Client for React&lt;/a&gt; to connect to **Visualizer API **and &lt;a href="https://github.com/chrisrzhou/react-wordcloud"&gt;react-wordcloud&lt;/a&gt; to render the data.&lt;/p&gt;

&lt;p&gt;At a high level, the frontend retrieves the top 50 hashtags, sorted descendingly by their rank (not shown in this post, but it is a simple sorted set operation). This is in order to have something to display in the wordcloud.&lt;/p&gt;

&lt;p&gt;Then it subscribes to the stream of ranked hashtags and whenever a ranked hashtag arrives, it either overwrites one of the existing 50 hashtags or it is added to the same set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subscribe to Ranked hashtags&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m using the codegen feature of Apollo Client to create a nice React hook for subscribing to the ranked hashtags&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Then my component is re-rendered every time a new ranked hashtag is available. I’m merging the new ranked hashtag with the initial 50&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
&lt;strong&gt;Ranked Hashtags Wordcloud&lt;/strong&gt;&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Displaying a nice little word cloud involves a few system components and multiple concepts, but each is easy to understand.&lt;/p&gt;

&lt;p&gt;Let me know if you like these posts, with more code than usual. Give the post a ❤️ to let me know and follow me not to miss any future posts on Redis.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>pubsub</category>
      <category>graphql</category>
      <category>react</category>
    </item>
    <item>
      <title>Redis as a Database — Speed Test with K6</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Sun, 13 Nov 2022 15:55:36 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/redis-as-a-database-speed-test-4bh2</link>
      <guid>https://dev.to/mariusmuntean/redis-as-a-database-speed-test-4bh2</guid>
      <description>&lt;p&gt;In my previous post on using &lt;a href="https://medium.com/@marius.munteann/redis-as-a-database-with-redis-om-2601a48b5c04"&gt;Redis as a Database&lt;/a&gt; I mentioned how I’m using it to store and retrieve tweets in the context of Visualizer, my pet project.&lt;/p&gt;

&lt;p&gt;Now I want to show some performance characteristics of Visualizer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Setup
&lt;/h2&gt;

&lt;p&gt;The tests were performed on my M1 Pro Macbook Pro with 32 GB of ram, connected to WiFi and on battery power. I’m running both Visualizer microservices in Release mode with Jetbrains Rider, Redis Stack in the command line, Visualizer Frontend in VSCode and the current version of macOS Ventura.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Ingestion
&lt;/h2&gt;

&lt;p&gt;Here’s the code to store a single tweet in Redis&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
This runs in a dedicated microservice and is executed every time a new tweet is retrieved from Twitter’s sample stream. In a future post I’ll present the architecture of Visualizer.

&lt;p&gt;Using RedisInsight I can see that with my current setup I can manage to send around 700 commands per second, mostly storing tweets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2qT4qJOP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5840/1%2AjCN3F51cRXTWRV1Qh5UdTQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2qT4qJOP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5840/1%2AjCN3F51cRXTWRV1Qh5UdTQ.png" alt="" width="880" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Retrieval
&lt;/h2&gt;

&lt;p&gt;Retrieving data is handled by another microservice, which only reads from Redis.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://k6.io/"&gt;K6&lt;/a&gt; I wrote a script that retrieves 10 tweets from the GraphQL API&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
The results are … a bit disappointing

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YmZgXDpQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6996/1%2AQLpm_WJAAGTU_KHVzNV5sQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YmZgXDpQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6996/1%2AQLpm_WJAAGTU_KHVzNV5sQ.png" alt="" width="880" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The average duration of 110 requests, over a period of 6 seconds, is ~3.7 seconds. I definitely need to investigate that. My suspicion is that I’m a bit inefficient with the deserialisation and that the GraphQL API has an inherent overhead.&lt;/p&gt;

&lt;p&gt;Fortunately K6 has native support for Redis 😎 so I wrote another K6 script that basically does the same thing that the Visualizer microservice does behind the scenes, i.e. formulate and send a command to Redis to retrieve data from a certain index, sorted and paginated. But this time there’s no deserialization and GraphQL overhead&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
And the results are much better

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NlAeMuce--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6732/1%2A1X-YOiFGD4thsSKTHBzeKQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NlAeMuce--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6732/1%2A1X-YOiFGD4thsSKTHBzeKQ.png" alt="" width="880" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;FYI, the &lt;em&gt;Iteration_duration&lt;/em&gt; includes setup+request+teardown. This time around the average request duration was around 7.6 milliseconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qss4nLSh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6732/1%2A5KcTAZnVJqjlkN6vrGFdsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qss4nLSh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6732/1%2A5KcTAZnVJqjlkN6vrGFdsw.png" alt="" width="880" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With up to 60 concurrent virtual users (a K6 term) Redis managed to serve over 29000 requests in 9 seconds, which works out to be around 3300 requests per second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The system performed excellently on my battery-powered Macbook Pro, managing to store hundreads of tweets per second.&lt;/p&gt;

&lt;p&gt;The read performance through my Visualizer microservice is disappointing and I definitely need investigate this further. Fortunately the problem doesn’t seem to be with Redis, which means I have a change to improve it.&lt;/p&gt;

&lt;p&gt;Stay tuned for more updates from my journey in Redisland.&lt;/p&gt;

&lt;p&gt;If you liked this post let me know on Twitter 😉 (@MunteanMarius), give it a ❤️ and follow me to get more content on Redis, Azure, MAUI and other cool stuff.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>graphql</category>
      <category>database</category>
      <category>k6</category>
    </item>
    <item>
      <title>Redis as a Database with Redis OM</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Sun, 13 Nov 2022 15:53:57 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/redis-as-a-database-with-redis-om-3jh1</link>
      <guid>https://dev.to/mariusmuntean/redis-as-a-database-with-redis-om-3jh1</guid>
      <description>&lt;p&gt;My newest interest is &lt;a href="https://redis.io/"&gt;Redis&lt;/a&gt;, an in-memory data store for key-value pairs, streaming, message brokering and a database.&lt;/p&gt;

&lt;p&gt;In this post I’ll give you an introduction into using Redis as a normal database. The code snippets will use C# and the final result will look very familiar to anyone who used Entity Framework.&lt;/p&gt;

&lt;p&gt;What you’re not getting is a spoon-feeding, instead you’re going to see the relevant parts of Visualizer, a &lt;a href="https://github.com/mariusmuntean/Visualizer"&gt;pet project of mine&lt;/a&gt;, where I’m using Redis to ingest and query tweets from Twitter’s &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/volume-streams/api-reference/get-tweets-sample-stream"&gt;sample steam&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;Setting up all the tools is beyond the scope of this post, but I still want to mention the most essential things.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://redis.com/blog/introducing-redis-stack/"&gt;Redis Stack&lt;/a&gt;: a suite of multiple components, that includes a Redis instance, multiple Redis modules and RedisInsight. Two modules, RediSearch and RedisJSON are essential here, as they allow us to use Redis as a proper DB. The simplest way to get going is to use the &lt;a href="https://redis.io/docs/stack/get-started/install/docker/"&gt;official Docker container&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/redis/redis-om-dotnet"&gt;Redis OM .NET&lt;/a&gt;: an official .NET library that offers (CLR) object mapping and query translation. I can recommend it wholeheartedly, even though it isn’t perfect. The excellent community and maintainers go above and beyond the call of duty. Ask them anything on the &lt;a href="http://discord.gg/redis"&gt;official Discord server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A good .NET IDE: like VS Code, Visual Studio Proper or Rider.&lt;/p&gt;

&lt;p&gt;And finally, a few spare minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Modeling
&lt;/h2&gt;

&lt;p&gt;In Visualizer, I’m receiving a stream of tweets, including their many properties and nested data, which represent 1% of the “real-time” tweets.&lt;/p&gt;

&lt;p&gt;The fields are described in the official &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/volume-streams/api-reference/get-tweets-sample-stream"&gt;Twitter API documentation&lt;/a&gt;, look for &lt;em&gt;Response Fields&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To store and retrieve the data, I’m using Redis OM, which wants a normal C# class whose properties are annotated.&lt;/p&gt;

&lt;p&gt;The data model for a single tweet looks like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
You can find the full file &lt;a href="https://github.com/mariusmuntean/Visualizer/tree/a76d42973ed55a358af06f71fc786be1b3a8a88e/Visualizer.Shared.Models"&gt;here&lt;/a&gt;.

&lt;p&gt;Notice that the class is annotated as a &lt;em&gt;Document *with *StorageType&lt;/em&gt; JSON. This lets Redis OM know that we want to store instances of this class in a searchable way and in JSON format. The &lt;em&gt;Prefixes&lt;/em&gt; is just for our convenience, to make it easier to distinguish the data in Redis, in case that we have multiple document types.&lt;/p&gt;

&lt;p&gt;Each property is also annotated, to instruct Redis OM (and indirectly RediSearch and RedisJSON) which fields should be searchable and how exactly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The attribute &lt;em&gt;RedisIdField&lt;/em&gt; is manadatory and you can interpret it as the primary key of a tweet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Indexed&lt;/em&gt; does exactly what you’d expect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Searchable&lt;/em&gt; is powerful, because it allows full text search in the string property.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;CascadeDepth&lt;/em&gt; tells Redis OM how deep it should look for annotated properties. My &lt;em&gt;TweetModel&lt;/em&gt; class has an array of &lt;em&gt;ReferencedTweet&lt;/em&gt; instance. The &lt;em&gt;ReferencedTweet&lt;/em&gt; class is not marked as a &lt;em&gt;Document&lt;/em&gt; because it is nested inside the &lt;em&gt;TweetModel&lt;/em&gt;. It will however contain its own annotated properties. That’s why I set the depth to 1.&lt;/p&gt;

&lt;p&gt;All attributes are described &lt;a href="https://github.com/redis/redis-om-dotnet#-modeling-your-domain-and-indexing-it"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Index
&lt;/h2&gt;

&lt;p&gt;After the data is modelled, a so-called &lt;em&gt;index&lt;/em&gt; needs to be created in Redis.&lt;/p&gt;

&lt;p&gt;First, create a &lt;em&gt;RedisConnectionProvider&lt;/em&gt;. In my Visualizer project I’m adding one to the DI container with an extension method&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Then, let Redis OM send the command that instructs RediSearch/RedisJSON which field is searchable and how. I’m doing it when one of my microservices starts up.&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
In Redis, the index will have the name &lt;em&gt;tweetmodel-idx&lt;/em&gt;. You can list them all by issuing &lt;em&gt;FT._LIST&lt;/em&gt; in the Redis CLI or get a detailed explanation of an index with &lt;em&gt;FT.INFO tweetmodel-idx&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt;

&lt;h2&gt;
  
  
  Data Storing
&lt;/h2&gt;

&lt;p&gt;After you have a few tweets (I’m using a library called &lt;a href="https://github.com/linvi/tweetinvi"&gt;Tweetinvi&lt;/a&gt;) you want to store them.&lt;/p&gt;

&lt;p&gt;Redis OM makes this easy by offering us a &lt;em&gt;RedisCollection&lt;/em&gt;, where &lt;em&gt;T&lt;/em&gt; is our data model, e.g. &lt;em&gt;TweetModel&lt;/em&gt;. If it helps, this is analogous to a &lt;em&gt;DbSet&lt;/em&gt; in Entity Framework,&lt;/p&gt;

&lt;p&gt;To get a hold of such a collection, you make use of the &lt;em&gt;RedisConnectionProvider&lt;/em&gt; that you registered previously&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
On it you call &lt;em&gt;InsertAsync() *and provide your *TweetModel&lt;/em&gt; instance&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
That’s it, your tweet is now stored in Redis. You can now have a look at your data with RedisInsight. This is how it looks for me

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cz-tN7RY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5840/1%2A3ZZFUt0z5zAYk4ltf0RxJA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cz-tN7RY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5840/1%2A3ZZFUt0z5zAYk4ltf0RxJA.png" alt="Stored Tweets in RedisInsight" width="880" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Filter, Sort and Paginate Tweets
&lt;/h2&gt;

&lt;p&gt;I usually work with relational DBs (mostly Postgres), where filtering, sorting and paginating results is as common as it gets.&lt;/p&gt;

&lt;p&gt;In Redis, especially using Redis OM, this is as easy as we’re used to as Entity Framework users.&lt;/p&gt;

&lt;p&gt;First, we need a DTO that defines all the necessary filtering, sorting and pagination fields.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Visualizer offers a GraphQL API, hence the “Input” in the DTOs name.

&lt;p&gt;Next, we have to formulate a query based on the DTO and retrieve the tweets that match that query.&lt;/p&gt;

&lt;p&gt;First, get a new &lt;em&gt;RedisCollection&lt;/em&gt; instance like you saw in the previous section&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Then you can start filtering, e.g. by a tweet’s ID&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Remember that &lt;em&gt;TweetModel.Text&lt;/em&gt; was annotated as &lt;em&gt;Searchable&lt;/em&gt;, which allows for full text searches. To filter only tweets that contain a certain word in their text you’d write something like this&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Note that there’s no call to &lt;em&gt;Contains()&lt;/em&gt; or anything like that, just a simple &lt;em&gt;==&lt;/em&gt;.

&lt;p&gt;The &lt;em&gt;TweetModel.CreatedAt&lt;/em&gt; property is &lt;em&gt;Indexed&lt;/em&gt; and &lt;em&gt;Searchable&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Tweets can be filtered by date like so&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
A cool feature of Redis is its native support for the geolocation data type. Tweets can include geolocation information and to filter the tweets that are at a certain distance from a location you’d write something like this&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Sorting and pagination are nothing special&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Finally, you’d want to materialize the query and get your tweets&lt;br&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Visualizer has a simple React frontend that allows to query for tweets

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--heGPz-p1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/7360/1%2Ao6n1Mh9PvdyN8HWadFNIqQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--heGPz-p1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/7360/1%2Ao6n1Mh9PvdyN8HWadFNIqQ.png" alt="Filter, sort and paginate tweets" width="880" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And even to filter them by location&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x-Wvahjo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/7360/1%2AUbQRXHqlfXGXLSAzAnzR9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x-Wvahjo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/7360/1%2AUbQRXHqlfXGXLSAzAnzR9w.png" alt="Filter by location and show on map" width="880" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  There's More to Come
&lt;/h2&gt;

&lt;p&gt;I’m planning to write a few more posts about Visualizer, where I show Redis’ PubSub, quick ranking with SortedSets and GraphQL subscriptions.&lt;/p&gt;

&lt;p&gt;If you liked this post let me know on Twitter 😉 (@MunteanMarius), give it a ❤️ and follow me to get more content on Redis, Azure, MAUI and other cool stuff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other content on this topic
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=VLTPqImLapM"&gt;Hussein Nasser — Can Redis be used as a Primary database?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=SGEG5q4LT9s"&gt;Redis — Redis as a Primary Database&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kerry.lothrop.de/devtalk-83-marius-muntean/"&gt;DevTalk 83: The hidden features of Redis. With Marius Muntean&lt;/a&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>database</category>
      <category>graphql</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>How I prepared for and passed the AZ-900 exam</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Sat, 27 Nov 2021 15:11:41 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/how-i-prepared-and-passed-the-az-900-exam-4k4b</link>
      <guid>https://dev.to/mariusmuntean/how-i-prepared-and-passed-the-az-900-exam-4k4b</guid>
      <description>&lt;p&gt;I recently passed the Azure Fundamentals exam (AZ-900) and I decided to share my experience in preparing for and taking the exam&lt;/p&gt;

&lt;h2&gt;
  
  
  My background
&lt;/h2&gt;

&lt;p&gt;I'm a .NET software engineer and I've worked with various Azure services over the last 6 years. The main services that I use at work are App Service for web APIs with ASP.NET Core, Azure Functions, SignalR, IoTHub, CosmosDB, EvenHub and Azure AD B2C.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;In time I had the impression that I'm using the same few Azure services over and over again. Every other week a new fancy Azure service would be announced, significantly reducing the time and effort to deliver a customer project, but I never got to use them and after a while I would just forget that they exist.&lt;br&gt;
So I decided to take the AZ-900 knowing that it doesn't go all to deep into any particular service, but instead it covers many different services and topics. I thought about it as a big picture exam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;

&lt;p&gt;The first thing I did was to follow Microsoft's recommended learning path for this exam. Just scroll down on this page and jump in &lt;a href="https://docs.microsoft.com/en-us/learn/certifications/exams/az-900"&gt;https://docs.microsoft.com/en-us/learn/certifications/exams/az-900&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I quickly realized that some things bored me because I must have used them 100 times, like creating yet another web API. But in other topics I obviously had gaps in my knowledge, e.g. stuff about data governance and details on SLAs.&lt;/p&gt;

&lt;p&gt;This took me around a week. After work, after unwinding for a while, I would sit on my couch with my notebook and try to get as far through the learning path as I could. Often times I realized I could keep going in a zombie-like fashion where I would read the text but not really absorb and internalize the information. Usually that's when I stopped for the day.&lt;/p&gt;

&lt;p&gt;Next I proceeded to test my knowledge with free online tests. I cannot recommend enough Adam Marczak's site and YouTube channel (&lt;a href="https://marczak.io/az-900/"&gt;https://marczak.io/az-900/&lt;/a&gt;). He has videos and tests on all topics that are covered by the AZ-900.&lt;/p&gt;

&lt;p&gt;This was probably enough to pass the exam, but for some reason I felt that a dedicated Whizlabs course was necessary 😁. I used this one &lt;a href="https://www.whizlabs.com/microsoft-azure-certification-az-900/"&gt;https://www.whizlabs.com/microsoft-azure-certification-az-900/&lt;/a&gt;&lt;br&gt;
And never regretted it. The videos are on point, the labs are clear and it comes with a ton of practice tests. The practice tests really cemented the information for me. I would do a test, then go over the answers, especially those I got wrong and read up on that topic. 👌👌👌&lt;/p&gt;

&lt;h2&gt;
  
  
  Test day
&lt;/h2&gt;

&lt;p&gt;I scheduled my exam with Pearson VUE, on a Saturday. You will have to install a tool from them that checks you computer and makes sure it has the necessary system requirements, like working speakers, microphone and a webcam.&lt;br&gt;
You'll also be reminded that you have to take the exam in a quiet room, alone, with no distractions in sight (whiteboard, monitors or TVs). You should only have your computer and your ID on the table/desk where you take the exam. Phones should not be in arm's reach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exam
&lt;/h3&gt;

&lt;p&gt;You should check into your exam half an hour before it is scheduled to start. I had to provide multiple pictures of my room and desk (from 4 angles) and a valid government ID. The system didn't accept the picture of my ID at first. My pulse started to shoot up, but I retook the photo and uploaded it again. It went through.&lt;br&gt;
Immediately after finishing the checkin I was allowed to start the exam, a few minutes before it was scheduled. I was pumped and so I went for it.&lt;/p&gt;

&lt;p&gt;The tool that verified my computer is the same tool that you use to take the exam. Actually it is the only application that is allowed to run while you take the exam. It will complain if it detects other running applications. Its UI is like a wizard that shows you some information and you have to confirm that you read and accept it. Sometimes you can go back and other times you cannot.&lt;/p&gt;

&lt;p&gt;Right before the actual exam questions show up, your webcam will be turned on and record you. It is an anti cheat measure.&lt;/p&gt;

&lt;p&gt;When I got to the actual exam questions a timer showed up and started counting down from 45 minutes. I had 35 questions. They're mostly multiple choice questions. Sometimes you have to drag a box with a label from the left of the screen to the matching concept on the right of the screen. Everything's explained to you in the app.&lt;br&gt;
I found all the questions clear and fair. No surprize there.&lt;br&gt;
What surpized me was that the proctor (that person that watches you through the webcam while you take the exam) suddenly contacted me via chat. That was my first and only notice that I should not take my eyes off the screen, otherwise he'd stop the exam right then and there and I would not be able to retake it.&lt;br&gt;
I was intimidated a bit and didn't have the inclination to ask what triggered that reaction. I can only guess that I was thinking about a question and had that 10000 miles look on my face.&lt;/p&gt;

&lt;p&gt;I powered through. You can mark any questions for review. When you're done answering all of them you will be shown every question and your answer. I presume that if I had marked any question for review, that I could still change my answers at this point in the test because there was plenty of time left and the timer was still running. &lt;br&gt;
After "reviewing" all of my answers (they were greyed out for me so I just clicked next, 35 times) I submitted them.&lt;br&gt;
Immediately I was greeted with my score of 865, I had passed 😁 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;My impression is that the exam was good for me. I got to refresh some terms and concepts that I hadn't had contact with in years and I learned some new things too.&lt;br&gt;
The exam isn't very technical, so anyone hoping to sharpen their skills by preparing for this exam should just keep in mind that this is a Fundamentals exam. Even so, I think it is beneficial for many software engineers.&lt;br&gt;
In hindsight the Whizlabs course would have been enough for me. The site often has discounts and if you use the Honey browser extension you'll be able to get courses at less than half the price 😉&lt;br&gt;
Speaking of technical skills, I'm starting to think that the AZ-204 could be the next achievable certification for me.&lt;/p&gt;

&lt;p&gt;Good luck!&lt;/p&gt;

</description>
      <category>azure</category>
      <category>az900</category>
      <category>certification</category>
      <category>azurefundamentals</category>
    </item>
    <item>
      <title>Build the Game of Life with Blazor</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Sun, 29 Aug 2021 17:28:03 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/build-the-game-of-life-with-blazor-4ie9</link>
      <guid>https://dev.to/mariusmuntean/build-the-game-of-life-with-blazor-4ie9</guid>
      <description>&lt;p&gt;Welcome to my series of posts where I'm building the famous &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life" rel="noopener noreferrer"&gt;Game of Life&lt;/a&gt; with different UI technologies.&lt;br&gt;
In this post I'll show you how it is done in &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-6.0" rel="noopener noreferrer"&gt;Blazor Webassembly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let me know in the comments if you're interested in seeing implementations in Xamarin.Forms/MAUI, WPF or Flutter.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy96bkbaz620taydfy6wx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy96bkbaz620taydfy6wx.gif" alt="Game of Life in motion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the code: &lt;a href="https://github.com/mariusmuntean/GameOfLife" rel="noopener noreferrer"&gt;https://github.com/mariusmuntean/GameOfLife&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;.NET - I bet you knew this one. Out of curiosity I went with .NET 6 Preview 7.&lt;/li&gt;
&lt;li&gt;Visual Studio Code or Visual Studio ('proper' or 'for Mac') or Rider - I really wanted to use VSCode for this but OmniSharp would not play ball so I resorted to Rider.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Create the Blazor Webassembly project
&lt;/h2&gt;

&lt;p&gt;If you're using Visual Studio proper or Visual Studio for Mac or even Rider, creating the project is just a series of clicks to create a new solution, then choose Blazor Webassembly App as the project type and give it a name.&lt;/p&gt;

&lt;p&gt;I went the CLI way and used this command to create the project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new blazorwasm &lt;span class="nt"&gt;-o&lt;/span&gt; gol.blazorwasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the project, just to make sure that everything works as expected.&lt;br&gt;
You now have the option to choose between a normal run with or without the debugger and the hot-reload feature.&lt;br&gt;
I went with hot-reloading. Just open a terminal/CLI window (my IDE has an integrated one) and run this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now be able to update files in your IDE and upon saving, they should be picked up automagically and the browser window will refresh.&lt;br&gt;
At some point you'll be asked this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Do you want to restart your app - Yes &lt;span class="o"&gt;(&lt;/span&gt;y&lt;span class="o"&gt;)&lt;/span&gt; / No &lt;span class="o"&gt;(&lt;/span&gt;n&lt;span class="o"&gt;)&lt;/span&gt; / Always &lt;span class="o"&gt;(&lt;/span&gt;a&lt;span class="o"&gt;)&lt;/span&gt; / Never &lt;span class="o"&gt;(&lt;/span&gt;v&lt;span class="o"&gt;)&lt;/span&gt;?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose &lt;code&gt;Always&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Business Logic
&lt;/h2&gt;

&lt;p&gt;In your project create a new directory and call it &lt;code&gt;Models&lt;/code&gt;. Inside &lt;code&gt;Models&lt;/code&gt; create an enum called &lt;code&gt;CellState&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Alive&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The game consists of a 2D grid where each slot is taken up by a cell. A cell can be either dead or alive. Now add the &lt;code&gt;Cell&lt;/code&gt; class, in another file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json.Serialization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;gol.blazorwasm.Models&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cell&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// For the deserializer.&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;param name="currentState"&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;param name="nextState"&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;JsonConstructorAttribute&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CellState&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt; &lt;span class="n"&gt;nextState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;CurrentState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;NextState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nextState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CellState&lt;/span&gt; &lt;span class="n"&gt;currentCellState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt; &lt;span class="n"&gt;CurrentState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt; &lt;span class="n"&gt;NextState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Tick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;CurrentState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NextState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;NextState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CurrentState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CurrentState&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alive&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dead&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CurrentState&lt;/code&gt; of a &lt;code&gt;Cell&lt;/code&gt; tells us how the cell is currently doing. Later we'll have to compute the new state of each &lt;code&gt;Cell&lt;/code&gt; based on the state of its neighbors. To make the code simpler, I decided to store the next state of the &lt;code&gt;Cell&lt;/code&gt; in the &lt;code&gt;NextState&lt;/code&gt; property.&lt;br&gt;
When the game is ready to transition each &lt;code&gt;Cell&lt;/code&gt; into its next state, it can call &lt;code&gt;Tick()&lt;/code&gt; on the &lt;code&gt;Cell&lt;/code&gt; instance and the &lt;code&gt;NextState&lt;/code&gt; becomes the &lt;code&gt;CurrentState&lt;/code&gt;.&lt;br&gt;
The method &lt;code&gt;Toggle()&lt;/code&gt; will allow us to click somewhere on the 2D grid and kill or revive a &lt;code&gt;Cell&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's talk about life. At the risk of sounding too reductionist, it's just a bunch of interacting cells. So we'll create one too&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;gol.blazorwasm.Models&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;CellsChanged&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Life&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Life&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"the rows and columns cannot be 0 or less"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;_rows&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;_cols&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;_cells&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;][];&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Life&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;initialCells&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CellsChanged&lt;/span&gt; &lt;span class="n"&gt;onNewGeneration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newRows&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initialCells&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newCols&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initialCells&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRows&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;newCols&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one or both dimensions of the provided 2d array is 0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;_cells&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initialCells&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;_rows&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newRows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;_cols&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newCols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;Cells&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what we just created. &lt;code&gt;Life&lt;/code&gt; is a class that keeps track of a bunch of cells. For that we're using &lt;code&gt;Cell[][] _cells&lt;/code&gt; which is just a 2D array of our simple &lt;code&gt;Cell&lt;/code&gt; class. Having a 2D array allows us to know exactly where each cell is and who its neighbors are.&lt;br&gt;
Traversing the 2D array can be cumbersome so I keep track of its dimensions with the fields &lt;code&gt;_rows&lt;/code&gt; and &lt;code&gt;_columns&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are two ways in which I want to be able to create a new &lt;code&gt;Life&lt;/code&gt;instance&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;From scratch - meaning I just tell it how many rows and columns of &lt;code&gt;Cell&lt;/code&gt;s I want and the &lt;code&gt;Life&lt;/code&gt; just initializes its 2D &lt;code&gt;_cells&lt;/code&gt; array with &lt;code&gt;Cell&lt;/code&gt;s in the &lt;code&gt;Dead&lt;/code&gt; state.&lt;br&gt;
Basically, that's what the first constructor does.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From a file - think of a saved game state. We'll later save the state of the game into a file and then load it up. When loading the saved game state, we need to tell the &lt;code&gt;Life&lt;/code&gt; instance what each of its &lt;code&gt;Cell&lt;/code&gt;'s state should be.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point we can create a new &lt;code&gt;Life&lt;/code&gt;, where all the cells are either dead (first constructor) or in a state that we received from 'outside' (second constructor).&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;Life&lt;/code&gt; needs a bit more functionality and then it is complete. The very first time we load up the game, all the cells will be dead. So it would be nice to be able to just breathe some life into the dead cells.&lt;br&gt;
For that, &lt;code&gt;Life&lt;/code&gt; needs a method that takes the location of a &lt;code&gt;Cell&lt;/code&gt; and toggles its state to the opposite value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;_rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Row value invalid"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;_cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Column value invalid"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Life&lt;/code&gt; instance just makes sure that the specified location of the &lt;code&gt;Cell&lt;/code&gt; makes sense and then tells that &lt;code&gt;Cell&lt;/code&gt; to toggle its state. If you remember, the &lt;code&gt;Cell&lt;/code&gt; class can toggle its state, if told to do so.&lt;/p&gt;

&lt;p&gt;The last and most interesting method of &lt;code&gt;Life&lt;/code&gt; implements the 3 &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Rules" rel="noopener noreferrer"&gt;rules&lt;/a&gt; of the Game of Life.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Any live cell with two or three live neighbours survives.&lt;/li&gt;
&lt;li&gt;Any dead cell with three live neighbours becomes a live cell.&lt;/li&gt;
&lt;li&gt;All other live cells die in the next generation. Similarly, all other dead cells stay dead.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Tick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Compute the next state for each cell&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentCell&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cellNeighbors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetCellNeighbors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;liveCellNeighbors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cellNeighbors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentState&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Rule source - https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Rules&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentState&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alive&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;liveCellNeighbors&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;liveCellNeighbors&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NextState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentState&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dead&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;liveCellNeighbors&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NextState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NextState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Switch to the next state for each cell&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentCell&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="n"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Tick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetCellNeighbors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;neighbors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rowOffset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;rowOffset&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;rowOffset&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;colOffset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;colOffset&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;colOffset&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowOffset&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;colOffset&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Skip self&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;neighborRow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rowOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;neighborCol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;colOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neighborRow&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;neighborRow&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;_rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neighborCol&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;neighborCol&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;_cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;neighbors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;neighborRow&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;neighborCol&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;neighbors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me quickly walk you through the code. I'm traversing the 2D array of &lt;code&gt;Cell&lt;/code&gt;s, making use of the rows and columns. For each cell I'm looking at its neighbors and based on the 3 game rules I'm computing the next state of the &lt;code&gt;Cell&lt;/code&gt;.&lt;br&gt;
When I'm done with that, I'm traversing the 2D grid again (I know, not very efficient of me, but I went for readable code) and telling each &lt;code&gt;Cell&lt;/code&gt; to switch to its next state.&lt;/p&gt;

&lt;p&gt;We're done with the business logic. It's time for the UI.&lt;/p&gt;
&lt;h2&gt;
  
  
  UI
&lt;/h2&gt;

&lt;p&gt;Similarly to React (see the previous post in this series), Blazor also uses a hierarchy of components. The similarities don't end here, in Blazor you can also mix code with markup and even go code-only, though I wouldn't recommend this.&lt;/p&gt;

&lt;p&gt;When you're mixing code and markup you're making use of the Razor syntax. There's usually a &lt;code&gt;@code{}'&lt;/code&gt; block that holds UI logic and some markup which can also be sprinkled with &lt;code&gt;@&lt;/code&gt;directives that allow you to write inline code that emits HTML.&lt;/p&gt;

&lt;p&gt;As far as I know the location of the &lt;code&gt;@code{}&lt;/code&gt; block is not important and I prefer having it immediately after the &lt;code&gt;@using&lt;/code&gt; and &lt;code&gt;@inject&lt;/code&gt; statements. The markup lives just below the &lt;code&gt;@code{}&lt;/code&gt; directive. That's also similar to React.&lt;/p&gt;
&lt;h3&gt;
  
  
  Canvas drawing in Blazor
&lt;/h3&gt;

&lt;p&gt;My first attempt was to use one of the Blazor libraries for drawing onto the canvas. These two looked the most promising&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blazor.Extensions.Canvas - &lt;a href="https://github.com/BlazorExtensions/Canvas" rel="noopener noreferrer"&gt;https://github.com/BlazorExtensions/Canvas&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Excubo.Blazor.Canvas &lt;a href="https://github.com/excubo-ag/Blazor.Canvas" rel="noopener noreferrer"&gt;https://github.com/excubo-ag/Blazor.Canvas&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They're both easy to set up and you'll have some pixels on your canvas in no time. The issue is that I needed to draw hundreds of rectangles every time the data in the game changes. To produce each rectangle, a call from the C# code to Javascript is necessary, which still has a big overhead.&lt;br&gt;
I tried batching too, but no dice.&lt;/p&gt;

&lt;p&gt;In the end I gave up on these libraries and conceded that in Blazor development you still have to use Javascript, despite Microsoft's promise "Use only C# in the browser, no more JavaScript!".&lt;/p&gt;
&lt;h2&gt;
  
  
  SimpleLife.razor
&lt;/h2&gt;

&lt;p&gt;This component will use a &lt;code&gt;Life&lt;/code&gt; instance to render the game. The &lt;code&gt;Life&lt;/code&gt; instance will be created by the component or be loaded from a file.&lt;/p&gt;

&lt;p&gt;Let's start by adding a &lt;code&gt;Components&lt;/code&gt; directory. Inside this directory add a new razor Component and call it &lt;code&gt;SimpleLife.razor&lt;/code&gt;. If you're adding it as a simple text file and then renaming its, just make sure that the Build Action is set to "Content".&lt;/p&gt;

&lt;p&gt;Add these &lt;code&gt;@using&lt;/code&gt; statements to the top of the file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;gol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blazorwasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Models&lt;/span&gt;
&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Components&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first one just helps us in using our modes. The second one made Rider (my IDE) happy.&lt;/p&gt;

&lt;p&gt;Now let the framework inject the Javascript runtime interop service. Add this &lt;code&gt;@inject&lt;/code&gt; directive below your &lt;code&gt;@using&lt;/code&gt;s&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@inject&lt;/span&gt; &lt;span class="n"&gt;IJSRuntime&lt;/span&gt; &lt;span class="n"&gt;_jsRuntime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll use this interop service in a bit.&lt;/p&gt;

&lt;p&gt;Get ready, we're adding the &lt;code&gt;@code&lt;/code&gt; block. Below &lt;code&gt;@inject&lt;/code&gt; add this this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@code&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;SpaceForButtons&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;ElementReference&lt;/span&gt; &lt;span class="n"&gt;_canvasRef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Life&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_cellEdgeAndSpacingLength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;_cellEdgeLength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_canvasWidth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_canvasHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Columns&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Rows&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PixelWidth&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PixelHeight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnParametersSet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;InitData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;InitData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_life&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Life&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Glider&lt;/span&gt;
        &lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;UpdateCellAndCanvasSize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;UpdateCellAndCanvasSize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_cellEdgeAndSpacingLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PixelWidth&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PixelHeight&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;SpaceForButtons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_cellEdgeLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.9&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;_cellEdgeAndSpacingLength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;_canvasWidth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_cellEdgeAndSpacingLength&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_canvasHeight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_cellEdgeAndSpacingLength&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Read in young Keanu Reeves' voice) Whoa what's going on here?&lt;br&gt;
We're declaring a constant for the vertical space to reserve for some buttons.&lt;br&gt;
Then we're declaring a variable &lt;code&gt;_life&lt;/code&gt; for the &lt;code&gt;Life&lt;/code&gt; instance that's going to be rendered.&lt;br&gt;
The next two variables hold the values for a rendered &lt;code&gt;Cell&lt;/code&gt;'s edge length, with and without the space between cells.&lt;br&gt;
The last variables will hold the exact canvas dimensions, in pixels.&lt;/p&gt;

&lt;p&gt;Next up are the properties. If you pay close attention you'll notice the &lt;code&gt;[Parameter]&lt;/code&gt; attributes. This means that they're going to be passed in from outside, when another component uses this one. &lt;br&gt;
The &lt;code&gt;Rows&lt;/code&gt; and &lt;code&gt;Columns&lt;/code&gt; properties let us know how many rows and columns our game should have. &lt;br&gt;
The &lt;code&gt;PixelWidth&lt;/code&gt; and &lt;code&gt;PixelHeight&lt;/code&gt; properties tell our component what the available space is. We could've made the component responsive to layout changes, but I went for simple code. I'm open to simple solutions, so please let me know in the comments how to make it responsive.&lt;/p&gt;

&lt;p&gt;As its name implies, &lt;code&gt;OnParametersSet()&lt;/code&gt; will be called when the parent component provided values for our parameter properties. I'm using this component lifecycle method to initialize a new &lt;code&gt;Life&lt;/code&gt; and compute the values for the variables in &lt;code&gt;InitData()&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;_life&lt;/code&gt; is assigned to a new instance of &lt;code&gt;Life&lt;/code&gt; that directly uses the &lt;code&gt;Rows&lt;/code&gt; and &lt;code&gt;Columns&lt;/code&gt; that were passed to our component. Immediately after that I'm making use of the &lt;code&gt;Life&lt;/code&gt;'s &lt;code&gt;Toggle()&lt;/code&gt; method to toggle a few cells to the &lt;code&gt;Alive&lt;/code&gt; state. They form a so-called 'Glider', one of the &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Examples_of_patterns" rel="noopener noreferrer"&gt;canonical shapes&lt;/a&gt; in the Game of Life.&lt;br&gt;
Then I'm doing a bit of computation to figure out the necessary &lt;code&gt;Cell&lt;/code&gt; edge length so as to fit the requested columns and rows in the available space.&lt;/p&gt;

&lt;p&gt;Before continuing with the &lt;code&gt;@code&lt;/code&gt; block, let's add some markup. Add this code just below your &lt;code&gt;@code&lt;/code&gt; block&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@if (_life != null)
{
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"@_canvasWidth"&lt;/span&gt;
                &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"@_canvasHeight"&lt;/span&gt;
                &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"@_canvasRef"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
}
else
{
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;get a Life&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yay, some Razor syntax! 🎉&lt;br&gt;
So, if our code that initializes the &lt;code&gt;Life&lt;/code&gt; instance ran, then &lt;code&gt;_life&lt;/code&gt; is set and the other values were computed. In that case render the canvas with the computed dimensions and store a reference to that canvas in &lt;code&gt;_canvasRef&lt;/code&gt;. Otherwise just emit a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;with a sad message.&lt;/p&gt;

&lt;p&gt;Before continuing, let's recap: we react to the component parameters being set by initializing an instance of &lt;code&gt;Life&lt;/code&gt; with the requested number of rows and columns, then we're computing the size of the canvas that's going to contain the game's cells and the optimal size &lt;code&gt;Cell&lt;/code&gt; edge length to fit them all on the canvas.&lt;/p&gt;

&lt;p&gt;Phew, that was a lot! We're almost ready to see the little &lt;code&gt;Cells&lt;/code&gt;s.&lt;/p&gt;
&lt;h3&gt;
  
  
  Crossing boundaries
&lt;/h3&gt;

&lt;p&gt;As I mentioned before we won't be using any drawing library for Blazor. Instead we're going to use the elegant &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API" rel="noopener noreferrer"&gt;canvas api&lt;/a&gt;.&lt;br&gt;
That's not directly possible from WebAssembly, so we're making use of the Javascript interop here. &lt;br&gt;
We're going to write a Javascript function that can use the canvas API to draw our cells and we're going to attach it to the window object.&lt;br&gt;
In your &lt;code&gt;wwwroot/index.html&lt;/code&gt; add the following script tag to the body of the document&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderCellsOnCanvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maskColors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_cellEdgeAndSpacingLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_cellEdgeLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rowIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cellState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;colIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginPath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maskColors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cellState&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
                &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colIndex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_cellEdgeAndSpacingLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rowIndex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;_cellEdgeAndSpacingLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_cellEdgeLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_cellEdgeLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function takes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;canvas&lt;/code&gt; - a reference to an HTML &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mask&lt;/code&gt; - a 2D array where each element stands for the state of the &lt;code&gt;Cell&lt;/code&gt; at those coordinates. 0 means the &lt;code&gt;Cell&lt;/code&gt; is dead and 1 means the &lt;code&gt;Cell&lt;/code&gt; is alive.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maskColors&lt;/code&gt; - a 1D array containing the color to use for each &lt;code&gt;Cell&lt;/code&gt; state. So it is a mapping from the values from &lt;code&gt;mask&lt;/code&gt; to usable colors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_cellEdgeAndSpacingLength&lt;/code&gt; - the length of a &lt;code&gt;Cell&lt;/code&gt;'s edge, including the space to the next &lt;code&gt;Cell&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_cellEdgeLength&lt;/code&gt;- the length of a &lt;code&gt;Cell&lt;/code&gt;'s edge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It then loops over the 2D array and maps every value to a color and then draws a square of that color, at the current &lt;code&gt;Cell&lt;/code&gt; position.&lt;/p&gt;

&lt;h3&gt;
  
  
  Back to the SimpleLife
&lt;/h3&gt;

&lt;p&gt;With this Javascript function available on the window object, we can go back to the &lt;code&gt;SimpleLife&lt;/code&gt; component and add another lifecycle method that computes the necessary parameters for the Javascript function and then calls it. Below &lt;code&gt;OnParametersSet()&lt;/code&gt; add this override&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;   &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnAfterRenderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;][];&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentCell&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
                &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;maskColors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"black"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"red"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_jsRuntime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"renderCellsOnCanvas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_canvasRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maskColors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_cellEdgeAndSpacingLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_cellEdgeLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It builds the &lt;code&gt;mask&lt;/code&gt; parameter by adding a 0 or a 1 for each &lt;code&gt;Cell&lt;/code&gt; in our &lt;code&gt;_life&lt;/code&gt;. This is also the place where we decide the colors for each &lt;code&gt;CellState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All that's left is to call the Javascript function with our injected Javascript interop service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render the SimpleLife
&lt;/h3&gt;

&lt;p&gt;To see the &lt;code&gt;SimpleLife&lt;/code&gt; component in action we need to use it on a page.&lt;br&gt;
In your &lt;code&gt;Pages&lt;/code&gt; directory, add a new one called &lt;code&gt;GOL.razor&lt;/code&gt; and give it this content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;
&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;gol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blazorwasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Components&lt;/span&gt;
&lt;span class="n"&gt;@inject&lt;/span&gt; &lt;span class="n"&gt;IJSRuntime&lt;/span&gt; &lt;span class="n"&gt;_jsRuntime&lt;/span&gt;

&lt;span class="n"&gt;@code&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PixelWidth&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PixelHeight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnAfterRenderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// This only works because I added an appropriate Javascript function to the "window" object. See index.html.&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dimensions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_jsRuntime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WindowDimensions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"getWindowDimensions"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;PixelWidth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;PixelHeight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;StateHasChanged&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WindowDimensions&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Height&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SimpleLife&lt;/span&gt; &lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt;
            &lt;span class="n"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt;
            &lt;span class="n"&gt;PixelWidth&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"@PixelWidth"&lt;/span&gt;
            &lt;span class="n"&gt;PixelHeight&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"@PixelHeight"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This page also makes use of a Javascript function that returns the window dimensions. Now that you know how to do it, just add this new &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag to your &lt;code&gt;index.html&lt;/code&gt;'s body&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getWindowDimensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the content of your &lt;code&gt;App.razor&lt;/code&gt; with this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Router&lt;/span&gt; &lt;span class="na"&gt;AppAssembly=&lt;/span&gt;&lt;span class="s"&gt;"@typeof(App).Assembly"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Found&lt;/span&gt; &lt;span class="na"&gt;Context=&lt;/span&gt;&lt;span class="s"&gt;"routeData"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;RouteView&lt;/span&gt; &lt;span class="na"&gt;RouteData=&lt;/span&gt;&lt;span class="s"&gt;"@routeData"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Found&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;NotFound&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sorry, there's nothing at this address.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/NotFound&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Router&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you still have your &lt;code&gt;dotnet watch&lt;/code&gt; command running you should now see a new instance of the Game of Life with a simple Glider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Back to the SimpleLife, part 2
&lt;/h3&gt;

&lt;p&gt;That was a lot! I know, but you should get away with copy pasting code. 😉&lt;/p&gt;

&lt;p&gt;We're ready to make the &lt;code&gt;SimpleLife&lt;/code&gt; component interactive. First is toggling &lt;code&gt;Cell&lt;/code&gt;s from dead to alive and back.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dead or Alive
&lt;/h4&gt;

&lt;p&gt;In the markup section, locate the &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element and add this attribute&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@onclick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"@(e =&amp;gt; OnCanvasClick(e.OffsetX, e.OffsetY))"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now implement the method that we're referencing. Just add this method to the &lt;code&gt;@code&lt;/code&gt; block&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnCanvasClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;pixelX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;pixelY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pixelX&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;pixelX&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_canvasWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pixelY&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;pixelY&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_canvasHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Translate pixel coordinates to rows and columns&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clickedRow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;pixelY&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;_canvasHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clickedCol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;pixelX&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;_canvasWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clickedRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clickedCol&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coordinates of a click event on the canvas as passed to the &lt;code&gt;OnCanvasClick&lt;/code&gt; method, which converts pixel coordinates to &lt;code&gt;Cell&lt;/code&gt; coordinates, i.e. row and column. Then the appropriate &lt;code&gt;Cell&lt;/code&gt; is told to toggle its state.&lt;br&gt;
You should now be able to click anywhere on the canvas to make ⬛️ cells 🟥 and vice versa.&lt;/p&gt;

&lt;p&gt;That's nice and dandy, but we want to actually play the Game.&lt;br&gt;
of Life. &lt;/p&gt;
&lt;h4&gt;
  
  
  They're alive!
&lt;/h4&gt;

&lt;p&gt;For that we want to apply the 3 rules of the game to the current game state and progress to the next game state.&lt;/p&gt;

&lt;p&gt;Below the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;that wraps the &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; add a new button like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="n"&gt;@onclick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;OnTickClicked&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="nc"&gt;btn&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;primary&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;Tick&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the &lt;code&gt;@code&lt;/code&gt; block, add this method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnTickClicked&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Tick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever you click the new button, the &lt;code&gt;Life&lt;/code&gt;'s &lt;code&gt;Tick()&lt;/code&gt; method is invoked. That method applies the game rules and moves every &lt;code&gt;Cell&lt;/code&gt; from its current state to its next state.&lt;br&gt;
Through Blazor magic, a re-render is triggered which calls our overriden &lt;code&gt;OnAfterRenderAsync()&lt;/code&gt; method which paints the current state onto the canvas.&lt;/p&gt;

&lt;p&gt;By the way, if you wonder where the CSS classes come from you should know that Bootstrap is baked into Blazor.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;Tick&lt;/code&gt; business was easy! Now it pays off that we've encapsulated that logic in &lt;code&gt;Life&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's continue and keep the critters under control.&lt;/p&gt;
&lt;h4&gt;
  
  
  Restoring order
&lt;/h4&gt;

&lt;p&gt;Let's assume that the game got out of hand and the &lt;code&gt;Cell&lt;/code&gt;s are plotting to overthrow the current world order. We need a way to clear the game board.&lt;br&gt;
Start by adding a new button, below the previous one&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="n"&gt;@onclick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;OnClear&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="nc"&gt;btn&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;primary&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;Clear&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;@code&lt;/code&gt; block add this method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnClear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;InitData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's not much to it, the method just calls our old &lt;code&gt;InitData()&lt;/code&gt; method which overwrite out &lt;code&gt;Life&lt;/code&gt; instance with a fresh one.&lt;/p&gt;

&lt;h4&gt;
  
  
  Saving game state
&lt;/h4&gt;

&lt;p&gt;Come on, admit it, you've grown fond of some of the critters and you want to play with them later.&lt;br&gt;
No problemo! we'll save the game state to a file. &lt;br&gt;
Below the existing &lt;code&gt;@using&lt;/code&gt; statements, add this one&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this gives us access to the new Json serializer from Asp.Net.&lt;/p&gt;

&lt;p&gt;Next step is to add yet another button like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="n"&gt;@onclick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;@OnDownload&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="nc"&gt;btn&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;primary&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;Save&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The referenced method should look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnDownload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cellsJsonStr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_life&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Cells&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"game state &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToShortDateString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToShortTimeString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;.json"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_jsRuntime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"downloadStringAsFile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cellsJsonStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method serializes the &lt;code&gt;Life&lt;/code&gt;s cells as a JSON string, generates a name for the file that will hold the saved game state and ... huh, Javascript interop again. &lt;br&gt;
You already know the drill. Add this new Javascript function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;downloadStringAsFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data:text/plain;charset=utf-8,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Javascript function uses a pretty standard way of downloading dynamically generated content.&lt;br&gt;
And bam! Your browser should inform you that a new file will be downloaded. Just save if somewhere you remember because we'll need it shortly.&lt;/p&gt;
&lt;h4&gt;
  
  
  Loading saved game state
&lt;/h4&gt;

&lt;p&gt;Loading saved game state will follow these high-level operations&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a file with the game state that you want to restore.&lt;/li&gt;
&lt;li&gt;Deserialize its content to a 2D array of &lt;code&gt;Cell&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;Create a new &lt;code&gt;Life&lt;/code&gt; instance with that 2D array of &lt;code&gt;Cells&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add this element just bellow the last button&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InputFile&lt;/span&gt; &lt;span class="n"&gt;OnChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"@OnSelectedFileChanged"&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="nc"&gt;btn&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;&amp;lt;/InputFile&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add the referenced method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnSelectedFileChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InputFileChangeEventArgs&lt;/span&gt; &lt;span class="n"&gt;eventArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;eventArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".json"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Stick to JSON files"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fileStream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eventArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenReadStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;deserializedCells&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeserializeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deserializedCells&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Couldn't deserialize the cells. So sad."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deserializedCells&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;Rows&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;deserializedCells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Expected to load cells with &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows and &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; columns"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;InitData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deserializedCells&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method is invoked when you click the &lt;code&gt;InputFile&lt;/code&gt; and pick a file. It makes sure you chose a JSON file and then opens a .NET Stream to the file content.&lt;br&gt;
Using the JSON Serializer from before, it tries to deserialize the file content to a &lt;code&gt;Cell[][]&lt;/code&gt; and if that works and the resulting 2D array of &lt;code&gt;Cell&lt;/code&gt;s has the correct shape it is passed to yet another method that should look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;InitData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_life&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Life&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;UpdateCellAndCanvasSize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This new method makes use of the second constructor of &lt;code&gt;Life&lt;/code&gt; which takes the &lt;code&gt;Cell&lt;/code&gt;s from 'outside' and just works with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For this second post in the series I had even more fun than before, but I also was frustrated a lot more than expected.&lt;br&gt;
I was disappointed by the bad interop performance while trying the two Blazor canvas libraries. That's not a fault of the libs, but of the general state of Blazor.&lt;br&gt;
Nonetheless with a little Javascript foo I got around that and the performance of this Blazor implementation is as good as that of the React implementation.&lt;/p&gt;

&lt;p&gt;Another way of improving the performance that I found early on was to use Ahead-of-Time compilation.&lt;br&gt;
For that you need to add this to your .csproj file, right inside the first &lt;code&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/code&gt; tag&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;RunAOTCompilation&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/RunAOTCompilation&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then publish a Release build of your app, which looks like this from the CLI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it complains about a missing workload, you can install it with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dotnet workload &lt;span class="nb"&gt;install &lt;/span&gt;wasm-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After 10 minutes you'll have an AOT-compiled app that should be much faster than the one you saw while developing.&lt;/p&gt;

&lt;p&gt;To try out the AOT version locally either use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if you have it, or if you're like me and like to stay in the .NET world as much as possible you can use &lt;code&gt;dotnet-serve&lt;/code&gt;. Fist install it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; dotnet-serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then navigate to the publish folder and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet-serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>gameoflife</category>
      <category>canvas</category>
    </item>
    <item>
      <title>Build the Game of Life with React</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Sun, 29 Aug 2021 13:53:33 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/build-the-game-of-life-with-react-5goh</link>
      <guid>https://dev.to/mariusmuntean/build-the-game-of-life-with-react-5goh</guid>
      <description>&lt;h3&gt;
  
  
  Motivation
&lt;/h3&gt;

&lt;p&gt;I've recently read a post about an interviewer who likes to ask of their candidates to implement &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life" rel="noopener noreferrer"&gt;Conway's Game of Life&lt;/a&gt;. Naturally I started thinking how I would do it. Since I'm intrigued by &lt;a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt; (because C#) and I use React at work (because it's better), here we are about to see how you can build the Game of Life, first with React and in a later post with Blazor.&lt;/p&gt;

&lt;p&gt;I plan to group these posts into a series, so that each stays digestible and you can read the one that interests you.&lt;/p&gt;

&lt;p&gt;Let me know in the comments if you're interested in seeing implementations in Xamarin.Forms/MAUI, WPF or Flutter.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy96bkbaz620taydfy6wx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy96bkbaz620taydfy6wx.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the code: &lt;a href="https://github.com/mariusmuntean/GameOfLife" rel="noopener noreferrer"&gt;https://github.com/mariusmuntean/GameOfLife&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Create the React project
&lt;/h2&gt;

&lt;p&gt;Create a new React project with &lt;code&gt;npx&lt;/code&gt;, give it a name and choose Typescript&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-react-app gol.react &lt;span class="nt"&gt;--template&lt;/span&gt; typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Business Logic
&lt;/h2&gt;

&lt;p&gt;In the &lt;code&gt;src&lt;/code&gt; directory, create a new one for the new types that you're going to add. I named mine &lt;code&gt;models&lt;/code&gt;. Add a file for an enum that represents the state of a single cell&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Dead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dead&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Alive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The game consists of a 2D grid where each slot is taken up by a cell. A cell can be either dead or alive. Now add the Cell class, ideally in another file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./CellState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;CurrentState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;NextState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currenCellState&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currenCellState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CurrentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currenCellState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;tick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CurrentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NextState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NextState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;toggle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CurrentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CurrentState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alive&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dead&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CurrentState&lt;/code&gt; of a Cell tells us how the cell is currently doing. Later we'll have to compute the new state of each Cell based on the state of its neighbors. To make the code simpler, I decided to store the next state of the Cell in the &lt;code&gt;NextState&lt;/code&gt; property.&lt;br&gt;
When the game is ready to transition each Cell into its next state, it can call &lt;code&gt;tick()&lt;/code&gt; on the Cell instance and the &lt;code&gt;NextState&lt;/code&gt; becomes the &lt;code&gt;CurrentState&lt;/code&gt;.&lt;br&gt;
The method &lt;code&gt;toggle()&lt;/code&gt; will allow us to click somewhere on the 2D grid and kill or revive a Cell.&lt;/p&gt;

&lt;p&gt;Let's talk about life. At the risk of sounding too reductionist, it's just a bunch of interacting cells. So we'll create one too&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Cell&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./CellState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EmptyCellsType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./EmptyCellsType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;InitialCellsType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./InitialCellsType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Life&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;onNewGeneration&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newCells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReadonlyArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReadonlyArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][];&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InitialCellsType&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;EmptyCellsType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onNewGeneration&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newCells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReadonlyArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReadonlyArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;InitialCellsType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialCells&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Width and height must be greater than 0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;col&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onNewGeneration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;onNewGeneration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what we just created. Life is a class that keeps track of a bunch of cells. For that we're using &lt;code&gt;_cells:Cell[][]&lt;/code&gt; which is just a 2D array of our simple &lt;code&gt;Cell&lt;/code&gt; class. Having a 2D array allows us to know exactly where each cell is and who its neighbors are.&lt;br&gt;
Traversing the 2D array can be cumbersome so I keep track of its dimensions with the properties &lt;code&gt;Rows&lt;/code&gt; and &lt;code&gt;Columns&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are two ways in which I want to be able to create a new &lt;code&gt;Life&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From scratch - meaning I jest tell it how many rows and columns of &lt;code&gt;Cell&lt;/code&gt;s I want and the &lt;code&gt;Life&lt;/code&gt; just initializes its 2D &lt;code&gt;_cells&lt;/code&gt; array with &lt;code&gt;Cell&lt;/code&gt;s in the &lt;code&gt;Dead&lt;/code&gt; state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For that, you need to add this new type&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmptyCellsType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It just holds a pair of numbers corresponding to the desired amount of &lt;code&gt;Cell&lt;/code&gt; rows and columns.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From a file - think of a saved game state. We'll later save the state of the game into a file and then load it up. When loading the saved game state, we need to tell the &lt;code&gt;Life&lt;/code&gt; instance what each of its &lt;code&gt;Cell&lt;/code&gt;'s state should be.
For now, just create this new class
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Cell&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InitialCellsType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;initialCells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we can create a new &lt;code&gt;Life&lt;/code&gt;, where all the cells are either dead or in a state that we received from 'outside'.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;Life&lt;/code&gt; needs a bit more functionality and then it is complete. The very first time we load up the game, all the cells will be dead. So it would be nice to be able to just breathe some life into the dead cells.&lt;br&gt;
For that, &lt;code&gt;Life&lt;/code&gt; needs a method that takes the location of a &lt;code&gt;Cell&lt;/code&gt; and toggles its state to the opposite value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;toggle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Row is out of range&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Col is out of range&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cellToToggle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;cellToToggle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Life&lt;/code&gt; instance just makes sure that the specified location of the &lt;code&gt;Cell&lt;/code&gt; makes sense and then tells that Cell to toggle its state. If you remember, the &lt;code&gt;Cell&lt;/code&gt; class can toggle its state, if told to do so.&lt;/p&gt;

&lt;p&gt;The last and most interesting method of &lt;code&gt;Life&lt;/code&gt; implements the 3  &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Rules" rel="noopener noreferrer"&gt;rules&lt;/a&gt; of the Game of Life.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Any live cell with two or three live neighbours survives.&lt;/li&gt;
&lt;li&gt;Any dead cell with three live neighbours becomes a live cell.&lt;/li&gt;
&lt;li&gt;All other live cells die in the next generation. Similarly, all other dead cells stay dead.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;tick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Compute the next state for each cell&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;col&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentCell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cellNeighbors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNeighbors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;liveNeighbors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cellNeighbors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;neighbor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;neighbor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CurrentState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alive&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Rule source - https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Rules&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CurrentState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alive&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;liveNeighbors&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;liveNeighbors&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NextState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CurrentState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dead&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;liveNeighbors&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NextState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NextState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Switch each cell to its next state&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;col&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentCell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nx"&gt;currentCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onNewGeneration&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;getNeighbors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;neighbors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;colOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;colOffset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;colOffset&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;rowOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;rowOffset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;rowOffset&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colOffset&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;rowOffset&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// skip self&lt;/span&gt;
          &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;neighborRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;rowOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;neighborCol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;colOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;neighborRow&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;neighborRow&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;neighborCol&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;neighborCol&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;neighbors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;neighborRow&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;neighborCol&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;neighbors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me quickly walk you through the code. I'm traversing the 2D array of &lt;code&gt;Cell&lt;/code&gt;s, making use of the rows and columns. For each cell I'm looking at its neighbors and based on the 3 game rules I'm computing the next state of the &lt;code&gt;Cell&lt;/code&gt;.&lt;br&gt;
When I'm done with that, I'm traversing the 2D grid again (I know, not very efficient of me, but I went for readable code) and telling each &lt;code&gt;Cell&lt;/code&gt; to switch to its next state.&lt;/p&gt;

&lt;p&gt;You might be wondering what this &lt;code&gt;onNewGeneration()&lt;/code&gt; function  is good for. Well, at this point in time I had no idea how the UI will function and I imagined that it would be nice to have a callback that lets me know when all the &lt;code&gt;Cell&lt;/code&gt;s were updated to their new state. It just so happens that we don't need that callback after all.&lt;/p&gt;

&lt;p&gt;We're done with the business logic. It's time for the UI.&lt;/p&gt;
&lt;h2&gt;
  
  
  UI
&lt;/h2&gt;

&lt;p&gt;In the &lt;code&gt;src&lt;/code&gt; directory, create a new directory called &lt;code&gt;SimpleLifeComponent&lt;/code&gt;. Inside this new directory create an &lt;code&gt;index.ts&lt;/code&gt; file with this content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SimpleLife&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./simple-life.component&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Immediately after that, add a new file called &lt;code&gt;simple-life.component.tsx&lt;/code&gt; next to the &lt;code&gt;index.ts&lt;/code&gt; (this way VS Code will stop yelling at you that it can't find the referenced file).&lt;/p&gt;

&lt;h4&gt;
  
  
  KonvaJs
&lt;/h4&gt;

&lt;p&gt;After some decent (10 minutes, but with my noise-cancelling headphones on) research (googled '2D drawing in React') of my own, I decided to go with &lt;a href="https://www.konvajs.org" rel="noopener noreferrer"&gt;KonvaJs&lt;/a&gt;.&lt;br&gt;
It has excellent support for React. Take a look at this snippet from their &lt;a href="https://konvajs.org/docs/react/Intro.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt; and you'll be ready to draw in no time&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Circle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-konva&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// Stage - is a div wrapper&lt;/span&gt;
    &lt;span class="c1"&gt;// Layer - is an actual 2d canvas element, so you can have several layers inside the stage&lt;/span&gt;
    &lt;span class="c1"&gt;// Rect and Circle are not DOM elements. They are 2d shapes on canvas&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Stage&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Rect&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Circle&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;stroke&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;black&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Layer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Stage&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, all you have to do is install it like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-konva konva
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  SimpleLife
&lt;/h4&gt;

&lt;p&gt;This is going to be the component that takes care of rendering the game and it will allow us to interact with the game. As usual, it is possible to break up a React component in multiple smaller ones, but my intention was for YOU to see as much code as possible, at a glance.&lt;/p&gt;

&lt;p&gt;Start by adding these imports&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Rect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-konva&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../models/Cell&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../models/CellState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Life&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../models/Life&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;InitialCellsType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../models/InitialCellsType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EmptyCellsType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../models/EmptyCellsType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy here, just the normal React imports, &lt;em&gt;Konva&lt;/em&gt; and our own types.&lt;/p&gt;

&lt;p&gt;Next step is to add the props type&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component will receive the number of rows and columns that define how many cells there's going to be. It also takes a width and a height, in pixels. The pixel dimensions tell our component how much space it has for its cells and it will 'fit' the cells in the available space. Don't overthink it, I didn't 😁.&lt;/p&gt;

&lt;p&gt;We will need an instance of &lt;code&gt;Life&lt;/code&gt; when the component lights up the very first time. For that, add this next function just below the &lt;code&gt;Props&lt;/code&gt; interface&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getInitialLife&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onNewGeneration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newCells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReadonlyArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReadonlyArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Life&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Life&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialLife&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Life&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;EmptyCellsType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onNewGeneration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Glider&lt;/span&gt;
    &lt;span class="nx"&gt;initialLife&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;initialLife&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;initialLife&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;initialLife&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;initialLife&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;initialLife&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function doesn't do much, but it's honest work. It takes the number of rows and columns (and that unused callback I mentioned above) and returns a function that returns a &lt;code&gt;Life&lt;/code&gt; with the specified amount of rows and columns. It also toggles some of the &lt;code&gt;Cell&lt;/code&gt;s to the &lt;code&gt;Alive&lt;/code&gt; state. The shaped those live cells make is a &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Examples_of_patterns" rel="noopener noreferrer"&gt;canonical shape&lt;/a&gt; and is called a 'Glider' because, as you will see, they will glide through the 2D space.&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;SimpleLife&lt;/code&gt; component, below the previous function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SimpleLife&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onNewGeneration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;newCells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReadonlyArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReadonlyArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// console.dir(newCells);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;life&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLife&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Life&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getInitialLife&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onNewGeneration&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="nx"&gt;updateState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forceUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;updateState&lt;/span&gt;&lt;span class="p"&gt;({}),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onCellClicked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;forceUpdate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cellEdgeAndSpacingLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cellEdgeLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;cellEdgeAndSpacingLength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvasWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cellEdgeAndSpacingLength&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvasHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cellEdgeAndSpacingLength&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Stage&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canvasWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canvasHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;life&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cellRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rowIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cellRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columnIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Rect&lt;/span&gt;
                    &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;rowIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;columnIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
                    &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columnIndex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;cellEdgeAndSpacingLength&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rowIndex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;cellEdgeAndSpacingLength&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cellEdgeLength&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cellEdgeLength&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CurrentState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;CellState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alive&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;black&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onCellClicked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rowIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columnIndex&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Rect&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;})}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Layer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Stage&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break it down. &lt;br&gt;
The component has a &lt;code&gt;Life&lt;/code&gt; instance, which is its internal state. It is created with the &lt;code&gt;getInitialLife()&lt;/code&gt; function that you added jus above the component. &lt;br&gt;
The &lt;code&gt;forceUpdate()&lt;/code&gt; is just a little trick that allows us to force re-rendering.&lt;/p&gt;

&lt;p&gt;Next up is the 4 lines with the computation. Their goal is to obtain the optimal cell edge length and canvas size, given the amount of rows and columns and the available space for our component.&lt;/p&gt;

&lt;p&gt;Finally some TSX. Inside a &lt;code&gt;&amp;lt;Stage&amp;gt;&lt;/code&gt;, which is a wrapper &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; for the canvas, I'm adding a &lt;code&gt;&amp;lt;Layer&amp;gt;&lt;/code&gt; (Konva renders this as an HTML canvas) that contains many rectangles, one rectangle for each of our &lt;code&gt;Cell&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;Remember that &lt;code&gt;life.cells&lt;/code&gt; is an array of arrays of &lt;code&gt;Cell&lt;/code&gt;. So there I'm using two nested calls to &lt;code&gt;map()&lt;/code&gt; that allow me to traverse the whole data structure and emit a new Konva &lt;code&gt;&amp;lt;Rect&amp;gt;&lt;/code&gt; for each &lt;code&gt;Cell&lt;/code&gt;. &lt;br&gt;
&lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are the &lt;code&gt;&amp;lt;Rect&amp;gt;&lt;/code&gt;'s pixel coordinates on the final canvas and &lt;code&gt;with&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; are the &lt;code&gt;&amp;lt;Rect&amp;gt;&lt;/code&gt;'s pixel dimensions. A &lt;code&gt;&amp;lt;Rect&amp;gt;&lt;/code&gt; will be ⬛️ when the &lt;code&gt;Cell&lt;/code&gt; is dead and 🟥 when the &lt;code&gt;Cell&lt;/code&gt; is alive. I've also wired up the &lt;code&gt;&amp;lt;Rect&amp;gt;&lt;/code&gt;'s &lt;code&gt;onClick&lt;/code&gt; handler to call our &lt;code&gt;onCellClicked()&lt;/code&gt; function, which tells the &lt;code&gt;Life&lt;/code&gt; instance to toggle the appropriate &lt;code&gt;Cell&lt;/code&gt;'s state.&lt;/p&gt;

&lt;p&gt;To actually see something on the screen, use the &lt;code&gt;&amp;lt;SimpleLife&amp;gt;&lt;/code&gt; component in the &lt;code&gt;App.tsx&lt;/code&gt; file. Something like this should work&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SimpleLife&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./SimpleLifeComponent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SimpleLife&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
                     &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
                     &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
                     &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SimpleLife&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point you should be able to see the game and click cells to toggle their state.&lt;/p&gt;

&lt;h5&gt;
  
  
  It's alive!
&lt;/h5&gt;

&lt;p&gt;Let's add a button that tells the &lt;code&gt;Life&lt;/code&gt; instance to progress to the next generation of &lt;code&gt;Cell&lt;/code&gt; states.&lt;br&gt;
Back in the &lt;code&gt;SimpleLife&lt;/code&gt; component, bellow &lt;code&gt;onCellClicked()&lt;/code&gt;, add this function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onTick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;forceUpdate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the TSX, below the closing &lt;code&gt;Stage&lt;/code&gt; tag (&lt;code&gt;&amp;lt;/Stage&amp;gt;&lt;/code&gt;) add this line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onTick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Tick&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open the link with &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Examples_of_patterns" rel="noopener noreferrer"&gt;canonical shapes&lt;/a&gt; in the Game of Life in a new browser window and create a few shapes by clicking in your game. By clicking the new button that you added, you should see how your shapes are doing in the Game of Life.&lt;/p&gt;

&lt;h5&gt;
  
  
  Oh my!
&lt;/h5&gt;

&lt;p&gt;Let's add a new button to clean up the mess you made :D&lt;br&gt;
First add this new function below onTick()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onClear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setLife&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getInitialLife&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onNewGeneration&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and this line of TSX below the previous button&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onClear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Clear&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you're able to clear the board and get the Glider back.&lt;/p&gt;

&lt;h5&gt;
  
  
  I'll save you, my little creatures, 4 ever!
&lt;/h5&gt;

&lt;p&gt;"Wouldn't it be nice to be able to save the game state and reload it later?" I hear you ask. Excellent question and yes, that would be nice!&lt;/p&gt;

&lt;p&gt;Let's start by preparing some infrastructure code. In your &lt;code&gt;src&lt;/code&gt; directory, add a new one and call it &lt;code&gt;utils&lt;/code&gt;. Inside utils create a file called &lt;code&gt;download.ts&lt;/code&gt; and add this function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;href&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data:text/plain;charset=utf-8,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;download&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function takes a file name and some text, and tells your browser that it wants to save that text as a file with the specified name.&lt;/p&gt;

&lt;p&gt;Back in the &lt;code&gt;SimpleLife&lt;/code&gt; component, add this import&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./../utils/download&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add this function below &lt;code&gt;onClear()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onSave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`game state &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toTimeString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;life&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, add this button to the TSX, just below the other buttons&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onSave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, whenever you have an assortment of creatures that you're particularly fond of, you can save them as a JSON file.&lt;/p&gt;

&lt;p&gt;"But how can I get them back?" Go back to &lt;code&gt;download.ts&lt;/code&gt; and add this function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pickFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onLoadedSuccessfully&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePickerInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;filePickerInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;filePickerInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;filePickerInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file-input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;filePickerInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accept&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;filePickerInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;filePickerInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filereader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;filereader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onloadend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nf"&gt;onLoadedSuccessfully&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filereader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;filereader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsText&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePickerInput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;filePickerInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePickerInput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When invoked, it opens the browser's file picker dialog and lets your callback know whenever you pick a JSON file.&lt;br&gt;
Back in &lt;code&gt;SimpleLife&lt;/code&gt;, adjust the previous import to look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pickFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./../utils/download&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add this nasty little function, below &lt;code&gt;onSave()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onLoad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;pickFile&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reloadedCellData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;reloadedCellData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reloadedCellsMissingPrototypeChain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reloadedCellData&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;reloadedCellsMissingPrototypeChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;reconstructed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reloadedCellsMissingPrototypeChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reloadedCellsMissingPrototypeChain&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;reconstructed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reconstructed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;col&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;reconstructed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reloadedCellsMissingPrototypeChain&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;CurrentState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;initialCell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InitialCellsType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InitialCellsType&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;initialCell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialCells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reconstructed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;setLife&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Life&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialCell&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It triggers the file picker and when the right file is selected it will deserialize it into an instance of &lt;code&gt;Cell[][]&lt;/code&gt;. Unfortunately, the deserialized object is lacking type information, which Typescript needs. So I'm just looping over the data and creating a proper &lt;code&gt;Cell[][]&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;finally, add yet another button to the TSX&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Load&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now you can load previous game states that you saved.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;I had fun building this little game and I hope you had too. KonvaJs turned out to be an excellent little library and now I can't stop thinking about my next drawing adventure in React.&lt;/p&gt;

&lt;p&gt;Keep your eyes open for new posts in this series. &lt;a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt; should be next!&lt;/p&gt;

</description>
      <category>react</category>
      <category>konvajs</category>
      <category>gameoflife</category>
    </item>
    <item>
      <title>Explore the SpaceX GraphQL API by building a Xamarin Forms app</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Tue, 02 Mar 2021 22:47:20 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/explore-the-spacex-graphql-api-by-building-a-xamarin-forms-app-424</link>
      <guid>https://dev.to/mariusmuntean/explore-the-spacex-graphql-api-by-building-a-xamarin-forms-app-424</guid>
      <description>&lt;h3&gt;
  
  
  What's this about
&lt;/h3&gt;

&lt;p&gt;We're going to build a Xamarin.Forms app that connects to the SpaceX GraphQL API and displays some cool rocket launch pictures. You'll see how to generate C# classes for typesafe coding, send queries with the GraphQLHttpClient, build an elegant UI in XAML and use a Xamarin.Forms Behavior.&lt;/p&gt;

&lt;p&gt;3&lt;br&gt;
2&lt;br&gt;
1&lt;br&gt;
🚀&lt;/p&gt;
&lt;h3&gt;
  
  
  Generate types from the GraphQl schema
&lt;/h3&gt;

&lt;p&gt;Here's the endpoint where you can get the schema from &lt;a href="https://api.spacex.land/graphql" rel="noopener noreferrer"&gt;https://api.spacex.land/graphql&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have your own way of generating the types, you can skip this section but this is how I did it.&lt;/p&gt;

&lt;p&gt;In a folder of your choice, initialize a new yarn project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, install these dev dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add -D graphql @graphql-codegen/c-sharp @graphql-codegen/cli @graphql-codegen/introspection @graphql-codegen/typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the following script property to your &lt;code&gt;package.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"generate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"graphql-codegen --config codegen.yml"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're done with the packages.json and we can move on to configuring the node module that will take care of generating the classes.&lt;/p&gt;

&lt;p&gt;Here's how that YAML file needs to look like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;overwrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.spacex.land/graphql"&lt;/span&gt;
&lt;span class="c1"&gt;#documents: "src/**/*.graphql"&lt;/span&gt;
&lt;span class="na"&gt;generates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gen/Models.cs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c-sharp"&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;namespaceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SpaceXGraphQL.SpaceX.gen&lt;/span&gt;
      &lt;span class="na"&gt;scalars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;timestamptz&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DateTime&lt;/span&gt;
        &lt;span class="na"&gt;uuid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Guid&lt;/span&gt;
  &lt;span class="na"&gt;./graphql.schema.json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;introspection"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me translate: get the schema from &lt;a href="https://api.spacex.land/graphql" rel="noopener noreferrer"&gt;https://api.spacex.land/graphql&lt;/a&gt;, using the "c-sharp" plugin generate classes with the namespace &lt;code&gt;SpaceXGraphQL.SpaceX.gen&lt;/code&gt; into the file (relative to self) &lt;code&gt;gen/Models.cs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;scalars&lt;/code&gt; property is a mapping between custom types from the GraphQL schema and matching C# types. It looks like they use Postgres under the hood. &lt;code&gt;timestamptz&lt;/code&gt; stores date and time info including the time zone and &lt;code&gt;uuid&lt;/code&gt; is just a 128 -bit quantity for identifying entities.&lt;/p&gt;

&lt;p&gt;
  GraphiQL (&lt;a href="https://api.spacex.land/graphql/" rel="noopener noreferrer"&gt;https://api.spacex.land/graphql/&lt;/a&gt;)
  &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzafq46qqwjpqo4bfha8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzafq46qqwjpqo4bfha8.png" alt="uuid"&gt;&lt;/a&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4mn7nd6unql5b7ag2v3z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4mn7nd6unql5b7ag2v3z.png" alt="timestamp"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Now install the referenced dependencies with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run the script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our classes were generated! Yay! 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea5tbqu7yhpwryccql9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea5tbqu7yhpwryccql9f.png" alt="Generated models"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thats it! We now have types to work with.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Write the Xamarin.Forms App
&lt;/h3&gt;

&lt;p&gt;Create a new Xamarin.Forms app and add the following NuGet packages&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"GraphQL.Client"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.2.1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"GraphQL.Client.Serializer.Newtonsoft"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.2.1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Newtonsoft.Json"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"12.0.3"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Xamarin.Essentials"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.6.1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"GraphQL.Client.Serializer.Newtonsoft"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"3.2.2"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the generated classes to your shared project. I actually added the whole yarn project folder&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86sb8c5yrnt6rq3ifcs1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86sb8c5yrnt6rq3ifcs1.png" alt="generated models"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's have fun!&lt;/p&gt;

&lt;p&gt;Set up this global style&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Application&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://xamarin.com/schemas/2014/forms"&lt;/span&gt;
             &lt;span class="na"&gt;xmlns:x=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/winfx/2009/xaml"&lt;/span&gt;
             &lt;span class="na"&gt;x:Class=&lt;/span&gt;&lt;span class="s"&gt;"SpaceXGraphQL.App"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Application.Resources&amp;gt;&lt;/span&gt;

        &lt;span class="c"&gt;&amp;lt;!-- Styles --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Style&lt;/span&gt; &lt;span class="na"&gt;TargetType=&lt;/span&gt;&lt;span class="s"&gt;"NavigationPage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Setter&lt;/span&gt; &lt;span class="na"&gt;Property=&lt;/span&gt;&lt;span class="s"&gt;"BarBackgroundColor"&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"#384355"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Setter&lt;/span&gt; &lt;span class="na"&gt;Property=&lt;/span&gt;&lt;span class="s"&gt;"BarTextColor"&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"#FF543D"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Style&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Styles --&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/Application.Resources&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app will have two pages. &lt;br&gt;
The first page shows a list of rocket launches, uses infinite scrolling and shows a pulsing indicator for "upcoming" launches.&lt;/p&gt;

&lt;p&gt;To keep things simple I didn't use any third party UI library nor did I integrate Prism or MvvmCross, though I'm a big fan of those.&lt;/p&gt;

&lt;p&gt;Start with the view model, I named mine &lt;code&gt;LaunchesPageViewModel&lt;/code&gt;. It implements &lt;code&gt;INotifyPropertyChanged&lt;/code&gt; like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;PropertyChangedEventHandler&lt;/span&gt; &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NotifyPropertyChangedInvocator&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnPropertyChanged&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;CallerMemberName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;propertyName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;PropertyChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PropertyChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It needs a few properties to hold the rocket launches, to indicate that it is fetching data and a command to load the next batch/page of launches&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Command&lt;/span&gt; &lt;span class="n"&gt;LoadMoreCommand&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_loadMoreCommand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;set&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_loadMoreCommand&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nf"&gt;OnPropertyChanged&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsLoading&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_isLoading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;set&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nf"&gt;OnPropertyChanged&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ObservableCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Launch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Launches&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_launches&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;set&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_launches&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nf"&gt;OnPropertyChanged&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, in the constructor I'm doing a bit of init work. The &lt;code&gt;LoadMoreCommand&lt;/code&gt; fetches rocket launches and, in case that it is executed again while the previous fetching isn't done, it just returns. When I'm done with the init, I start fetching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;LaunchesPageViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GraphQLHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.spacex.land/graphql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NewtonsoftJsonSerializer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;Launches&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ObservableCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Launch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="n"&gt;LoadMoreCommand&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isFetchingMore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;try&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;_isFetchingMore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetLaunches&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;finally&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;_isFetchingMore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="n"&gt;IsLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nf"&gt;GetLaunches&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ContinueWith&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;IsLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now for the last piece, the actual GraphQL query. We're making use of the "launches" query, which allows use to tell it how many launch events to return and how many to skip, effectively offering a paginated api.&lt;br&gt;
Notice that I'm using the amount of launches that I currently have as offset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;GetLaunches&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;launchesRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;GraphQLRequest&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"query getLaunches($limit: Int, $offset: Int)
                                {
                                  launches(limit: $limit, offset: $offset) {
                                    id
                                    is_tentative
                                    upcoming                                    
                                    mission_name
                                    links {
                                      article_link
                                      video_link
                                      flickr_images
                                      mission_patch
                                    }
                                    launch_date_utc
                                    details
                                  }
                                }"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Launches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendQueryAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;launchesRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errors&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphQLError&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()).&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;launch&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;launches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Launches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's all for the &lt;code&gt;LaunchesPageViewModel&lt;/code&gt;.&lt;br&gt;
The &lt;code&gt;LaunchesPage&lt;/code&gt;, is implemented exclusively in XAML, except for a selection handler.&lt;/p&gt;

&lt;p&gt;Basically I'm making use of a &lt;a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/introduction" rel="noopener noreferrer"&gt;CollectionView&lt;/a&gt; (for its virtualization) to show the launches. Notice the properties &lt;code&gt;RemainingItemsThreshold&lt;/code&gt; and &lt;code&gt;RemainingItemsThresholdReachedCommand&lt;/code&gt;. Whenever theres 5 items or fewer to show, the &lt;code&gt;CollectionView&lt;/code&gt; calls the &lt;code&gt;LoadMoreCommand&lt;/code&gt;, which sends a query to the GraphQL API to fetch the next batch of launches.&lt;/p&gt;

&lt;p&gt;
  LaunchesPage.xaml
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Loading --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ActivityIndicator&lt;/span&gt; &lt;span class="na"&gt;IsVisible=&lt;/span&gt;&lt;span class="s"&gt;"{Binding IsLoading}"&lt;/span&gt;
                           &lt;span class="na"&gt;IsRunning=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;
                           &lt;span class="na"&gt;Color=&lt;/span&gt;&lt;span class="s"&gt;"#FF543D"&lt;/span&gt;
                           &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Center"&lt;/span&gt; &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Center"&lt;/span&gt;
                           &lt;span class="na"&gt;WidthRequest=&lt;/span&gt;&lt;span class="s"&gt;"120"&lt;/span&gt; &lt;span class="na"&gt;HeightRequest=&lt;/span&gt;&lt;span class="s"&gt;"120"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

        &lt;span class="c"&gt;&amp;lt;!-- Launches --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;CollectionView&lt;/span&gt; &lt;span class="na"&gt;ItemsSource=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Launches}"&lt;/span&gt;
                        &lt;span class="na"&gt;ItemSizingStrategy=&lt;/span&gt;&lt;span class="s"&gt;"MeasureFirstItem"&lt;/span&gt;
                        &lt;span class="na"&gt;SelectionMode=&lt;/span&gt;&lt;span class="s"&gt;"Single"&lt;/span&gt;
                        &lt;span class="na"&gt;SelectionChanged=&lt;/span&gt;&lt;span class="s"&gt;"OnLaunchSelected"&lt;/span&gt;
                        &lt;span class="na"&gt;RemainingItemsThreshold=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;
                        &lt;span class="na"&gt;RemainingItemsThresholdReachedCommand=&lt;/span&gt;&lt;span class="s"&gt;"{Binding LoadMoreCommand}"&lt;/span&gt;
                        &lt;span class="na"&gt;Margin=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;CollectionView.ItemsLayout&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;LinearItemsLayout&lt;/span&gt; &lt;span class="na"&gt;Orientation=&lt;/span&gt;&lt;span class="s"&gt;"Vertical"&lt;/span&gt;
                                   &lt;span class="na"&gt;ItemSpacing=&lt;/span&gt;&lt;span class="s"&gt;"5.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/CollectionView.ItemsLayout&amp;gt;&lt;/span&gt;

            &lt;span class="c"&gt;&amp;lt;!-- Launch Template --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;CollectionView.ItemTemplate&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;DataTemplate&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;Frame&lt;/span&gt; &lt;span class="na"&gt;CornerRadius=&lt;/span&gt;&lt;span class="s"&gt;"15"&lt;/span&gt;
                           &lt;span class="na"&gt;Padding=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Margin=&lt;/span&gt;&lt;span class="s"&gt;"5,0,5,0"&lt;/span&gt;
                           &lt;span class="na"&gt;HasShadow=&lt;/span&gt;&lt;span class="s"&gt;"False"&lt;/span&gt;
                           &lt;span class="na"&gt;BackgroundColor=&lt;/span&gt;&lt;span class="s"&gt;"#041727"&lt;/span&gt;
                           &lt;span class="na"&gt;IsClippedToBounds=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;Grid&lt;/span&gt; &lt;span class="na"&gt;RowDefinitions=&lt;/span&gt;&lt;span class="s"&gt;"*,*"&lt;/span&gt;
                              &lt;span class="na"&gt;ColumnDefinitions=&lt;/span&gt;&lt;span class="s"&gt;"Auto,*"&lt;/span&gt;
                              &lt;span class="na"&gt;HeightRequest=&lt;/span&gt;&lt;span class="s"&gt;"80"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

                            &lt;span class="c"&gt;&amp;lt;!-- Mission patch --&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;Image&lt;/span&gt; &lt;span class="na"&gt;HeightRequest=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt; &lt;span class="na"&gt;WidthRequest=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt;
                                   &lt;span class="na"&gt;Margin=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;
                                   &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Grid.RowSpan=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;
                                   &lt;span class="na"&gt;Aspect=&lt;/span&gt;&lt;span class="s"&gt;"AspectFit"&lt;/span&gt;
                                   &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"CenterAndExpand"&lt;/span&gt;
                                   &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"CenterAndExpand"&lt;/span&gt;
                                   &lt;span class="na"&gt;Source=&lt;/span&gt;&lt;span class="s"&gt;"{Binding links.mission_patch}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;
                                   &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"End"&lt;/span&gt;
                                   &lt;span class="na"&gt;TextColor=&lt;/span&gt;&lt;span class="s"&gt;"White"&lt;/span&gt;
                                   &lt;span class="na"&gt;FontAttributes=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&lt;/span&gt;
                                   &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"{Binding mission_name}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;
                                   &lt;span class="na"&gt;FontSize=&lt;/span&gt;&lt;span class="s"&gt;"Small"&lt;/span&gt;
                                   &lt;span class="na"&gt;TextColor=&lt;/span&gt;&lt;span class="s"&gt;"#FF543D"&lt;/span&gt;
                                   &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Start"&lt;/span&gt;
                                   &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"{Binding launch_date_utc}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

                            &lt;span class="c"&gt;&amp;lt;!-- Upcoming indicator --&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;BoxView&lt;/span&gt; &lt;span class="na"&gt;Grid.Row=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Grid.RowSpan=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;Grid.Column=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;
                                     &lt;span class="na"&gt;HeightRequest=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;WidthRequest=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;
                                     &lt;span class="na"&gt;Margin=&lt;/span&gt;&lt;span class="s"&gt;"20"&lt;/span&gt;
                                     &lt;span class="na"&gt;CornerRadius=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;
                                     &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"End"&lt;/span&gt; &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Center"&lt;/span&gt;
                                     &lt;span class="na"&gt;Color=&lt;/span&gt;&lt;span class="s"&gt;"#FF543D"&lt;/span&gt;
                                     &lt;span class="na"&gt;IsVisible=&lt;/span&gt;&lt;span class="s"&gt;"False"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;BoxView.Behaviors&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;behaviors:PulseBehavior&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;/BoxView.Behaviors&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;BoxView.Triggers&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;DataTrigger&lt;/span&gt; &lt;span class="na"&gt;TargetType=&lt;/span&gt;&lt;span class="s"&gt;"BoxView"&lt;/span&gt;
                                                 &lt;span class="na"&gt;Binding=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Path=upcoming}"&lt;/span&gt;
                                                 &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                                        &lt;span class="nt"&gt;&amp;lt;DataTrigger.Setters&amp;gt;&lt;/span&gt;
                                            &lt;span class="nt"&gt;&amp;lt;Setter&lt;/span&gt; &lt;span class="na"&gt;Property=&lt;/span&gt;&lt;span class="s"&gt;"IsVisible"&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                                        &lt;span class="nt"&gt;&amp;lt;/DataTrigger.Setters&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;/DataTrigger&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;/BoxView.Triggers&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/BoxView&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/Grid&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/Frame&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/DataTemplate&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/CollectionView.ItemTemplate&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/CollectionView&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  LaunchesPage.xaml.cs
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XamlCompilation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;XamlCompilationOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Compile&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LaunchesPage&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContentPage&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;LaunchesPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;InitializeComponent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;BindingContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LaunchesPageViewModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnLaunchSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SelectionChangedEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentSelection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentSelection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Launch&lt;/span&gt; &lt;span class="n"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Navigation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PushAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LaunchPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LaunchPageViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;CollectionView&lt;/span&gt; &lt;span class="n"&gt;cw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;cw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SelectedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  PulseBehavior.cs
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xamarin.Forms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xamarin.Forms.Internals&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SpaceXGraphQL.Behaviors&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PulseBehavior&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Behavior&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;PulsingAnimation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Pulsing"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_running&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_isReversed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnAttachedTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="n"&gt;bindable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnAttachedTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bindable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;_running&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;bindable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Animate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PulsingAnimation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newScale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_isReversed&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="m"&gt;1.0d&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;bindable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newScale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;easing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Easing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CubicInOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;_isReversed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;_isReversed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_running&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnDetachingFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="n"&gt;bindable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnDetachingFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bindable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;_running&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;bindable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AbortAnimation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PulsingAnimation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;We're done with the first page and the result looks like this&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn626ska3wth2nfjzdznj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn626ska3wth2nfjzdznj.png" alt="Launches overview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now for the second page. It is going to show some launch details like the coolest images of the launch, the launch name and patch, a link to the original article and a way of sharing the images.&lt;/p&gt;

&lt;p&gt;We're starting with the view model which I named &lt;code&gt;LaunchPageViewModel&lt;/code&gt;. It uses the exact same &lt;code&gt;INotifyPropertyChanged&lt;/code&gt; implementation, so I'm skipping over it here.&lt;br&gt;
This view model needs properties to hold the data of a single launch and commands to open the original article link and to share a launch image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Launch&lt;/span&gt; &lt;span class="n"&gt;Launch&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_launch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;set&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_launch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nf"&gt;OnPropertyChanged&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Command&lt;/span&gt; &lt;span class="n"&gt;ArticleLinkTappedCommand&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Command&lt;/span&gt; &lt;span class="n"&gt;ImageTappedCommand&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the constructor, the view model initializes its members and the two commands, making use of Xamarin Essentials for opening links and sharing data.&lt;br&gt;
You'll notice that the constructor take as argument a launch id. That's the id of the launch whose data we're querying and showing.&lt;/p&gt;

&lt;p&gt;Finally, here's how the GetLaunch() method looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;GetLaunch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Launch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;launchRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;GraphQLRequest&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"query getLaunch($id: ID!) {
                                      launch(id: $id) {
                                        id
                                        is_tentative
                                        upcoming
                                        mission_name
                                        links {
                                          article_link
                                          video_link
                                          flickr_images
                                          mission_patch
                                        }
                                        launch_date_utc
                                        details
                                      }
                                    }"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Variables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_launchId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendQueryAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;launchRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errors&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphQLError&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()).&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Launch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the page is almost exclusively implemented in XAML.&lt;br&gt;

  LaunchPage.xaml
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;ContentPage&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://xamarin.com/schemas/2014/forms"&lt;/span&gt;
             &lt;span class="na"&gt;xmlns:x=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/winfx/2009/xaml"&lt;/span&gt;
             &lt;span class="na"&gt;xmlns:converters=&lt;/span&gt;&lt;span class="s"&gt;"clr-namespace:SpaceXGraphQL.Converters;assembly=SpaceXGraphQL"&lt;/span&gt;
             &lt;span class="na"&gt;x:Class=&lt;/span&gt;&lt;span class="s"&gt;"SpaceXGraphQL.LaunchPage"&lt;/span&gt;
             &lt;span class="na"&gt;BackgroundColor=&lt;/span&gt;&lt;span class="s"&gt;"#041727"&lt;/span&gt;
             &lt;span class="na"&gt;x:Name=&lt;/span&gt;&lt;span class="s"&gt;"ParentPage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;ContentPage.Resources&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;converters:GetLastItemConverter&lt;/span&gt; &lt;span class="na"&gt;x:Key=&lt;/span&gt;&lt;span class="s"&gt;"GetLastItemConverter"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ContentPage.Resources&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;ScrollView&lt;/span&gt; &lt;span class="na"&gt;Padding=&lt;/span&gt;&lt;span class="s"&gt;"20, 0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;StackLayout&amp;gt;&lt;/span&gt;
            &lt;span class="c"&gt;&amp;lt;!-- Top image --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Grid&lt;/span&gt; &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Start"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;Frame&lt;/span&gt; &lt;span class="na"&gt;CornerRadius=&lt;/span&gt;&lt;span class="s"&gt;"20"&lt;/span&gt;
                       &lt;span class="na"&gt;Padding=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
                       &lt;span class="na"&gt;HasShadow=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;
                       &lt;span class="na"&gt;IsClippedToBounds=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;Image&lt;/span&gt; &lt;span class="na"&gt;Source=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Launch.links.flickr_images, Converter={StaticResource GetLastItemConverter}}"&lt;/span&gt;
                           &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"FillAndExpand"&lt;/span&gt;
                           &lt;span class="na"&gt;BackgroundColor=&lt;/span&gt;&lt;span class="s"&gt;"#384355"&lt;/span&gt;
                           &lt;span class="na"&gt;Aspect=&lt;/span&gt;&lt;span class="s"&gt;"AspectFill"&lt;/span&gt;
                           &lt;span class="na"&gt;HeightRequest=&lt;/span&gt;&lt;span class="s"&gt;"250"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/Frame&amp;gt;&lt;/span&gt;

                &lt;span class="c"&gt;&amp;lt;!-- Patch, name and date --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;StackLayout&lt;/span&gt; &lt;span class="na"&gt;Orientation=&lt;/span&gt;&lt;span class="s"&gt;"Horizontal"&lt;/span&gt;
                             &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Start"&lt;/span&gt; &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Start"&lt;/span&gt;
                             &lt;span class="na"&gt;Margin=&lt;/span&gt;&lt;span class="s"&gt;"20"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;Image&lt;/span&gt; &lt;span class="na"&gt;Source=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Launch.links.mission_patch}"&lt;/span&gt;
                           &lt;span class="na"&gt;Aspect=&lt;/span&gt;&lt;span class="s"&gt;"AspectFit"&lt;/span&gt;
                           &lt;span class="na"&gt;HeightRequest=&lt;/span&gt;&lt;span class="s"&gt;"40"&lt;/span&gt; &lt;span class="na"&gt;WidthRequest=&lt;/span&gt;&lt;span class="s"&gt;"40"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;StackLayout&amp;gt;&lt;/span&gt;

                        &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Launch.mission_name}"&lt;/span&gt;
                               &lt;span class="na"&gt;TextColor=&lt;/span&gt;&lt;span class="s"&gt;"White"&lt;/span&gt;
                               &lt;span class="na"&gt;FontAttributes=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&lt;/span&gt; &lt;span class="na"&gt;FontSize=&lt;/span&gt;&lt;span class="s"&gt;"Large"&lt;/span&gt;
                               &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Start"&lt;/span&gt; &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Center"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Launch.launch_date_utc}"&lt;/span&gt;
                               &lt;span class="na"&gt;TextColor=&lt;/span&gt;&lt;span class="s"&gt;"LightGray"&lt;/span&gt;
                               &lt;span class="na"&gt;FontAttributes=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&lt;/span&gt; &lt;span class="na"&gt;FontSize=&lt;/span&gt;&lt;span class="s"&gt;"Micro"&lt;/span&gt;
                               &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Start"&lt;/span&gt; &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Center"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/StackLayout&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/StackLayout&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/Grid&amp;gt;&lt;/span&gt;

            &lt;span class="c"&gt;&amp;lt;!-- Article link --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;VerticalOptions=&lt;/span&gt;&lt;span class="s"&gt;"End"&lt;/span&gt; &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"End"&lt;/span&gt;
                   &lt;span class="na"&gt;Margin=&lt;/span&gt;&lt;span class="s"&gt;"0, 5, 20,0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;Label.FormattedText&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;FormattedString&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;Span&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Article Link"&lt;/span&gt; &lt;span class="na"&gt;TextColor=&lt;/span&gt;&lt;span class="s"&gt;"RoyalBlue"&lt;/span&gt;
                              &lt;span class="na"&gt;FontSize=&lt;/span&gt;&lt;span class="s"&gt;"Small"&lt;/span&gt; &lt;span class="na"&gt;FontAttributes=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;Span.GestureRecognizers&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;TapGestureRecognizer&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"{Binding ArticleLinkTappedCommand}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/Span.GestureRecognizers&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/Span&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/FormattedString&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/Label.FormattedText&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/Label&amp;gt;&lt;/span&gt;

            &lt;span class="c"&gt;&amp;lt;!-- Description --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Description"&lt;/span&gt; &lt;span class="na"&gt;TextColor=&lt;/span&gt;&lt;span class="s"&gt;"#FF543D"&lt;/span&gt; &lt;span class="na"&gt;FontSize=&lt;/span&gt;&lt;span class="s"&gt;"Large"&lt;/span&gt;
                   &lt;span class="na"&gt;FontAttributes=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&lt;/span&gt;
                   &lt;span class="na"&gt;Margin=&lt;/span&gt;&lt;span class="s"&gt;"0, 20,0,5"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Launch.details}"&lt;/span&gt;
                   &lt;span class="na"&gt;FontSize=&lt;/span&gt;&lt;span class="s"&gt;"Body"&lt;/span&gt;
                   &lt;span class="na"&gt;TextColor=&lt;/span&gt;&lt;span class="s"&gt;"White"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

            &lt;span class="c"&gt;&amp;lt;!-- Media --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Media"&lt;/span&gt; &lt;span class="na"&gt;TextColor=&lt;/span&gt;&lt;span class="s"&gt;"#FF543D"&lt;/span&gt;
                   &lt;span class="na"&gt;FontSize=&lt;/span&gt;&lt;span class="s"&gt;"Large"&lt;/span&gt; &lt;span class="na"&gt;FontAttributes=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&lt;/span&gt; &lt;span class="na"&gt;Margin=&lt;/span&gt;&lt;span class="s"&gt;"0, 20,0,5"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;StackLayout&lt;/span&gt; &lt;span class="na"&gt;BindableLayout.ItemsSource=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Launch.links.flickr_images}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;BindableLayout.EmptyView&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;Label&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Loading ..."&lt;/span&gt; &lt;span class="na"&gt;TextColor=&lt;/span&gt;&lt;span class="s"&gt;"White"&lt;/span&gt; &lt;span class="na"&gt;FontSize=&lt;/span&gt;&lt;span class="s"&gt;"Small"&lt;/span&gt;
                           &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"Center"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/BindableLayout.EmptyView&amp;gt;&lt;/span&gt;

                &lt;span class="c"&gt;&amp;lt;!-- Images --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;BindableLayout.ItemTemplate&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;DataTemplate&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;SwipeView&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;SwipeView.RightItems&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;SwipeItems&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;SwipeItem&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Share"&lt;/span&gt;
                                               &lt;span class="na"&gt;BackgroundColor=&lt;/span&gt;&lt;span class="s"&gt;"#FF543D"&lt;/span&gt;
                                               &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Source={x:Reference ParentPage}, Path=BindingContext.ImageTappedCommand}"&lt;/span&gt;
                                               &lt;span class="na"&gt;CommandParameter=&lt;/span&gt;&lt;span class="s"&gt;"{Binding .}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;/SwipeItems&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/SwipeView.RightItems&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;Frame&lt;/span&gt; &lt;span class="na"&gt;CornerRadius=&lt;/span&gt;&lt;span class="s"&gt;"20"&lt;/span&gt;
                                   &lt;span class="na"&gt;Padding=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;Margin=&lt;/span&gt;&lt;span class="s"&gt;"0,0,0,5"&lt;/span&gt;
                                   &lt;span class="na"&gt;HasShadow=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;
                                   &lt;span class="na"&gt;IsClippedToBounds=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;Image&lt;/span&gt; &lt;span class="na"&gt;Source=&lt;/span&gt;&lt;span class="s"&gt;"{Binding .}"&lt;/span&gt;
                                       &lt;span class="na"&gt;HorizontalOptions=&lt;/span&gt;&lt;span class="s"&gt;"FillAndExpand"&lt;/span&gt;
                                       &lt;span class="na"&gt;BackgroundColor=&lt;/span&gt;&lt;span class="s"&gt;"#384355"&lt;/span&gt;
                                       &lt;span class="na"&gt;Aspect=&lt;/span&gt;&lt;span class="s"&gt;"AspectFill"&lt;/span&gt;
                                       &lt;span class="na"&gt;HeightRequest=&lt;/span&gt;&lt;span class="s"&gt;"250"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/Frame&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/SwipeView&amp;gt;&lt;/span&gt;

                    &lt;span class="nt"&gt;&amp;lt;/DataTemplate&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/BindableLayout.ItemTemplate&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/StackLayout&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/StackLayout&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ScrollView&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ContentPage&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  LaunchPage.xaml.cs
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xamarin.Forms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xamarin.Forms.Xaml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SpaceXGraphQL&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XamlCompilation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;XamlCompilationOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Compile&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LaunchPage&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContentPage&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;LaunchPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LaunchPageViewModel&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;InitializeComponent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;BindingContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  GetLastItemConverter.cs
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Globalization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xamarin.Forms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SpaceXGraphQL.Converters&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetLastItemConverter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IValueConverter&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nf"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;targetType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CultureInfo&lt;/span&gt; &lt;span class="n"&gt;culture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;enumerable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;enumerable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Last&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nf"&gt;ConvertBack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;targetType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CultureInfo&lt;/span&gt; &lt;span class="n"&gt;culture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;We're done with the second and last page and the result looks like this&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wbz3erhn4c0tua5dzgv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wbz3erhn4c0tua5dzgv.png" alt="Launch details"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Except for the hiccup while generating the types, connecting to the GraphQL API was pretty easy. Kudos to them for providing the data for free!&lt;br&gt;
Let me know if you're using another tool for code generation which can handle SpaceX's GraphQL schema.&lt;/p&gt;

&lt;p&gt;I have to say that implementing the app in two afternoons was a lot of fun. I was pleasantly surprised by how good the Xamarin.Forms hot reload has become. Last time I checked, which was in the summer of 2020, it was much less stable. This time around I could actually use it to prototype my app.&lt;/p&gt;

&lt;p&gt;If you've made it this far, I got a bonus for you! Here's the  link to my GitHub repo with all the code: &lt;a href="https://github.com/mariusmuntean/SpaceXGraphQL" rel="noopener noreferrer"&gt;https://github.com/mariusmuntean/SpaceXGraphQL&lt;/a&gt;&lt;/p&gt;

</description>
      <category>spacex</category>
      <category>graphql</category>
      <category>xamarinforms</category>
      <category>xaml</category>
    </item>
    <item>
      <title>Coordinator Layout with Xamarin.Forms</title>
      <dc:creator>Marius Muntean</dc:creator>
      <pubDate>Sun, 26 Jul 2020 10:08:43 +0000</pubDate>
      <link>https://dev.to/mariusmuntean/coordinator-layout-with-xamarin-forms-5elc</link>
      <guid>https://dev.to/mariusmuntean/coordinator-layout-with-xamarin-forms-5elc</guid>
      <description>&lt;h1&gt;
  
  
  Coordinator Layout with Xamarin.Forms
&lt;/h1&gt;

&lt;p&gt;In this article we’ll create a simple Xamarin Forms app that uses the Coordinator Layout. The app is inspired by a view from the &lt;a href="https://play.google.com/store/apps/details?id=com.blinkslabs.blinkist.android" rel="noopener noreferrer"&gt;Blinkist&lt;/a&gt; app and it will have similar animations for its title section and the buttons right below it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F64abmnmxzgsmp35vi5z8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F64abmnmxzgsmp35vi5z8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Coordinator Layout allows you to synchronise a top view and a bottom view by changing the top view based on the scrolling position of the bottom view.&lt;/p&gt;

&lt;p&gt;Have a look at the gif below and notice how the title view is expanding and the label is increasing in size while scrolling down. &lt;a href="https://material.io/components/app-bars-top#behavior" rel="noopener noreferrer"&gt;Source&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkzdcmsvpe01t2xlqfd8d.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkzdcmsvpe01t2xlqfd8d.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Heads Up
&lt;/h3&gt;

&lt;p&gt;In a production-ready app you’d use MVVM and have the content be provided by a viewmodel from a data source. Since the focus of this article is to use CoordinatorLayout, I decided not to muddle the waters by providing any code that’s not directly relevant.&lt;/p&gt;

&lt;p&gt;To build the app you’ll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Visual Studio with the Mobile app workload or Rider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Android Emulator or a real device.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Basic Xamarin.Forms knowledge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A few spare minutes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let’s go
&lt;/h2&gt;

&lt;p&gt;Create a new blank Xamarin.Forms application. I’m using Rider and for me that looks like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4py36nodv33famyq1agv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4py36nodv33famyq1agv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upgrade the preinstalled Xamarin.Forms library and add a reference to the &lt;a href="https://www.nuget.org/packages/CoordinatorLayout.XamarinForms/" rel="noopener noreferrer"&gt;CoordinatorLayout.XamarinForms&lt;/a&gt; NuGet package to all the projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0l0571ord4thzva6omjp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0l0571ord4thzva6omjp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Build and run the Android app, just to make sure everything is working as expected. In case you encounter some error, it should be easier to fix it now rather than later.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;MainPage.xaml&lt;/strong&gt; add an xml namespace for CoordinatorLayout.XamarinForms and add a CoordinatorLayout element. To see anything in your app add a Label to the CoordinatorLayout.TopView and another Label with overflowing text to the CoordinatorLayout.BottomView.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8d1pzt9082wgzyk653bo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8d1pzt9082wgzyk653bo.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The CoordinatorLayout control has default values that allow you to expand the top view by dragging on the bottom view up and down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3xqbvrdjosfohs3buw75.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3xqbvrdjosfohs3buw75.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bottom View
&lt;/h3&gt;

&lt;p&gt;Let’s finish up the bottom view, since that’s the easiest. Just give the Label the following values for Padding and BackgroundColor&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BackgroundColor="SteelBlue" Padding="10,30,10,10"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F596h43jrc79m3nkdfay4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F596h43jrc79m3nkdfay4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Action View
&lt;/h3&gt;

&lt;p&gt;The Action View is an optional view that sits on the border between the Top View and to Bottom View.&lt;/p&gt;

&lt;p&gt;We’ll use that to create the Read and Listen buttons from the first screenshot.&lt;/p&gt;

&lt;p&gt;Since the ActionView just “hovers” on the aforementioned border region, it partially overlaps the Top and Bottom Views. This means that it can’t really compute its required height and we have to tell it how tall it should be.&lt;/p&gt;

&lt;p&gt;The CoordinatorLayout allows us to express the Action View’s height proportionally to its own height using the property &lt;em&gt;ProportionalActionViewContainerHeight&lt;/em&gt;. For this sample app we’ll set it to ”0.1".&lt;/p&gt;

&lt;p&gt;By default, the Action View is hidden when the Top View is collapsed. For this app we don’t want that, so we’ll turn off that behaviour by setting &lt;em&gt;AutohideActionView&lt;/em&gt;=”False”.&lt;/p&gt;

&lt;p&gt;To display those rounded buttons with a separator in between I’m using a Grid with three columns, containing a Button, a BoxView and another Button respectively. For the rounded corners I’m using a Frame with a CornerRadius of 20.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwkkf7xrlbifo1nvd8su9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwkkf7xrlbifo1nvd8su9.png" alt="Action View containing two buttons with rounded corners."&gt;&lt;/a&gt;&lt;em&gt;Action View containing two buttons with rounded corners.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At this point the app should look like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff87c8u1a8hvz8rcn4p5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff87c8u1a8hvz8rcn4p5j.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It would be really fancy to change the width and the corner radius of the buttons depending on the expansion progress of the Top View. The CoordinatorLayout has an event called &lt;em&gt;ExpansionEventHandler&lt;/em&gt; that contains the expansion progress. The progress is reported as a value between 0.0 (top view is completely shrunk) and 1.0 (top view is completely expanded).&lt;/p&gt;

&lt;p&gt;To leverage that event we’ll need to give the Frame element a name, like “ActionFrame” and to add a callback method for the &lt;em&gt;ExpansionEventHandler&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In the callback method all we have to do is vary the Frame’s corner radius and margin between a minimum and a maximum value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsa4ky4dh5hiy6m1btx8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsa4ky4dh5hiy6m1btx8d.png" alt="Adjusting the Frame’s corner radius and the margin based on the expansion progress."&gt;&lt;/a&gt;&lt;em&gt;Adjusting the Frame’s corner radius and the margin based on the expansion progress.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At this point the Action View smoothly changes from a button bar to a sticky button header when scrolling up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Top View
&lt;/h3&gt;

&lt;p&gt;The top view will start in the expanded state and will show a background image, the author name, a title and a partial underline. When shrunk, those elements will fade out and a new smaller title will fade in.&lt;/p&gt;

&lt;p&gt;To achieve this, we’ll set &lt;em&gt;InitialExpansionState&lt;/em&gt;=”Expanded” on the CoordinatorLayout and add all the necessary elements to the Top View.&lt;/p&gt;

&lt;p&gt;Notice that the smaller title (called SecondaryTitle) starts out with its &lt;em&gt;Opacity&lt;/em&gt;=”0.0”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw1h2pcsltgi348vhls11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw1h2pcsltgi348vhls11.png" alt="Top View with named elements."&gt;&lt;/a&gt;&lt;em&gt;Top View with named elements.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Using the same callback method for the expansion progress that we used before, we can implement the described behaviour of the Top View.&lt;/p&gt;

&lt;p&gt;Since the expansion progress is 0.0 when the Top View is shrunk and 1.0 when it is fully expanded, we can use that value as it is for the opacity of the background image and the title.&lt;/p&gt;

&lt;p&gt;For the secondary title, we’ll use a formula that fades the Label in when the Top View is almost completely shrunk and fades it out otherwise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0ryry4xy8bfom4q9c2jo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0ryry4xy8bfom4q9c2jo.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take a moment to drag up and down on the CoordinatorLayout and observe the nice animations. While you expand the Top View, its content slowly fades into view and the Action View moves downward while at the same time morphing from a rectangular button bar into a rounded button area.&lt;/p&gt;

&lt;p&gt;The last step is to add the transparent buttons to the left and right of the top view. There are multiple ways of creating those buttons and for this sample app we’ll leverage Xamarin’s support for custom fonts and the FontImageSource class.&lt;/p&gt;

&lt;p&gt;First download the Material Design Icons from &lt;a href="https://materialdesignicons.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and extract the zip file. Add the .ttf font file to your shared project and set its Build action to EmbeddedResource.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fumjw363w2a92r8jfagwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fumjw363w2a92r8jfagwr.png" alt="Build action for the .ttf file should be EmbeddedResource."&gt;&lt;/a&gt;&lt;em&gt;Build action for the .ttf file should be EmbeddedResource.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A couple of versions ago Xamarin.Forms got really good support for &lt;a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/text/fonts#use-a-custom-font" rel="noopener noreferrer"&gt;custom fonts&lt;/a&gt;, such that all you need to do is add this line to your App.xaml.cs&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[assembly: ExportFont("materialdesignicons-webfont.ttf", Alias = "MaterialDesign")]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffenkyqha0ynah9uib0ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffenkyqha0ynah9uib0ko.png" alt="ExportFont attribute in the App.xaml.cs class."&gt;&lt;/a&gt;&lt;em&gt;ExportFont attribute in the App.xaml.cs class.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We are now ready to add the buttons. The easiest way is to nest the entire CoordinatorLayout in a Grid element and then to add the buttons on top of the CoordinatorLayout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnuo6xdnukns6xb6tobzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnuo6xdnukns6xb6tobzd.png" alt="The buttons are defined below the CoordinatorLayout, but still inside the Grid."&gt;&lt;/a&gt;&lt;em&gt;The buttons are defined below the CoordinatorLayout, but still inside the Grid.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You should now have an app that looks like this&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/8sgCrW6xi7A"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The snapping behaviour is built into CoordinatorLayout but you can tweak it or even turn it off. Here’s an explanation of all the adjustable properties &lt;a href="https://github.com/mariusmuntean/CoordinatorLayout.Forms#property-reference" rel="noopener noreferrer"&gt;https://github.com/mariusmuntean/CoordinatorLayout.Forms#property-reference&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;I am the creator of CoordinatorLayout.XamarinForms and I’ve recently released version 1.3.0, which improves touch input support for the Bottom View. Give it a try and let me know what you built with it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/mariusmuntean/CoordinatorLayout.Forms" rel="noopener noreferrer"&gt;source code&lt;/a&gt; of the library contains a full-blown sample app that highlights everything CoordinatorLayout.XamarinForms can do.&lt;/p&gt;

&lt;p&gt;While you’re there, have a look at my other open source projects. I publish all my projects under the MIT license and you’re free to do anything with my code. Have fun!&lt;/p&gt;

</description>
      <category>xamarinforms</category>
      <category>customlayout</category>
      <category>coordinatorlayout</category>
    </item>
  </channel>
</rss>
