<?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: Edward Miller</title>
    <description>The latest articles on DEV Community by Edward Miller (@symbiogenesis).</description>
    <link>https://dev.to/symbiogenesis</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%2F1004987%2Fe0e03f36-bc06-4fe3-a9ea-475bf48c7b38.png</url>
      <title>DEV Community: Edward Miller</title>
      <link>https://dev.to/symbiogenesis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/symbiogenesis"/>
    <language>en</language>
    <item>
      <title>A feature wishlist for C#</title>
      <dc:creator>Edward Miller</dc:creator>
      <pubDate>Sat, 27 Jul 2024 17:53:10 +0000</pubDate>
      <link>https://dev.to/symbiogenesis/the-main-features-i-want-for-c-3f2n</link>
      <guid>https://dev.to/symbiogenesis/the-main-features-i-want-for-c-3f2n</guid>
      <description>&lt;p&gt;C# is very near to a perfect programming language, by my standards. But there are still some rough edges.&lt;/p&gt;

&lt;p&gt;There has been much discussion about proposals such as extension types or the field keyword inside getters and setters, which are mostly about convenience.&lt;/p&gt;

&lt;p&gt;And there's been a lot of work on source generators and AOT, which is mainly about performance.&lt;/p&gt;

&lt;p&gt;But I think we need a focus on correctness. Here are some examples:&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Nullable Types
&lt;/h2&gt;

&lt;p&gt;The null reference exception is a legacy of the "billion-dollar mistake" that Tony Hoare said he made in the design of the ALGOL W language in 1965. We have been dealing with it ever since.&lt;/p&gt;

&lt;p&gt;Nullable types ought to be an authentic part of the type system in C#. They currently appear to be like a thin shim of syntactic sugar that are little more than Roslyn analyzers.&lt;/p&gt;

&lt;p&gt;If you make a public function that requires a non-nullable parameter that is a reference type, and call it from another project that doesn't enable nullable types... there is actually nothing stopping the parameter from being filled with null.&lt;/p&gt;

&lt;p&gt;No error would be thrown at compile-time or runtime when trying to fill that parameter with a null.&lt;/p&gt;

&lt;p&gt;So, despite that you have already clearly specified your intentions, you have to guard your public parameters against null references.&lt;/p&gt;

&lt;p&gt;And if you try to detect whether a type is nullable or not, using reflection, then a reference type is going to always be seen as nullable. As far as I know, there is not even a way to write a generic function that detects whether you defined a reference type as non-nullable. Because no matter what you actually do it really is nullable.&lt;/p&gt;

&lt;p&gt;All of that works fine for value types, but that still leaves way too much room for covert null reference exceptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better type inference for generics with constraints
&lt;/h2&gt;

&lt;p&gt;We should be able to do complex constraints where one generic type is a collection of another generic type. This helps broaden the expressivity of the type specifications, and thereby more precisely define what it is the program ought to be doing at compile-time.&lt;/p&gt;

&lt;p&gt;We should want this for all the same reasons we like strong types in general.&lt;/p&gt;

&lt;p&gt;see also: &lt;a href="https://github.com/dotnet/roslyn/pull/7850" rel="noopener noreferrer"&gt;https://github.com/dotnet/roslyn/pull/7850&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Exception Type Matching
&lt;/h2&gt;

&lt;p&gt;All possible exception types that can be thrown by a particular function call should be known at compile-time, much like Rust's concept of the &lt;code&gt;Option&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;Analyzer &lt;a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1031" rel="noopener noreferrer"&gt;CA1031&lt;/a&gt; exists specifically to inform the user that they ought not be catching general exceptions.&lt;/p&gt;

&lt;p&gt;A better language would be able to show you precisely in the IntelliSense popup &lt;em&gt;all&lt;/em&gt; possible exception types that could be thrown, even without the library authors documenting the exceptions with XML comments.&lt;/p&gt;

&lt;p&gt;If this were the case this would help the specific kinds of exceptions to be handled in the specific ways that they ought to be, without any fear of unknowns.&lt;/p&gt;

&lt;p&gt;In Rust, you can do a switch statement upon all the possible exceptions, which makes it really clear when one of the possible exceptions wasn't considered.&lt;/p&gt;

&lt;p&gt;In C#, baking this into the normal try-catch syntax would be fine, and then turning on CA1031 by default would be fine, as long as IntelliSense would always and reliably tell you exactly what exception types are missing. As it does with a switch statement on an enum.&lt;/p&gt;




&lt;p&gt;And, aside from correctness-oriented improvements, there are some oversights in existing features need to be addressed:&lt;/p&gt;

&lt;h2&gt;
  
  
  Kotlin-style &lt;code&gt;init&lt;/code&gt; blocks
&lt;/h2&gt;

&lt;p&gt;If you want to add some imperative logic during initialization, you cannot currently use a primary constructor. Kotlin solved this with the &lt;code&gt;init&lt;/code&gt; block. C# should copy that.&lt;/p&gt;

&lt;p&gt;see also: &lt;a href="https://github.com/dotnet/csharplang/discussions/4025" rel="noopener noreferrer"&gt;https://github.com/dotnet/csharplang/discussions/4025&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;And some general .NET Framework enhancements are needed in the standard libraries:&lt;/p&gt;

&lt;h2&gt;
  
  
  Range support in &lt;code&gt;ICollection&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This one is super obvious. &lt;code&gt;ObservableCollection&lt;/code&gt; is a great example of where we need it. There have been countless custom implementations of something like &lt;code&gt;ObservableRangeCollection&lt;/code&gt;. Having all of the changes show up in a single CollectionChanged event makes vastly more sense than users falling back to using loops of Add() calls because the framework has this deficiency.&lt;/p&gt;

&lt;p&gt;see also: &lt;a href="https://github.com/dotnet/runtime/issues/18087" rel="noopener noreferrer"&gt;https://github.com/dotnet/runtime/issues/18087&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deserializing interfaces with &lt;code&gt;System.Text.Json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Newtonsoft.Json&lt;/code&gt; supports this by default without much effort on the part of the developer. &lt;code&gt;System.Text.Json&lt;/code&gt; now has some support for "polymorphic deserialization," but it involves a lot of manual intervention on the part of the programmer.&lt;/p&gt;

&lt;p&gt;Apparently, there are security concerns around this, and the current approach certainly allows for improved performance. But for quick and dirty stuff, it would be nice to have a simple default that just works for most cases. While still giving some safety valves against attack vectors, like attempting to (de)serialize Exception types.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;ConfigureAwait()&lt;/code&gt; improvements
&lt;/h2&gt;

&lt;p&gt;Library authors generally want to use &lt;code&gt;ConfigureAwait(false)&lt;/code&gt; everywhere, but there is no easy way to define this globally. Having the codebase littered with &lt;code&gt;ConfigureAwait(false)&lt;/code&gt; everywhere is ugly, and some async function calls are likely to be overlooked and thus defeat the entire point.&lt;/p&gt;

&lt;p&gt;Turning on analyzer &lt;a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2007" rel="noopener noreferrer"&gt;CA2007&lt;/a&gt; can help, but you would need to intentionally do so, and know to do so. And it is still annoying.&lt;/p&gt;

&lt;p&gt;There must be a better way.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>A plan for cybersecurity and grid safety</title>
      <dc:creator>Edward Miller</dc:creator>
      <pubDate>Fri, 10 Feb 2023 22:42:51 +0000</pubDate>
      <link>https://dev.to/symbiogenesis/a-plan-for-cybersecurity-and-grid-safety-5fpf</link>
      <guid>https://dev.to/symbiogenesis/a-plan-for-cybersecurity-and-grid-safety-5fpf</guid>
      <description>&lt;p&gt;Humanity needs the following for cybersecurity and grid safety, particularly in our new era of machine learning and deepfakes. &lt;/p&gt;

&lt;p&gt;These would be high-impact causes to fund or contribute to:&lt;/p&gt;




&lt;h3&gt;
  
  
  Identity Verification and CAPTCHA
&lt;/h3&gt;

&lt;p&gt;We require coordinated efforts to design next-generation ID verification and CAPTCHA systems, potentially using various multi-factor validation focused more on validating that someone is a biological organism rather than someone who has language ability or who knows your Social Security Number.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Efforts:&lt;/strong&gt; &lt;a href="https://trustedcomputinggroup.org/"&gt;TPM standard&lt;/a&gt;, biometrics, &lt;a href="//Login.gov"&gt;Login.gov&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Quantum-Proof Cryptography
&lt;/h3&gt;

&lt;p&gt;We must encourage adoption of quantum-proof encryption. Quantum computing has a small chance of destroying all cryptographic infrastructure. That would mean all of our civilizational infrastructure would be compromised, from banking to the military.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Efforts:&lt;/strong&gt; &lt;a href="https://openquantumsafe.org/"&gt;Open Quantum Safe project&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Formal Verification
&lt;/h3&gt;

&lt;p&gt;We must ensure adoption of formally verified and memory-safe computing infrastructure including OSes, TLS, DNS, NTP, SSH, etc.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Efforts:&lt;/strong&gt; &lt;a href="https://sel4.systems/"&gt;seL4&lt;/a&gt;, &lt;a href="https://project-everest.github.io/"&gt;Project Everest&lt;/a&gt;, the &lt;a href="https://www.memorysafety.org/"&gt;Prossimo project&lt;/a&gt; of the ISRG, &lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt;, and &lt;a href="https://github.com/viperproject/prusti-dev"&gt;Prusti&lt;/a&gt; for the Rust language&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  EMP Shielding
&lt;/h3&gt;

&lt;p&gt;We must harden all communications and energy grid infrastructure with EMP shielding. This can be accomplished with basic technology, such as faraday cages. But there is quite a lot of grid infrastructure, and it all needs to be carefully shielded, and that could potentially cost a lot of money.&lt;/p&gt;

&lt;p&gt;All the news we are seeing about weather balloons being shot down is potentially related to testing out the US's readiness against a high altitude EMP blast. Such a blast could wipe out all communications infrastructure on an entire continent, and lead to unfathomable destruction.&lt;/p&gt;

&lt;p&gt;But it isn't just nation-states that can inflict this damage. Solar storms can produce the same effects, and it is inevitable that we will be dealing with such an event. We need to be prepared, even if the cost is steep.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Efforts:&lt;/strong&gt; &lt;a href="https://www.congress.gov/bill/113th-congress/house-bill/2417"&gt;H.R.2417 Secure High-voltage Infrastructure for Electricity from Lethal Damage Act&lt;/a&gt; &lt;em&gt;(not passed)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Data Breach Protection
&lt;/h3&gt;

&lt;p&gt;There are many known-faulty central bottlenecks for information security and identity compromise, like the cartel of credit reporting agencies. If we cannot outright obsolete these, we must at least penalize them for security breaches.&lt;/p&gt;

&lt;p&gt;For certain extremely important information, like credit reporting systems, zero-knowledge and fully homomorphic encryption may be important. And they should at least try to keep up with malicious actors, who do pursue such things. But such systems are onerous and expensive, and wouldn't be pursued unless it were made urgent. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Efforts:&lt;/strong&gt; &lt;a href="https://homomorphicencryption.org/"&gt;Homomorphic Encryption Standardization&lt;/a&gt;, &lt;a href="https://www.congress.gov/bill/115th-congress/senate-bill/2289"&gt;S.2289 Data Breach Prevention and Compensation Act&lt;/a&gt; &lt;em&gt;(not passed)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Endpoint Security and Router Security
&lt;/h3&gt;

&lt;p&gt;We should leverage the threat of court fines and insurance penalties to get organizations to decommission obsolete hardware and software, for themselves and their members. It takes decades to roll out core networking upgrades at the moment, but threats are moving fast and that is unacceptable. ISPs should give rewards to those who are in compliance with all the latest security measures.&lt;/p&gt;

&lt;p&gt;Gray hat hackers have done such things as remotely exploiting scores of routers purely to perform remote updates and security patching. That is a possible area of help, but isn't organized.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Efforts:&lt;/strong&gt; Ongoing efforts by many organizations with things like Wifi 6, WPA3, multi-factor auth requirements, Windows 11, etc.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Automated code checking
&lt;/h3&gt;

&lt;p&gt;Automated bots to help open source codebases are now common. They perform static analysis, automated dependency upgrade pull requests, and so on. This model can and has been extended to automatically patch common coding mistakes, and we are just scraping the surface of what is possible&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Efforts:&lt;/strong&gt; &lt;a href="https://github.com/dependabot"&gt;Dependabot&lt;/a&gt;, &lt;a href="https://codeql.github.com/"&gt;CodeQL&lt;/a&gt;, &lt;a href="https://scan.coverity.com/"&gt;Coverity&lt;/a&gt;, facebook's &lt;a href="https://fbinfer.com/"&gt;Infer&lt;/a&gt; tool, etc&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Reproducible Builds
&lt;/h3&gt;

&lt;p&gt;Reproducible build systems need to become common, since compromised build servers have been known as a key vector for compromising libraries and vended software. This would allow us to have peace of mind that running a scripted build process will always produce an identical binary that can be reliably checked by others. Thus, when downloading a binary from a server, you can have confidence that it was produced using the source code you expected, rather than just taking it on faith.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Efforts:&lt;/strong&gt; &lt;a href="https://reproducible-builds.org/"&gt;Reproducible Builds project&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>cybersecurity</category>
      <category>security</category>
    </item>
    <item>
      <title>Code Styling should be enforced by default</title>
      <dc:creator>Edward Miller</dc:creator>
      <pubDate>Fri, 03 Feb 2023 18:14:27 +0000</pubDate>
      <link>https://dev.to/symbiogenesis/code-styling-should-be-enforced-by-default-3hhg</link>
      <guid>https://dev.to/symbiogenesis/code-styling-should-be-enforced-by-default-3hhg</guid>
      <description>&lt;p&gt;Having standards, even suboptimal ones, is usually better than not having any standard.&lt;/p&gt;

&lt;p&gt;Languages, compilers, IDEs, templates, and so on should all enforce consistent standards by default.&lt;/p&gt;

&lt;p&gt;The Rust language has rustfmt and clippy. Javascript has eslint and prettier.&lt;/p&gt;

&lt;p&gt;I'm a C# guy, so that is what I care about. For .NET we do have &lt;a href="https://github.com/DotNetAnalyzers/StyleCopAnalyzers"&gt;StyleCop analyzers&lt;/a&gt;. And EditorConfig exists to help at the IDE level across all languages. And git itself can be configured with such things as eol and autoclrf.&lt;/p&gt;

&lt;p&gt;But StyleCop is not on by default, and not enforced by default. A default EditorConfig configuration is not automatically applied by default when generating project templates, either via Visual Studio or "dotnet new".&lt;/p&gt;

&lt;p&gt;Someone already went through the trouble of producing a &lt;a href="https://github.com/RehanSaeed/EditorConfig"&gt;editorconfig dotfile&lt;/a&gt; that conforms to the StyleCop rules, and yet it isn't even suggested to be applied by any important C# tool. Which is a glaring oversight as bad as when default gitignore files were not suggested by default, leading to countless repos cluttered with bin and obj folders.&lt;/p&gt;

&lt;p&gt;The concept of &lt;a href="https://semanticdiff.com/"&gt;Semantic Diff&lt;/a&gt;s may finally be starting to take off, and that will eventually go a long way to improve things when it comes to diffs.&lt;/p&gt;

&lt;p&gt;But messy diffs are not the only reason to want code to be consistent. It is also crucial for readability, and even for ensuring that the program logic is explicit. If it is mandatory to qualify instance members, as StyleCop's rules recommend, that helps make things explicit. Yet, the de facto community conventions in C# are the opposite of the guidelines.&lt;/p&gt;

&lt;p&gt;Future versions of Visual Studio, OmniSharp, Roslyn, and the dotnet CLI should be taking this to heart going forward, and begin to enforce all of this by default for new projects.&lt;/p&gt;

&lt;p&gt;But the best thing you can do is to keep your own home tidy, by ensuring that you turn on StyleCop analyzers, enforce them in your pipeline, and include a strict editorconfig dotfile.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>.NET MAUI iOS Camera Photos Rotated bug fix</title>
      <dc:creator>Edward Miller</dc:creator>
      <pubDate>Sun, 29 Jan 2023 08:17:50 +0000</pubDate>
      <link>https://dev.to/symbiogenesis/net-maui-ios-camera-rotated-bug-fix-3ee4</link>
      <guid>https://dev.to/symbiogenesis/net-maui-ios-camera-rotated-bug-fix-3ee4</guid>
      <description>&lt;p&gt;At the time of writing this article, there is &lt;a href="https://github.com/dotnet/maui/issues/9535"&gt;a bug&lt;/a&gt; in MAUI that has existed since Xamarin.Forms where photos taken from iOS do not respect the device orientation, or record that information in the EXIF data, and thus they can end up incorrectly rotated.&lt;/p&gt;

&lt;p&gt;This one was one of the most annoying bugs in MAUI, and I solved it with this workaround taken from IeuanWalker on &lt;a href="https://github.com/xamarin/Essentials/issues/1514"&gt;this thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I adapted the code to more precisely conform to the existing MediaPicker conventions, and am using nullable types.&lt;/p&gt;

&lt;p&gt;Just put this in your Platforms/iOS folder:&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;namespace&lt;/span&gt; &lt;span class="nn"&gt;YourAppName.Platforms.iOS&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&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.Threading.Tasks&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;Foundation&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;Microsoft.Maui.Graphics.Platform&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;UIKit&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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PhotoCaptureUtil&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;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FileResult&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CapturePhotoAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPickerOptions&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;options&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;options&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;MediaPickerOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Take a photo"&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;await&lt;/span&gt; &lt;span class="n"&gt;MainThread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeOnMainThreadAsync&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;InternalCapturePhotoAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&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;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FileResult&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;InternalCapturePhotoAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPickerOptions&lt;/span&gt; &lt;span class="n"&gt;options&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;taskCompletionSource&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;TaskCompletionSource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FileResult&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Create an image picker object&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;imagePicker&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;UIImagePickerController&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;SourceType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIImagePickerControllerSourceType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Camera&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;MediaTypes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UIImagePickerController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AvailableMediaTypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIImagePickerControllerSourceType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PhotoLibrary&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;imagePicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&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;vc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCurrentUIViewController&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;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Cannot retrieve &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;UIViewController&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;imagePicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AllowsEditing&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;imagePicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FinishedPickingMedia&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="n"&gt;sender&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="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;jpegFilename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FileSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CacheDirectory&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;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;.jpg"&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;uiImage&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;Info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UIImagePickerController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OriginalImage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;UIImage&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;normalizedImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uiImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NormalizeOrientation&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;normalizedData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalizedImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsJPEG&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;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DismissViewControllerAsync&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalizedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jpegFilename&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;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&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="n"&gt;taskCompletionSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrySetResult&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;FileResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jpegFilename&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;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;taskCompletionSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrySetException&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;IOException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Error saving the image: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&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="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;imagePicker&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;imagePicker&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;imagePicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canceled&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="n"&gt;sender&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="p"&gt;=&amp;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;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DismissViewControllerAsync&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;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;taskCompletionSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrySetResult&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="n"&gt;imagePicker&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;imagePicker&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PresentViewControllerAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imagePicker&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;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;taskCompletionSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&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;class&lt;/span&gt; &lt;span class="nc"&gt;CameraDelegate&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UIImagePickerControllerDelegate&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;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;FinishedPickingMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIImagePickerController&lt;/span&gt; &lt;span class="n"&gt;picker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSDictionary&lt;/span&gt; &lt;span class="n"&gt;info&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;picker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DismissViewController&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then call it 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;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;MediaPicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCaptureSupported&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;Shell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisplayAlertAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Camera permissions are not enabled on this platform."&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="cp"&gt;#if IOS
&lt;/span&gt;    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fileResult&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;PhotoCaptureUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CapturePhotoAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fileResult&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;MediaPicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CapturePhotoAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Use .NET MAUI Map control with MVVM</title>
      <dc:creator>Edward Miller</dc:creator>
      <pubDate>Sun, 22 Jan 2023 04:08:24 +0000</pubDate>
      <link>https://dev.to/symbiogenesis/use-net-maui-map-control-with-mvvm-dfl</link>
      <guid>https://dev.to/symbiogenesis/use-net-maui-map-control-with-mvvm-dfl</guid>
      <description>&lt;p&gt;To use the current version of the .NET MAUI map control with MVVM, you will need to wrap it with some extra properties, if you want basic functionality like being able to set a selected pin or move the map via the ViewModel.&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;namespace&lt;/span&gt; &lt;span class="nn"&gt;MvvmMap&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;Microsoft.Maui.Maps&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;Map&lt;/span&gt; &lt;span class="p"&gt;=&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;Maui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&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;MvvmMap&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Map&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;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;BindableProperty&lt;/span&gt; &lt;span class="n"&gt;MapSpanProperty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BindableProperty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&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;MapSpan&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MapSpan&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MvvmMap&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="n"&gt;BindingMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TwoWay&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&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;n&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;b&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;MvvmMap&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;MapSpan&lt;/span&gt; &lt;span class="n"&gt;mapSpan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;MoveMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapSpan&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;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;BindableProperty&lt;/span&gt; &lt;span class="n"&gt;SelectedItemProperty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BindableProperty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&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;SelectedItem&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MvvmMap&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="n"&gt;BindingMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TwoWay&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;MapSpan&lt;/span&gt; &lt;span class="n"&gt;MapSpan&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MapSpan&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="nf"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MapSpanProperty&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;=&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="nf"&gt;SetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MapSpanProperty&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="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="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;get&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;object&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="nf"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SelectedItemProperty&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;=&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="nf"&gt;SetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SelectedItemProperty&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MoveMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MvvmMap&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MapSpan&lt;/span&gt; &lt;span class="n"&gt;mapSpan&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;timer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Dispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interval&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMilliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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;s&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="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;s&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;IDispatcherTimer&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

                &lt;span class="n"&gt;MainThread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginInvokeOnMainThread&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;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MoveToRegion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapSpan&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;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&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;Then you will want to implement it in XAML, backed with a ViewModel that contains an ObservableCollection of whatever model object you are using (in this case "Records").&lt;/p&gt;

&lt;p&gt;To get the event arguments back from the EventToCommandBehavior, you need to specify the TypeArguments, as shown. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; this is also implementing the &lt;a href="https://vladislavantonyuk.github.io/articles/Customize-map-pins-in-.NET-MAUI/"&gt;CustomPin&lt;/a&gt; control from Vladislav Antonyuk to allow for custom icons. If you use this, then you need to name the map and bind the map to the pin via the name, as shown.&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;ContentPage.Resources&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;converters:StringToLocationConverter&lt;/span&gt; &lt;span class="na"&gt;x:Key=&lt;/span&gt;&lt;span class="s"&gt;"stringToLocation"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;converters:RecordToIconConverter&lt;/span&gt; &lt;span class="na"&gt;x:Key=&lt;/span&gt;&lt;span class="s"&gt;"recordToIcon"&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;cc:MvvmMap&lt;/span&gt; &lt;span class="na"&gt;x:Name=&lt;/span&gt;&lt;span class="s"&gt;"mvvmMap1"&lt;/span&gt; &lt;span class="na"&gt;ItemsSource=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Records}"&lt;/span&gt; &lt;span class="na"&gt;MapType=&lt;/span&gt;&lt;span class="s"&gt;"{Binding MapType, Mode=OneTime}"&lt;/span&gt; &lt;span class="na"&gt;MapSpan=&lt;/span&gt;&lt;span class="s"&gt;"{Binding MapSpan}"&lt;/span&gt; &lt;span class="na"&gt;IsShowingUser=&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;cc:MvvmMap.ItemTemplate&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;DataTemplate&lt;/span&gt; &lt;span class="na"&gt;x:DataType=&lt;/span&gt;&lt;span class="s"&gt;"models:Record"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;cc:CustomPin&lt;/span&gt; &lt;span class="na"&gt;Location=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Location, Converter={StaticResource stringToLocation}}"&lt;/span&gt;
                              &lt;span class="na"&gt;ImageSource=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Converter={StaticResource recordToIcon}}"&lt;/span&gt;
                              &lt;span class="na"&gt;Label=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Name}"&lt;/span&gt;
                              &lt;span class="na"&gt;Map=&lt;/span&gt;&lt;span class="s"&gt;"{Reference mvvmMap1}"&lt;/span&gt; &lt;span class="nt"&gt;/&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;/cc:MvvmMap.ItemTemplate&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;cc:MvvmMap.Behaviors&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;toolkit:EventToCommandBehavior&lt;/span&gt; &lt;span class="na"&gt;EventName=&lt;/span&gt;&lt;span class="s"&gt;"Loaded"&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"{Binding LoadedCommand}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;toolkit:EventToCommandBehavior&lt;/span&gt; &lt;span class="na"&gt;x:TypeArguments=&lt;/span&gt;&lt;span class="s"&gt;"MapClickedEventArgs"&lt;/span&gt; &lt;span class="na"&gt;EventName=&lt;/span&gt;&lt;span class="s"&gt;"MapClicked"&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"{Binding MapClickedCommand}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/cc:MvvmMap.Behaviors&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/cc:MvvmMap&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The converter allows storing locations as strings in your database or wherever, and could 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;namespace&lt;/span&gt; &lt;span class="nn"&gt;YourProject.Converters&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StringToLocationConverter&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="p"&gt;?&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="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="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;?&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="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="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;?&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="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="k"&gt;return&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="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;not&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;latLong&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;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value is not a string"&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="nf"&gt;GetLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latLong&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="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="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;?&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="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="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;?&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;=&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;NotImplementedException&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;static&lt;/span&gt; &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="nf"&gt;GetLocation&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;latLong&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetLatitudeAndLongitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latLong&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="nf"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&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;static&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;Latitude&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;Longitude&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;GetLatitudeAndLongitude&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;latLong&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latLong&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;ArgumentNullException&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;latLong&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;segments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;latLong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&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;latitude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;double&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="n"&gt;segments&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;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvariantCulture&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;longitude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;double&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="n"&gt;segments&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="nf"&gt;TrimStart&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvariantCulture&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="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&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;See a working example here: &lt;a href="https://github.com/symbiogenesis/MauiMvvmMap"&gt;https://github.com/symbiogenesis/MauiMvvmMap&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>.NET MAUI thoughts</title>
      <dc:creator>Edward Miller</dc:creator>
      <pubDate>Sat, 21 Jan 2023 22:56:16 +0000</pubDate>
      <link>https://dev.to/symbiogenesis/net-maui-thoughts-3agd</link>
      <guid>https://dev.to/symbiogenesis/net-maui-thoughts-3agd</guid>
      <description>&lt;p&gt;I am wrapping up developing a .NET MAUI app targeting Windows, Android, and iOS.&lt;/p&gt;

&lt;p&gt;Overall, I would say that despite all the hiccups it was exactly what I was hoping for. Most of the problems I experienced were a result of the immaturity of the platform early on, and have since been resolved.&lt;/p&gt;

&lt;p&gt;Despite having no specific mobile development experience, I was able to make progress very quickly. And would be able to go even faster today.&lt;/p&gt;

&lt;p&gt;I had done a bunch of Silverlight, WPF, DotVVM, and other XAML style development for ages, and XAML + MVVM is by far my favorite stack.&lt;/p&gt;

&lt;p&gt;I first coded the app in Xamarin.Forms since MAUI hadn't been released. But migrating was fairly easy. The upgrade-assistant tool helped some, and having any experience with ASP.NET style dependency injection makes the new parts feel familiar.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Stuff I liked:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It provides an open source cross-platform stack for native application development in .NET, with support for the latest .NET versions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It is more batteries-included than Xamarin.Forms. Lots of concepts from Xamarin Essentials, the Xamarin Community Toolkit, and the wider ecosystem were simply incorporated directly into the framework, and expanded upon. Like SVG support, built-in screenshots, geolocation, permissions, file pickers, dark mode detection, and other platform-agnostic helpers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It interoperates very seamlessly with Blazor, so you would be able to share everything from your ViewModel layer and below.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can even directly incorporate WebView2 controls that you can interact with pretty nicely, which should allow mixing in web content, such as React, Angular, or Blazor apps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;My application was able to use an offline-first approach via Entity Framework and SQLite. I ended up implementing a bi-directional offline sync between SQLite and SQL Server, and this was done very easily as a background syncing service via the Dotmim.Sync library. And I was able to completely share the DbContext between the clients and the server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I didn't know this at first, but there is an awesome &lt;a href="https://jesseliberty.com/2021/12/09/the-miracle-of-iqueryattributable/" rel="noopener noreferrer"&gt;IQueryAttributable&lt;/a&gt; interface that you can implement to parse all query parameters in one central place, and this just feels vastly nicer to use than attributes.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MAUI/Xamarin &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/shapes/?view=net-maui-7.0" rel="noopener noreferrer"&gt;Shapes&lt;/a&gt; are really cool for making programmable platform-agnostic vector graphics.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The MAUI Community Toolkit has a lot of nice things like the Popup control, StateContainer, DockPanel, FolderPicker, and MVVM helpers. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The newest .NET versions support so many cool features built-in, and you can turn on a ton of extra features (&lt;a href="https://www.youtube.com/watch?v=NDweaZZZcbc" rel="noopener noreferrer"&gt;nullable types&lt;/a&gt;), roslyn analyzers (&lt;a href="https://github.com/JosefPihrt/Roslynator" rel="noopener noreferrer"&gt;Roslynator&lt;/a&gt;, &lt;a href="https://www.nuget.org/packages/StyleCop.Analyzers/1.2.0-beta.435" rel="noopener noreferrer"&gt;StyleCop&lt;/a&gt;), code formatting helpers (&lt;a href="https://editorconfig.org/" rel="noopener noreferrer"&gt;EditorConfig&lt;/a&gt;), static analysis tools (&lt;a href="https://www.youtube.com/watch?v=Bc0Tt0qOMn4" rel="noopener noreferrer"&gt;InferSharp&lt;/a&gt;), build pipeline enhancements (&lt;a href="https://github.com/dotnet/reproducible-builds" rel="noopener noreferrer"&gt;Reproducible Builds&lt;/a&gt;, &lt;a href="https://devblogs.microsoft.com/nuget/enable-repeatable-package-restores-using-a-lock-file/" rel="noopener noreferrer"&gt;Nuget lockfiles&lt;/a&gt;), and so on. With all these guard rails up, it feels like the space for errors is much lower.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Hiccups:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Took awhile for the integrated map control to be released but it is mostly here now. Needed to do a bunch of manual work to implement &lt;a href="https://vladislavantonyuk.azurewebsites.net/articles/Customize-map-pins-in-.NET-MAUI" rel="noopener noreferrer"&gt;custom icons&lt;/a&gt;, &lt;a href="https://github.com/CommunityToolkit/Maui/pull/604" rel="noopener noreferrer"&gt;Windows support&lt;/a&gt;, and &lt;a href="https://dev.to/symbiogenesis/use-net-maui-map-control-with-mvvm-dfl"&gt;MVVM support&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GUIDs didn't seem to be implemented in a byte-identical way across the client-server boundary, which broke the syncing if Guids were primary keys, so I switched to ULID via &lt;a href="https://github.com/RobThree/NUlid" rel="noopener noreferrer"&gt;NUlid&lt;/a&gt; and all problems were resolved. It also has the benefit of being sortable, and encoding datetime information inside of it. I don't know where this Guid bug ultimately can be blamed, but this is the kind of mysterious thing that you may end up dealing with given that you are using different compiler toolchains on the different platforms. Mono and .NET are still not perfectly identical, even though they are converging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Entity Framework migrations support for SQLite is still limited, but improving.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The new Single-Project concept is more trouble than it is worth right now. The old Xamarin.Forms per-platorm project approach worked better. The new single-project style will often trigger a build for all of the platforms when you are only meaning to run or publish a single one. And unit tests can be annoying to set up with the Single-Project approach because the target frameworks are incompatible. I find myself commenting out parts of the CSPROJ frequently to deal with these kinds of issues, and had to add a bunch of conditional restrictions to various tags. These issues may just be growing pains.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Stuff I didn't like:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;WinUI3 is still lacking basic and critical features like camera support and a native map control. And it is the only supported Windows target.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Encountered a fair amount of bugs. Most of them have been resolved, or I &lt;a href="https://dev.to/symbiogenesis/net-maui-ios-camera-rotated-bug-fix-3ee4"&gt;found workarounds&lt;/a&gt;, but new ones pop up as well. Expect some of this. Thankfully, the service releases come out monthly.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It feels like source generators are slowly taking over. It can be implemented really well sometimes, and there are lots of cool ones for regex and enums and such, but I didn't like the way it was done in CommunityToolkit.Mvvm. The magicalness of it is more of a downside than an upside. Also, I have experience with the older approach from PropertyChanged.Fody and friends, and that style seemed nicer. But you can just choose not to use them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It supports so many fast-moving targets that are ultimately out of the control of the MAUI team, so a lot of things will inevitably break with new releases of Android, iOS, Mac, Tizen, and WinUI. The fact that it targets native controls on each of the platforms contributes to inconsistencies, and &lt;a href="https://learn.microsoft.com/en-us/events/dotnetconf-2021/drawn-controls-in-net-maui" rel="noopener noreferrer"&gt;drawn controls&lt;/a&gt; are still a ways off.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Similarly, the submission processes to the various app stores can be challenging, and ever-evolving, but I eventually got the hang of it. Even things like version strings can be a challenge, and MAUI doesn't try to enforce a default versioning standard that will reliably work in all the stores. But it isn't a unified process. You use the Archive tool for Android and iOS, but use a separate wizard for Windows. Luckily, &lt;em&gt;&lt;strong&gt;dotnet publish&lt;/strong&gt;&lt;/em&gt; works as a consistent pipeline-friendly alternative. And EXE support on Windows is here too.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MAUI Shapes cannot be used as an ImageSource, which precludes easily using them in buttons, various libraries, etc.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is no good free option for a DataGrid. But with a bunch of work I was able to adapt a basic &lt;a href="https://github.com/akgulebubekir/Maui.DataGrid/" rel="noopener noreferrer"&gt;open source library&lt;/a&gt; for my needs. I just made about a dozen PRs into that repo, so hopefully you will be able to benefit from my efforts. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I don't particularly like the &lt;em&gt;&lt;strong&gt;UseMaui&lt;/strong&gt;&lt;/em&gt; tag in the csproj since it just hides the package version. Useless magic. Luckily, nuget lockfiles can help keep it clear what service release you are on, etc. In addition to all the many other reasons to use lockfiles. Hope nuget turns them on by default sometime soon.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>career</category>
      <category>productivity</category>
      <category>writing</category>
      <category>leadership</category>
    </item>
  </channel>
</rss>
