Andrea Bizzotto iOS & Flutter Development Blog http://bizz84.github.io/ Mon, 02 Dec 2019 17:04:42 +0000 Mon, 02 Dec 2019 17:04:42 +0000 Jekyll v3.8.5 Case Study: Automating UI/Integration Tests with Flutter Driver and Codemagic <blockquote> <p>Written by: <strong>Andrea Bizzotto</strong> of <a href="https://codingwithflutter.com/">Coding With Flutter</a></p> </blockquote> <p>Unit tests and widget tests are a great way of testing classes, functions and widgets in isolation. However, it is just as important to test that all these moving parts work together as a whole.</p> <p>And when dealing with complex apps with many screens, it is hard and error-prone to manually test multiple user journeys.</p> <p>Integration tests help us automate this process, and we can use them to test our Flutter apps on a real device or emulator.</p> <p>So in this article, we will learn how to write integration tests using Flutter driver, and see how they differ from widget tests.</p> <p>And we will use them to test a user journey for an existing open source app built with Flutter &amp; Firebase.</p> <p>Finally, we will see how to run them with Codemagic, either on an emulator, or a real device powered by the AWS Device Farm.</p> <h2 id="application-overview">Application overview</h2> <p>We will use this open source app as an example:</p> <ul> <li><a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a></li> </ul> <p>This app shows how to use different sign-in methods to authenticate with Firebase. It already implements a sign-in and sign-out flow that is composed of four steps:</p> <p><img src="/images/integration-tests-all-screnshots.png" alt="" /></p> <p>This flow is a good candidate for an automated integration test (<strong>UI test</strong> to be precise, more on this below).</p> <p>So our goal is to write a test that can loop through the four steps above by interacting with the UI, and verify that the correct widgets are in place after every interaction.</p> <p>And because integration tests run with Flutter driver, we will be able to see the app run on the simulator as the test executes:</p> <p><img src="/images/integration-tests-loop.gif" alt="" /></p> <h2 id="integration-tests-or-ui-tests">Integration tests or UI tests?</h2> <p>Our application uses Firebase authentication to sign-in the user. And in general, you apps will be using various services to talk to the network or other external inputs (e.g. location updates).</p> <p>So, should integration tests run with a <strong>real</strong> or <strong>mocked</strong> network service?</p> <p>While there is value in testing the whole product end-to-end, there are also some risks:</p> <ul> <li>you don’t want to pollute the production environment with network requests from test code.</li> <li>testing with the real network service can be slow at best, and unreliable at worst.</li> </ul> <p>So in my apps I always mock the network service. This gives me a more controlled environment where I can stub network calls, and even decide how long they should take to respond. This makes my tests more predictable and reliable.</p> <p>Strictly speaking, this means that I’m writing <strong>UI tests</strong>. Because I’m mimicking the behaviour of real users, by simulating their interaction with the app via gestures. <em>For clarity, I will stick with the term <strong>integration test</strong> for the rest of the article, as this is how they are called in Flutter.</em></p> <p>With that said, let’s get back to our example.</p> <h2 id="mocking-the-authentication-service">Mocking the authentication service</h2> <p>In practice, we want to run an integration test for the entire app, so there needs to be a way to <strong>inject</strong> a mock authentication service at the top of the widget tree.</p> <p>In my example project, I have accomplished this by defining this enum:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">enum</span> <span class="n">AuthServiceType</span> <span class="o">{</span> <span class="n">firebase</span><span class="o">,</span> <span class="n">mock</span> <span class="o">}</span> </code></pre></div></div> <p>And when I create the Flutter app, I inject the desired <code class="language-plaintext highlighter-rouge">AuthServiceType</code> like so:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">runApp</span><span class="o">(</span><span class="n">MyApp</span><span class="o">());</span> <span class="kd">class</span> <span class="nc">MyApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="c1">// [initialAuthServiceType] is made configurable for testing</span> <span class="kd">const</span> <span class="n">MyApp</span><span class="o">({</span><span class="k">this</span><span class="o">.</span><span class="na">initialAuthServiceType</span> <span class="o">=</span> <span class="n">AuthServiceType</span><span class="o">.</span><span class="na">firebase</span><span class="o">});</span> <span class="kd">final</span> <span class="n">AuthServiceType</span> <span class="n">initialAuthServiceType</span><span class="o">;</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// top-level providers and MaterialApp here</span> <span class="c1">// create real or mock authentication service based on initialAuthServiceType.</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>With this setup, the production app will always use the Firebase authentication service, while test code can use a mock service instead.</p> <p>And in general, I recommend injecting all service classes at the top of the widget tree (above <code class="language-plaintext highlighter-rouge">MaterialApp</code>), so that it is easier to swap them out when configuring widget and integration tests.</p> <p>With this in mind, we’re ready to add our integration tests.</p> <h2 id="adding-integration-tests-with-flutter-driver">Adding integration tests with Flutter Driver</h2> <p>According to the official documentation, integration tests require two steps:</p> <ol> <li>deploy an instrumented application to a real device or emulator.</li> <li>“drive” the application from a separate test suite, checking to make sure everything is correct along the way.</li> </ol> <p>This is different to how widget tests are run, where we use a test environment that is much simpler (and faster) than a full-blown UI system.</p> <p><em>NOTE: If you’re not familiar with integration tests, I recommend reading <a href="https://flutter.dev/docs/cookbook/testing/integration/introduction">An introduction to integration testing</a> from the Flutter documentation.</em></p> <p>Step one is to add the <code class="language-plaintext highlighter-rouge">flutter_driver</code> package to the <code class="language-plaintext highlighter-rouge">pubspec.yaml</code> file:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dev_dependencies: flutter_driver: sdk: flutter </code></pre></div></div> <p>And to create our integration tests, we can define a project-level folder with two files:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>test_driver/ app_test.dart app.dart </code></pre></div></div> <p>Inside the <code class="language-plaintext highlighter-rouge">app.dart</code> file, we can add the following:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// project-specific imports</span> <span class="kn">import</span> <span class="s">'package:firebase_auth_demo_flutter/main.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:firebase_auth_demo_flutter/services/auth_service_adapter.dart'</span><span class="o">;</span> <span class="c1">// flutter-specific imports</span> <span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:flutter_driver/driver_extension.dart'</span><span class="o">;</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// This line enables the extension.</span> <span class="n">enableFlutterDriverExtension</span><span class="o">();</span> <span class="c1">// Call the `main()` function of the app, or call `runApp` with</span> <span class="c1">// any widget you are interested in testing.</span> <span class="n">runApp</span><span class="o">(</span><span class="n">MyApp</span><span class="o">(</span><span class="nl">initialAuthServiceType:</span> <span class="n">AuthServiceType</span><span class="o">.</span><span class="na">mock</span><span class="o">));</span> <span class="o">}</span> </code></pre></div></div> <p>Note how we’re passing <code class="language-plaintext highlighter-rouge">AuthServiceType.mock</code> when we create the <code class="language-plaintext highlighter-rouge">MyApp</code> widget.</p> <p>Then we can start implementing the <code class="language-plaintext highlighter-rouge">app_test.dart</code> file, by adding the standard boilerplate code for the <code class="language-plaintext highlighter-rouge">FlutterDriver</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Imports the Flutter Driver API.</span> <span class="kn">import</span> <span class="s">'package:flutter_driver/flutter_driver.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:test/test.dart'</span><span class="o">;</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span> <span class="n">FlutterDriver</span> <span class="n">driver</span><span class="o">;</span> <span class="c1">// Connect to the Flutter driver before running any tests.</span> <span class="n">setUpAll</span><span class="o">(()</span> <span class="n">async</span> <span class="o">{</span> <span class="n">driver</span> <span class="o">=</span> <span class="n">await</span> <span class="n">FlutterDriver</span><span class="o">.</span><span class="na">connect</span><span class="o">();</span> <span class="o">});</span> <span class="c1">// Close the connection to the driver after the tests have completed.</span> <span class="n">tearDownAll</span><span class="o">(()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">driver</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">driver</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">});</span> <span class="o">}</span> </code></pre></div></div> <p>After this, we can add a quick test to check the health status of our Flutter Driver extension. It’s a good idea to run this before any other tests.</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">test</span><span class="o">(</span><span class="s">'check flutter driver health'</span><span class="o">,</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">health</span> <span class="o">=</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">checkHealth</span><span class="o">();</span> <span class="n">expect</span><span class="o">(</span><span class="n">health</span><span class="o">.</span><span class="na">status</span><span class="o">,</span> <span class="n">HealthStatus</span><span class="o">.</span><span class="na">ok</span><span class="o">);</span> <span class="o">});</span> </code></pre></div></div> <p><em>Credit: <a href="https://medium.com/@darshankawar">Darshan Kawar</a>, who wrote this good article about <a href="https://medium.com/flutter-community/testing-flutter-ui-with-flutter-driver-c1583681e337">Testing Flutter UI with Flutter Driver</a>.</em></p> <p>Next, we can add a method that we will use to add a short delay between UI interactions. As we will see, this is used only for demo purposes:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">delay</span><span class="o">([</span><span class="kt">int</span> <span class="n">milliseconds</span> <span class="o">=</span> <span class="mi">250</span><span class="o">])</span> <span class="n">async</span> <span class="o">{</span> <span class="n">await</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;.</span><span class="na">delayed</span><span class="o">(</span><span class="n">Duration</span><span class="o">(</span><span class="nl">milliseconds:</span> <span class="n">milliseconds</span><span class="o">));</span> <span class="o">}</span> </code></pre></div></div> <p>With this in place, we can start writing the first test:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">test</span><span class="o">(</span><span class="s">'sign in anonymously, sign out'</span><span class="o">,</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="c1">// 1. find and tap anonymous sign in button</span> <span class="kd">final</span> <span class="n">anonymousSignInButton</span> <span class="o">=</span> <span class="n">find</span><span class="o">.</span><span class="na">byValueKey</span><span class="o">(</span><span class="n">Keys</span><span class="o">.</span><span class="na">anonymous</span><span class="o">);</span> <span class="c1">// 2. check to fail early if the auth state is authenticated</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">waitFor</span><span class="o">(</span><span class="n">anonymousSignInButton</span><span class="o">);</span> <span class="c1">// 3. add small delay for demo recording</span> <span class="n">await</span> <span class="n">delay</span><span class="o">(</span><span class="mi">750</span><span class="o">);</span> <span class="c1">// 4. interact with UI</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">tap</span><span class="o">(</span><span class="n">anonymousSignInButton</span><span class="o">);</span> <span class="o">});</span> </code></pre></div></div> <p>On step 1, we use the <code class="language-plaintext highlighter-rouge">find</code> object to get a handle to a widget by key.</p> <p><strong>Note</strong>: this is not the same finder used with widget tests, and the value passed to <code class="language-plaintext highlighter-rouge">byValueKey</code> needs to be a <code class="language-plaintext highlighter-rouge">String</code>, not a <code class="language-plaintext highlighter-rouge">Key</code> object.</p> <p>In fact, things will break if we use this import in the integration tests:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter_test/flutter_test.dart'</span><span class="o">;</span> </code></pre></div></div> <p>Instead, we should use only the symbols defined in the <code class="language-plaintext highlighter-rouge">flutter_driver</code> package:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter_driver/flutter_driver.dart'</span><span class="o">;</span> </code></pre></div></div> <p>And since we’re referencing widgets by key, I defined a <code class="language-plaintext highlighter-rouge">Keys</code> class to avoid duplication and hard-coded strings across the production and test code:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Keys</span> <span class="o">{</span> <span class="c1">// list of widget keys that need to be accessed in the test code</span> <span class="kd">static</span> <span class="kd">const</span> <span class="kt">String</span> <span class="n">anonymous</span> <span class="o">=</span> <span class="s">'anonymous'</span><span class="o">;</span> <span class="kd">static</span> <span class="kd">const</span> <span class="kt">String</span> <span class="n">logout</span> <span class="o">=</span> <span class="s">'logout'</span><span class="o">;</span> <span class="kd">static</span> <span class="kd">const</span> <span class="kt">String</span> <span class="n">alertDefault</span> <span class="o">=</span> <span class="s">'alertDefault'</span><span class="o">;</span> <span class="kd">static</span> <span class="kd">const</span> <span class="kt">String</span> <span class="n">alertCancel</span> <span class="o">=</span> <span class="s">'alertCancel'</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <p>Accordingly, in my sign-in page I will have a button, defined like this:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SignInButton</span><span class="o">(</span> <span class="nl">key:</span> <span class="n">Key</span><span class="o">(</span><span class="n">Keys</span><span class="o">.</span><span class="na">anonymous</span><span class="o">),</span> <span class="nl">text:</span> <span class="n">Strings</span><span class="o">.</span><span class="na">goAnonymous</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">lime</span><span class="o">[</span><span class="mi">300</span><span class="o">],</span> <span class="nl">textColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black87</span><span class="o">,</span> <span class="nl">onPressed:</span> <span class="n">isLoading</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="o">()</span> <span class="o">=&gt;</span> <span class="n">_signInAnonymously</span><span class="o">(</span><span class="n">context</span><span class="o">),</span> <span class="o">),</span> </code></pre></div></div> <p>Let’s get back on track. Here’s our test once again:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">test</span><span class="o">(</span><span class="s">'sign in anonymously, sign out'</span><span class="o">,</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="c1">// 1. find and tap anonymous sign in button</span> <span class="kd">final</span> <span class="n">anonymousSignInButton</span> <span class="o">=</span> <span class="n">find</span><span class="o">.</span><span class="na">byValueKey</span><span class="o">(</span><span class="n">Keys</span><span class="o">.</span><span class="na">anonymous</span><span class="o">);</span> <span class="c1">// 2. check to fail early if the auth state is authenticated</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">waitFor</span><span class="o">(</span><span class="n">anonymousSignInButton</span><span class="o">);</span> <span class="c1">// 3. add small delay for demo recording</span> <span class="n">await</span> <span class="n">delay</span><span class="o">(</span><span class="mi">750</span><span class="o">);</span> <span class="c1">// 4. interact with UI</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">tap</span><span class="o">(</span><span class="n">anonymousSignInButton</span><span class="o">);</span> <span class="o">});</span> </code></pre></div></div> <p>In step 2, we ask the driver to wait until we find the anonymous sign-in button. It is good practice to check this before any UI interaction takes place, to ensure that we’re in the initial sign-in page and we’re showing the correct buttons.</p> <p>In fact, we should always keep in mind that <strong>the application state can change across tests</strong>. In other words, multiple integration tests are “sharing” the same application, and can introduce side effects if each test doesn’t return to the initial application state.</p> <p>Step 3 adds a small delay, and step 4 dispatches a tap gesture to the button.</p> <p>As a result, the (mock) authentication service is called, with a request to sign-in the user. All the mock service does is to wait for a short delay, and change its internal state to indicate that the user is signed-in. When this happens, the application updates itself and shows the home page:</p> <p><img src="/images/integration-tests-sign-in-sequence.png" alt="" /></p> <p>All good so far, but our test implementation is not complete yet.</p> <p>Next, we want to find and tap the logout button:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// find and tap logout button</span> <span class="kd">final</span> <span class="n">logoutButton</span> <span class="o">=</span> <span class="n">find</span><span class="o">.</span><span class="na">byValueKey</span><span class="o">(</span><span class="n">Keys</span><span class="o">.</span><span class="na">logout</span><span class="o">);</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">waitFor</span><span class="o">(</span><span class="n">logoutButton</span><span class="o">);</span> <span class="n">await</span> <span class="nf">delay</span><span class="p">(</span><span class="mi">750</span><span class="o">);</span> <span class="c1">// for video capture</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">tap</span><span class="o">(</span><span class="n">logoutButton</span><span class="o">);</span> </code></pre></div></div> <p>This works just like our previous code. We reference a button by key, and wait for the driver to find it. Once the home page is presented, all UI animations are complete, and we return from the first <code class="language-plaintext highlighter-rouge">await</code> call.</p> <p>And once we have a logout button, we can tap on it.</p> <p>In this specific flow, the app shows a dialog asking for logout confirmation:</p> <p><img src="/images/integration-tests-logout-confirmation-crop.png" alt="" /></p> <p>And by now, we know how to deal with this:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// find and tap confirm logout button</span> <span class="kd">final</span> <span class="n">confirmLogoutButton</span> <span class="o">=</span> <span class="n">find</span><span class="o">.</span><span class="na">byValueKey</span><span class="o">(</span><span class="n">Keys</span><span class="o">.</span><span class="na">alertDefault</span><span class="o">);</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">waitFor</span><span class="o">(</span><span class="n">confirmLogoutButton</span><span class="o">);</span> <span class="n">await</span> <span class="nf">delay</span><span class="p">(</span><span class="mi">750</span><span class="o">);</span> <span class="c1">// for video capture</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">tap</span><span class="o">(</span><span class="n">confirmLogoutButton</span><span class="o">);</span> </code></pre></div></div> <p>After we tap on the logout confirmation button, another call is made to sign-out with the mock authentication service. In turn, this updates the authentication status.</p> <p>Finally, we want to check that we’re back to the sign-in page. And we can complete our test with one final line:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// try to find anonymous sign in button again</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">waitFor</span><span class="o">(</span><span class="n">anonymousSignInButton</span><span class="o">);</span> </code></pre></div></div> <p>All in all, this is the code for the entire test:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">test</span><span class="o">(</span><span class="s">'sign in anonymously, sign out'</span><span class="o">,</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="c1">// find and tap anonymous sign in button</span> <span class="kd">final</span> <span class="n">anonymousSignInButton</span> <span class="o">=</span> <span class="n">find</span><span class="o">.</span><span class="na">byValueKey</span><span class="o">(</span><span class="n">Keys</span><span class="o">.</span><span class="na">anonymous</span><span class="o">);</span> <span class="c1">// Check to fail early if the auth state is authenticated</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">waitFor</span><span class="o">(</span><span class="n">anonymousSignInButton</span><span class="o">);</span> <span class="n">await</span> <span class="n">delay</span><span class="o">(</span><span class="mi">750</span><span class="o">);</span> <span class="c1">// for video capture</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">tap</span><span class="o">(</span><span class="n">anonymousSignInButton</span><span class="o">);</span> <span class="c1">// find and tap logout button</span> <span class="kd">final</span> <span class="n">logoutButton</span> <span class="o">=</span> <span class="n">find</span><span class="o">.</span><span class="na">byValueKey</span><span class="o">(</span><span class="n">Keys</span><span class="o">.</span><span class="na">logout</span><span class="o">);</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">waitFor</span><span class="o">(</span><span class="n">logoutButton</span><span class="o">);</span> <span class="n">await</span> <span class="n">delay</span><span class="o">(</span><span class="mi">750</span><span class="o">);</span> <span class="c1">// for video capture</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">tap</span><span class="o">(</span><span class="n">logoutButton</span><span class="o">);</span> <span class="c1">// find and tap confirm logout button</span> <span class="kd">final</span> <span class="n">confirmLogoutButton</span> <span class="o">=</span> <span class="n">find</span><span class="o">.</span><span class="na">byValueKey</span><span class="o">(</span><span class="n">Keys</span><span class="o">.</span><span class="na">alertDefault</span><span class="o">);</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">waitFor</span><span class="o">(</span><span class="n">confirmLogoutButton</span><span class="o">);</span> <span class="n">await</span> <span class="n">delay</span><span class="o">(</span><span class="mi">750</span><span class="o">);</span> <span class="c1">// for video capture</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">tap</span><span class="o">(</span><span class="n">confirmLogoutButton</span><span class="o">);</span> <span class="c1">// try to find anonymous sign in button again</span> <span class="n">await</span> <span class="n">driver</span><span class="o">.</span><span class="na">waitFor</span><span class="o">(</span><span class="n">anonymousSignInButton</span><span class="o">);</span> <span class="o">});</span> </code></pre></div></div> <p>The last thing to do is to run our integration test:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter drive --target=test_driver/app.dart </code></pre></div></div> <p>And then, we can sit back and relax as Flutter driver runs the test on our simulator (look, no hands 😀):</p> <p><img src="/images/integration-tests-loop.gif" alt="" /></p> <p>For reference, this is a stripped-out log of the output from Flutter driver:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Starting application: test_driver/app.dart Running Xcode build... ├─Assembling Flutter resources... 10.8s └─Compiling, linking and signing... 12.0s Xcode build done. 27.0s Configuring the default Firebase app... flutter: Observatory listening on http://127.0.0.1:62531/q6q6Nbol1fU=/ Configured the default Firebase app __FIRAPP_DEFAULT. [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:62531/q6q6Nbol1fU=/ [trace] FlutterDriver: Isolate found with number: 844330546624811 [trace] FlutterDriver: Isolate is paused at start. [trace] FlutterDriver: Attempting to resume isolate [trace] FlutterDriver: Waiting for service extension [info ] FlutterDriver: Connected to Flutter application. 00:01 +0: check flutter driver health 00:01 +1: sign in anonymously, sign out 00:07 +2: (tearDownAll) 00:07 +2: All tests passed! Stopping application instance. </code></pre></div></div> <p>Note how the test took 7 seconds to run. This would run faster if we removed the artificial delays. But it’s worth noting that running integration tests takes longer than widget and unit tests.</p> <p>Still, we can now write integration tests that cover <a href="https://github.com/flutter/flutter/issues/9002">almost</a> all the user flows in our app, which is great! 🚀</p> <p>And our testers will be super-delighted, as they will no longer need to do this by hand. 🙏</p> <p><img src="/images/integration-tests-meme-pro.jpg" alt="" /></p> <h2 id="running-on-codemagic">Running on Codemagic</h2> <p>Having unit, widget and integration tests is good.</p> <p>But it’s even better to run them automatically every time we open or update a pull request.</p> <p>And this is where Codemagic comes in.</p> <p>Codemagic offers a user-friendly UI that makes it easy to automate the testing and delivery of our apps.</p> <p>And when it comes to testing, we can configure our builds to run Flutter tests (unit and widget) and Flutter Driver tests (integration):</p> <p><img src="/images/integration-tests-codemagic-tests.png" alt="" /></p> <p>Not only that, but we can choose to run on the iOS simulator, Android emulator, and even on a real device running on the <a href="https://aws.amazon.com/device-farm/">AWS Device Farm</a>.</p> <p>And because end-users will be using your apps on real devices, running automated tests in this way is very important.</p> <p>So for this project, I have setup an AWS account, and registered an <strong>AWS access key ID</strong> and <strong>AWS secret access key</strong> <a href="https://blog.codemagic.io/test-flutter-app-on-aws-device-farm/">as outlined here</a>.</p> <p>And after configuring this on Codemagic, I ran my tests, and got the desired results for the integration tests in my build log:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Run 'sylph run 2019-10-22 01:09:31.259' completed 1 of 1 jobs. Result: PASSED Device minutes: 7.98 (5.57 metered). Counters: skipped: 0 warned: 0 failed: 0 stopped: 0 passed: 3 errored: 0 total: 3 Downloading artifacts... Downloading artifacts to /tmp/sylph_artifacts/sylph_run_2019-10-22_01_09_31.259/test_concurrent_runs/android_pool_1/Google_Pixel_2-Google_Pixel_2-8.0.0 results=[{result: true}] Concurrent runs completed. Sylph run completed in 12m:32s:975ms. Sylph run 'sylph run 2019-10-22 01:09:31.259' succeeded. </code></pre></div></div> <h2 id="conclusion">Conclusion</h2> <p>In this article we have seen how to setup and write integration tests for our Flutter apps with Flutter Driver.</p> <p>We have gained more insight into how integration tests work, and how they differ from widget tests.</p> <p>And we have seen how to use them to test user flows, using my <a href="https://aws.amazon.com/device-farm/">reference authentication demo app</a>.</p> <p>Finally, we have automated the entire process by setting up a workflow to run all the tests on Codemagic. And we have seen how to test on real devices with the AWS Device Farm.</p> <p>And if we want to go further, we can even write integration tests to <a href="https://flutter.dev/docs/cookbook/testing/integration/profiling">record the performance</a> of our app. This way, we can ensure that the UI works smoothly on a variety of devices running on AWS Device Farm.</p> <p>For more in-depth information on some of the topics covered in this article, I recommend reading the following resources:</p> <ul> <li><a href="https://flutter.dev/docs/cookbook/testing/integration/introduction">An introduction to integration testing</a></li> <li><a href="https://flutter.dev/docs/cookbook/testing/integration/profiling">Performance profiling</a></li> <li><a href="https://medium.com/flutter-community/testing-flutter-ui-with-flutter-driver-c1583681e337">Testing Flutter UI with Flutter Driver</a></li> <li><a href="http://cogitas.net/write-integration-test-flutter/">How to write an integration test in Flutter</a></li> <li><a href="https://blog.codemagic.io/practical-guide-flutter-firebase-codemagic/">Practical guide: Flutter + Firebase + Codemagic</a></li> <li><a href="https://blog.codemagic.io/test-flutter-app-on-aws-device-farm/">Run Flutter integration tests on real devices with AWS Device Farm and Sylph</a></li> </ul> <p>The full source code for the reference authentication demo is available on GitHub:</p> <ul> <li><a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a></li> </ul> <hr /> <p>What do you think? Do you write integration tests for your Flutter apps, or plan to? Let me know on Twitter at <a href="https://twitter.com/biz84">@biz84</a>. You can also check my other tutorials at <a href="https://codingwithflutter.com/">codingwithflutter.com</a>.</p> Wed, 30 Oct 2019 03:00:18 +0000 http://bizz84.github.io/2019/10/30/integration-tests-codemagic.html http://bizz84.github.io/2019/10/30/integration-tests-codemagic.html Flutter, Android, iOS, Mobile App Development Flutter & Firebase Full Course: Special Launch Offer <p>After nearly <strong>one year</strong> of intense work, my full Flutter course is finally ready and publicly available!</p> <p><img src="/images/flutter-course-live-banner.jpg" alt="" /></p> <p>Over the last few months, many students have enrolled and gave it great praise, making it one of the highest-rated courses on Udemy.</p> <p>For that, I’m immensely grateful!</p> <p>And now, the course is complete with <strong>23 hours</strong> of content.</p> <h2 id="special-launch-promo-codes">Special Launch Promo Codes</h2> <p>Enroll now with these links to take advantage of my launch sale:</p> <p><a href="https://www.udemy.com/course/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=LAUNCH10&amp;password=codingwithflutter"><strong>$9.99 - LAUNCH10</strong></a> (first 50 students)</p> <p><a href="https://www.udemy.com/course/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=LAUNCH12&amp;password=codingwithflutter"><strong>$11.99 - LAUNCH12</strong></a> (next 50 students)</p> <p><a href="https://www.udemy.com/course/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=DART15&amp;password=codingwithflutter"><strong>$14.99 - DART15</strong></a> (everyone else)</p> <p>Note: this is offered with a <strong>30-Day Money-Back Guarantee</strong>.</p> <h2 id="who-is-this-course-for">Who is this course for?</h2> <p>This is a <strong>beginner</strong> &amp; <strong>intermediate</strong>-level course. It is ideal for:</p> <ul> <li>Beginners who are getting started with Flutter &amp; Firebase.</li> <li>Developers who already understand the basics, and want to build production-ready Flutter apps following best practices.</li> <li>Developers who want to write apps on iOS and Android with a single code-base.</li> </ul> <h2 id="whats-in-the-course">What’s in the course?</h2> <p>All the basics are covered, so that you can start with Dart, Flutter &amp; Firebase from scratch.</p> <p>But there is a lot more.</p> <p>You will learn how to build a complete, production-ready app for iOS &amp; Android, following a practical, hands-on approach.</p> <p>Important concepts are explained with clear diagrams. You will always learn <strong>what</strong> you will be building and <strong>why</strong>, and then <strong>how</strong> to do it.</p> <p>The following topics are covered:</p> <ul> <li>Introduction to Dart</li> <li>Setup instructions for macOS &amp; Windows</li> <li>Building Layouts</li> <li>Navigation</li> <li>Firebase Authentication (including Google &amp; Facebook auth)</li> <li>State Management (stateless and stateful widgets)</li> <li>Streams &amp; reactive apps</li> <li>Forms, input handling and validation</li> <li>Material, Cupertino and platform-aware widgets</li> <li>InheritedWidget and Provider</li> <li>BLoCs</li> <li>State Management with Provider</li> <li>Databases and Cloud Firestore</li> <li>Forms, ListViews, Date &amp; Time pickers</li> <li>Advanced stream operations with RxDart</li> <li>Unit &amp; Widget tests with mockito</li> </ul> <h2 id="course-structure">Course Structure</h2> <p>The course has a linear structure:</p> <p><img src="/images/flutter-course-live-sections-sequence.png" alt="" /></p> <p>Each lesson builds on top of the previous one.</p> <p>Each section covers a different topic.</p> <p>So you can follow the course from beginning to end, or choose the topics you’re most interested in.</p> <p>In any case, the course contains a wealth of practical advice, along with tips and techniques that I have battle-tested in my work.</p> <h2 id="great-reviews-already">Great Reviews Already!</h2> <p>The course has an exceptional rating of 4.8 stars, which is one of the highest for Flutter (and Udemy overall).</p> <p>Here are some reviews from students that have taken it:</p> <p><em>“Clear explanations, a useful practical application that demonstrates all the core things essential to building an app ready for release into the wild. Every step taken is clearly and thoroughly explained, plus downloadable code for every section of this course. A fantastic current course and is proving invaluable for strategies for building ideas for my own app. Cannot praise this course and Andrea’s presentation highly enough.” - Cheryl Kirsten</em></p> <p><em>“I can’t recommend this course highly enough, as a professional developer looking for a fast track into Flutter development Andrea’s course has paid for itself many times over, the state management section alone is worth the price of admission.. the entire course is perfectly paced and provides a wealth of practical advice and just enough code to kickstart your own Flutter projects. Just excellent.” - Bruce Rees</em></p> <p><em>“There are a lot of courses on Udemy that claim to be a “complete course on Flutter”, but this TRULY is a complete course on Flutter. Andrea teaches you how to write production ready code that can easily be scaled, and teaches you concepts that go far beyond just “making it work”. He’s very response to message, and is constantly improving the content. Amazing course! “ - Zeeshan Syed</em></p> <p><em>“I’ve taken several Flutter courses online and have found them very useful in their own right. This course, however, has done the BEST job in covering and explaining (a) best practices (specifically around modularizing your code and structuring your project accordingly) and (b) the always controversial Flutter state management. Andrea provides examples of the varying state management concepts and it’s easy to grasp the advantages and shortcomings of each. I highly recommend this course if you’re looking for a deeper-dive into building a much more complete and production-ready app.” - J Mendoza</em></p> <p><em>“Not only does the instructor provide syntax to use for a flutter/dart implementation for this app, but he also takes the viewer through progression of implementations to help explain various ways to implement and why. This is very rare to see and so much more effective. One of the best of the courses or tutorials I’ve seen on Udemy, lynda, or YouTube for programming languages. Certainly the best of the Flutter tutorials so far. Definitely, above expectations!” - Gadgetologist</em></p> <h2 id="do-you-want-to-become-a-competent-flutter-developer">Do you want to become a competent Flutter developer?</h2> <p>If you want to fast-track your Flutter learning.</p> <p>If you want to support my work.</p> <p>This is the time to buy my course at an incredibly discounted price.</p> <p>And recommend it to your friends as well.</p> <h2 id="enroll-today---starting-at-only-999">Enroll Today - starting at only $9.99</h2> <p><a href="https://www.udemy.com/course/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=LAUNCH10&amp;password=codingwithflutter"><strong>$9.99 - LAUNCH10</strong></a> (first 50 students)</p> <p><a href="https://www.udemy.com/course/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=LAUNCH12&amp;password=codingwithflutter"><strong>$11.99 - LAUNCH12</strong></a> (next 50 students)</p> <p><a href="https://www.udemy.com/course/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=DART15&amp;password=codingwithflutter"><strong>$14.99 - DART15</strong></a> (everyone else)</p> <p><em>NOTE: Courses are often on sale on Udemy, and this one is no different. However, buying the course with an instructor coupon <a href="https://support.udemy.com/hc/en-us/articles/229605008-Instructor-Revenue-Share">is the best way</a> to support my work. And by using the links above you can get it at a great price.</em></p> <p>I hope that you will join other students that are learning Flutter with my course.</p> <p>Happy coding!</p> <script src="https://assets.convertkit.com/assets/CKJS4.js?v=21"></script> <div class="ck_form_container ck_inline" data-ck-version="7"> <div class="ck_form ck_minimal"> <div class="ck_form_fields"> <h3 class="ck_form_title">LEARN FLUTTER TODAY</h3> <div class="ck_description"> <p><img src="/img/coding-with-flutter-cheat-sheet-panel-noborder.png" style="display:block; margin-left: auto; margin-right: auto;" width="307" /></p> <p>Sign up for updates and get my free Flutter Layout Cheat Sheet.</p> </div> <div id="ck_success_msg" style="display:none;"> <p>Success! Now check your email to confirm your subscription.</p> </div> <!-- Form starts here --> <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/403521/subscribe" data-remote="true"> <input type="hidden" value="{&quot;form_style&quot;:&quot;minimal&quot;,&quot;converted_behavior&quot;:&quot;show&quot;,&quot;days_no_show&quot;:&quot;15&quot;,&quot;delay_seconds&quot;:&quot;10&quot;,&quot;display_devices&quot;:&quot;all&quot;,&quot;display_position&quot;:&quot;br&quot;,&quot;embed_style&quot;:&quot;inline&quot;,&quot;embed_trigger&quot;:&quot;scroll_percentage&quot;,&quot;scroll_percentage&quot;:&quot;70&quot;}" id="ck_form_options" /> <input type="hidden" name="id" value="403521" id="landing_page_id" /> <input type="hidden" name="ck_form_recaptcha" value="" id="ck_form_recaptcha" /> <div class="ck_errorArea"> <div id="ck_error_msg" style="display:none"> <p>There was an error submitting your subscription. Please try again.</p> </div> </div> <div class="ck_control_group ck_email_field_group"> <label class="ck_label" for="ck_emailField" style="display: none">Email Address</label> <input type="email" name="email" class="ck_email_address" id="ck_emailField" placeholder="Email Address" required="" /> </div> <div class="ck_control_group ck_captcha2_h_field_group ck-captcha2-h" style="position: absolute !important;left: -999em !important;"> <input type="text" name="captcha2_h" class="ck-captcha2-h" id="ck_captcha2_h" placeholder="We use this field to detect spam bots. If you fill this in, you will be marked as a spammer." /> </div> <label class="ck_checkbox" style="display:none;"> <input class="optIn ck_course_opted" name="course_opted" type="checkbox" id="optIn" checked="" /> <span class="ck_opt_in_prompt">I'd like to receive the free email course.</span> </label> <button class="subscribe_button ck_subscribe_button btn fields" id="ck_subscribe_button"> Subscribe </button> <span class="ck_guarantee"> No Spam. Ever. Unsubscribe at any time. </span> </form> </div> </div> </div> <style type="text/css">/* Layout */ .ck_form.ck_minimal { /* divider image */ background: #f9f9f9; font-family: 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; line-height: 1.5em; overflow: hidden; color: #202020; font-size: 16px; border: solid 1px #d1d1d1; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; clear: both; margin: 20px 0px; text-align: center; } .ck_form.ck_minimal h3.ck_form_title { text-align: center; margin: 0px 0px 10px; font-size: 28px; } .ck_form.ck_minimal h4 { text-align: center; font-family: 'Open Sans', Helvetica, Arial, sans-serif; text-transform: uppercase; font-size: 18px; font-weight: normal; padding-top: 0px; margin-top: 0px; } .ck_form.ck_minimal p { padding: 0px; } .ck_form, .ck_form * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .ck_form.ck_minimal .ck_form_fields { width: 100%; float: left; padding: 5%; } /* Form fields */ .ck_errorArea { display: none; /* temporary */ } #ck_success_msg { padding: 10px 10px 0px; border: solid 1px #ddd; background: #eee; } .ck_form.ck_minimal input[type="text"], .ck_form.ck_minimal input[type="email"] { font-size: 18px; padding: 10px 8px; width: 68%; border: 1px solid #d6d6d6; /* stroke */ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ background-color: #fff; /* layer fill content */ margin-bottom: 5px; height: auto; float: left; margin: 0px; margin-right: 2%; height: 42px; } .ck_form input[type="text"]:focus, .ck_form input[type="email"]:focus { outline: none; border-color: #aaa; } .ck_form.ck_minimal .ck_subscribe_button { width: 100%; color: #fff; margin: 0px; padding: 11px 0px; font-size: 18px; background: #0d6db8; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ cursor: pointer; border: none; text-shadow: none; width: 30%; float: left; height: 42px; } .ck_form.ck_minimal .ck_guarantee { color: #626262; font-size: 12px; text-align: center; padding: 15px 0px 0px; display: block; clear: both; } .ck_form .ck_powered_by { display: block; color: #aaa; font-size: 12px; } .ck_form .ck_powered_by:hover { display: block; color: #444; } .ck_converted_content { display: none; padding: 5%; background: #fff; } .ck_form.ck_minimal.width400 .ck_subscribe_button, .ck_form.ck_minimal.width400 input[type="email"] { width: 100%; float: none; margin-top: 5px; } .ck_slide_up, .ck_modal, .ck_slide_up .ck_minimal, .ck_modal .ck_minimal { min-width: 400px; } .page .ck_form.ck_minimal { margin: 50px auto; max-width: 600px; } /* v6 */ .ck_slide_up.ck_form_v6, .ck_modal.ck_form_v6, .ck_slide_up.ck_form_v6 .ck_minimal, .ck_modal.ck_form_v6 .ck_minimal { min-width: 0 !important; } @media all and (min-width: 801px) { .ck_modal.ck_form_v6 .ck_form.ck_minimal { margin-left: -300px; width: 600px; } } .ck_modal.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 20px; } .ck_slide_up.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 10px; } .ck_form_v6 #ck_success_msg { margin-top: 15px; padding: 0px 10px; } .ck_slide_up.ck_form_v6 .ck_minimal + .ck_close_link { top: 5px; } .ck_slide_up.ck_form_v6 .ck_minimal h3.ck_form_title { margin-top: 5px; } </style> Mon, 09 Sep 2019 03:00:18 +0000 http://bizz84.github.io/2019/09/09/flutter-firebase-course-live.html http://bizz84.github.io/2019/09/09/flutter-firebase-course-live.html Flutter, Android, iOS, Mobile App Development Dart Features for Better Code: Types and working with parameters <p>This tutorial presents some of the fundamental features of the Dart language, and shows how to use them in practice.</p> <p>Using them correctly leads to code that is <strong>clear</strong>, <strong>lightweight</strong>, and more <strong>robust</strong>.</p> <h1 id="1-type-inference">1. Type inference</h1> <p>The Dart compiler can automatically infer the type of variables from their <strong>initializer</strong>, so that we don’t have to declare the type ourselves.</p> <p>In practice, this means that we can convert this code:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">String</span> <span class="n">name</span> <span class="o">=</span> <span class="s">'Andrea'</span><span class="o">;</span> <span class="kt">int</span> <span class="n">age</span> <span class="o">=</span> <span class="mi">35</span><span class="o">;</span> <span class="kt">double</span> <span class="n">height</span> <span class="o">=</span> <span class="mf">1.84</span><span class="o">;</span> </code></pre></div></div> <p>to this:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="n">name</span> <span class="o">=</span> <span class="s">'Andrea'</span><span class="o">;</span> <span class="kd">var</span> <span class="n">age</span> <span class="o">=</span> <span class="mi">35</span><span class="o">;</span> <span class="kd">var</span> <span class="n">height</span> <span class="o">=</span> <span class="mf">1.84</span><span class="o">;</span> </code></pre></div></div> <p>This works because Dart can <strong>infer</strong> the type from the expression on the right side of the assignment.</p> <hr /> <p>We could declare a variable like this:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="n">x</span><span class="o">;</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">15</span><span class="o">;</span> <span class="n">x</span> <span class="o">=</span> <span class="s">'hello'</span><span class="o">;</span> </code></pre></div></div> <p>In this case, <code class="language-plaintext highlighter-rouge">x</code> is <strong>declared first</strong> and <strong>initialized later</strong>.</p> <p>Its type is <code class="language-plaintext highlighter-rouge">dynamic</code>, meaning that it can be assigned from expressions of different types.</p> <p><strong>Takeaway</strong></p> <ul> <li>Dart will infer the correct type when using <code class="language-plaintext highlighter-rouge">var</code>, as long as variables are <strong>declared</strong> and <strong>initialized</strong> at the same time.</li> </ul> <h1 id="2-final-and-const">2. final and const</h1> <p>When a variable is declared with var, it can be assigned again:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="n">name</span> <span class="o">=</span> <span class="s">'Andrea'</span><span class="o">;</span> <span class="n">name</span> <span class="o">=</span> <span class="s">'Bob'</span><span class="o">;</span> </code></pre></div></div> <p>In other words:</p> <blockquote> <p><code class="language-plaintext highlighter-rouge">var</code> means <strong>multiple assignment</strong>.</p> </blockquote> <p>This is not allowed with <code class="language-plaintext highlighter-rouge">final</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="n">name</span> <span class="o">=</span> <span class="s">'Andrea'</span><span class="o">;</span> <span class="n">name</span> <span class="o">=</span> <span class="s">'Bob'</span><span class="o">;</span> <span class="c1">// 'name', a final variable, can only be set once</span> </code></pre></div></div> <h3 id="final-in-practice">Final in practice</h3> <p>It is very common to use <code class="language-plaintext highlighter-rouge">final</code> when declaring properties inside widget classes. Example:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">PlaceholderContent</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="kd">const</span> <span class="n">PlaceholderContent</span><span class="o">({</span> <span class="k">this</span><span class="o">.</span><span class="na">title</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">message</span><span class="o">,</span> <span class="o">});</span> <span class="kd">final</span> <span class="kt">String</span> <span class="n">title</span><span class="o">;</span> <span class="kd">final</span> <span class="kt">String</span> <span class="n">message</span><span class="o">;</span> <span class="c1">// TODO: Implement build method</span> <span class="o">}</span> </code></pre></div></div> <p>Here, the <code class="language-plaintext highlighter-rouge">title</code> and <code class="language-plaintext highlighter-rouge">message</code> can’t be modified inside the widget, because:</p> <blockquote> <p><code class="language-plaintext highlighter-rouge">final</code> means <strong>single assignment</strong>.</p> </blockquote> <p>So, the difference between <code class="language-plaintext highlighter-rouge">var</code> vs <code class="language-plaintext highlighter-rouge">final</code> is about <strong>multiple</strong> vs <strong>single</strong> assignment. Now let’s look at <code class="language-plaintext highlighter-rouge">const</code>:</p> <h3 id="const">const</h3> <blockquote> <p><code class="language-plaintext highlighter-rouge">const</code> defines a <strong>compile-time</strong> constant.</p> </blockquote> <p><code class="language-plaintext highlighter-rouge">const</code> is used to define <strong>hard-coded</strong> values, such as colors, font sizes and icons.</p> <p>But we can also use a <strong>const</strong> constructor when defining widget classes.</p> <p>This is possible, as long as everything inside the widget is also a compile-time constant. Example:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">PlaceholderContent</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="kd">const</span> <span class="n">PlaceholderContent</span><span class="o">({</span> <span class="k">this</span><span class="o">.</span><span class="na">title</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">message</span><span class="o">,</span> <span class="o">});</span> <span class="kd">final</span> <span class="kt">String</span> <span class="n">title</span><span class="o">;</span> <span class="kd">final</span> <span class="kt">String</span> <span class="n">message</span><span class="o">;</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span> <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="o">,</span> <span class="nl">children:</span> <span class="o">&lt;</span><span class="n">Widget</span><span class="o">&gt;[</span> <span class="n">Text</span><span class="o">(</span> <span class="n">title</span><span class="o">,</span> <span class="nl">style:</span> <span class="n">TextStyle</span><span class="o">(</span><span class="nl">fontSize:</span> <span class="mf">32.0</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black54</span><span class="o">),</span> <span class="o">),</span> <span class="n">Text</span><span class="o">(</span> <span class="n">message</span><span class="o">,</span> <span class="nl">style:</span> <span class="n">TextStyle</span><span class="o">(</span><span class="nl">fontSize:</span> <span class="mf">16.0</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black54</span><span class="o">),</span> <span class="o">),</span> <span class="o">],</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>And if a widget has a <code class="language-plaintext highlighter-rouge">const</code> constructor, it can be built like this:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nf">PlaceholderContent</span><span class="p">(</span> <span class="nl">title:</span> <span class="s">'Nothing here'</span><span class="o">,</span> <span class="nl">message:</span> <span class="s">'Add a new item to get started'</span><span class="o">,</span> <span class="o">)</span> </code></pre></div></div> <p>As a result, this widget is optimised by Flutter, as <strong>it is not rebuilt when the parent changes</strong>.</p> <p>Takeaway:</p> <ul> <li><code class="language-plaintext highlighter-rouge">final</code> means <strong>single assignment</strong></li> <li><code class="language-plaintext highlighter-rouge">const</code> defines a <strong>compile-time constant</strong></li> <li><strong>const</strong> widgets <a href="https://stackoverflow.com/questions/53492705/does-using-const-in-the-widget-tree-improve-performance">are not rebuilt when their parent changes</a>.</li> <li>Prefer <code class="language-plaintext highlighter-rouge">const</code> over <code class="language-plaintext highlighter-rouge">final</code> when possible</li> </ul> <h1 id="3-named--positional-parameters">3. Named &amp; positional parameters</h1> <p>In Dart we define named parameters by surrounding them with curly (<code class="language-plaintext highlighter-rouge">{}</code>) braces:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">PlaceholderContent</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="c1">// constructor with named parameters</span> <span class="kd">const</span> <span class="n">PlaceholderContent</span><span class="o">({</span> <span class="k">this</span><span class="o">.</span><span class="na">title</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">message</span><span class="o">,</span> <span class="o">});</span> <span class="kd">final</span> <span class="kt">String</span> <span class="n">title</span><span class="o">;</span> <span class="kd">final</span> <span class="kt">String</span> <span class="n">message</span><span class="o">;</span> <span class="c1">// TODO: Implement build method</span> <span class="o">}</span> </code></pre></div></div> <p>This means that we can create the widget above like this:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PlaceholderContent</span><span class="o">(</span> <span class="nl">title:</span> <span class="s">'Nothing here'</span><span class="o">,</span> <span class="nl">message:</span> <span class="s">'Add a new item to get started'</span><span class="o">,</span> <span class="o">)</span> </code></pre></div></div> <p>As an alternative, we could omit the curly braces in the constructor to declare positional parameters:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// constructor with positional parameters</span> <span class="kd">const</span> <span class="nf">PlaceholderContent</span><span class="p">(</span> <span class="k">this</span><span class="o">.</span><span class="na">title</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">message</span><span class="o">,</span> <span class="o">);</span> </code></pre></div></div> <p>As a result, parameters are defined by their <strong>position</strong>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PlaceholderContent</span><span class="o">(</span> <span class="s">'Nothing here'</span><span class="o">,</span> <span class="c1">// title at position 0</span> <span class="s">'Add a new item to get started'</span><span class="o">,</span> <span class="c1">// message at position 1</span> <span class="o">)</span> </code></pre></div></div> <p>This works, but it quickly gets confusing when we have many parameters.</p> <p>Named parameters help with this, because they make the code easier to write (and read).</p> <hr /> <p>By the way, you can combine positional and named parameters like so:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// positional parameters first, then named parameters</span> <span class="kt">void</span> <span class="nf">_showAlert</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">,</span> <span class="o">{</span><span class="kt">String</span> <span class="n">title</span><span class="o">,</span> <span class="kt">String</span> <span class="n">content</span><span class="o">})</span> <span class="o">{</span> <span class="c1">// TODO: Show Alert</span> <span class="o">}</span> </code></pre></div></div> <p>It is common for Flutter widgets to have a single positional parameter, followed by named parameters. The <code class="language-plaintext highlighter-rouge">Text</code> widget is a good example of this.</p> <p>As a guideline, I always want my code to be clear and self-explanatory. And I choose to use named or positional parameters accordingly.</p> <h1 id="4-required--default-values">4. @required &amp; default values</h1> <p>By default, named parameters can be omitted.</p> <blockquote> <p>Omitting a named parameter is the same as giving it a <code class="language-plaintext highlighter-rouge">null</code> value.</p> </blockquote> <p>And sometimes, this leads to unintended consequences.</p> <p>In the example above, we could define a <code class="language-plaintext highlighter-rouge">PlaceholderContent()</code> and forget to pass in the <code class="language-plaintext highlighter-rouge">title</code> and <code class="language-plaintext highlighter-rouge">message</code>.</p> <p>And this would lead to a red screen with an error, because we would then be passing <code class="language-plaintext highlighter-rouge">null</code> data values to the <code class="language-plaintext highlighter-rouge">Text</code> widgets, which is not allowed.</p> <p><img src="/images/dart-features-better-code-non-null-string.png" alt="" /></p> <hr /> <h3 id="required-to-the-rescue">@required to the rescue</h3> <p>We can annotate any required parameters like so:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nf">PlaceholderContent</span><span class="p">(</span><span class="o">{</span> <span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">title</span><span class="o">,</span> <span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">message</span><span class="o">,</span> <span class="o">});</span> </code></pre></div></div> <p>And the compiler will emit a warning if we forget to pass them in.</p> <p><img src="/images/dart-features-better-code-required-parameters.png" alt="" /></p> <p>Still, if we want we can pass <code class="language-plaintext highlighter-rouge">null</code> values explicitly:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PlaceholderContent</span><span class="o">(</span> <span class="nl">title:</span> <span class="kc">null</span><span class="o">,</span> <span class="nl">message:</span> <span class="kc">null</span><span class="o">,</span> <span class="o">)</span> </code></pre></div></div> <p>And the compiler is happy with this.</p> <p>To prevent passing <code class="language-plaintext highlighter-rouge">null</code> values, we can add some assertions:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nf">PlaceholderContent</span><span class="p">(</span><span class="o">{</span> <span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">title</span><span class="o">,</span> <span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">message</span><span class="o">,</span> <span class="o">})</span> <span class="o">:</span> <span class="k">assert</span><span class="o">(</span><span class="n">title</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">message</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">);</span> </code></pre></div></div> <p>Our code is safer with this change, because:</p> <ul> <li><code class="language-plaintext highlighter-rouge">@required</code> adds a <strong>compile-time</strong> check</li> <li><code class="language-plaintext highlighter-rouge">assert</code> adds a <strong>runtime</strong> check</li> </ul> <p>And if we add asserts to our code, then runtime errors are much easier to fix, because they will point exactly to the line that caused the error.</p> <h3 id="non-nullable-types">non-nullable types</h3> <p><code class="language-plaintext highlighter-rouge">@required</code> and <code class="language-plaintext highlighter-rouge">assert</code> make our code safer, by they feel a bit clunky.</p> <p>It would be nicer if we could specify that an object cannot be null at compile time.</p> <p>This is accomplished with non-nullable types, which were built into Swift and Kotlin from the start.</p> <p>And non-nullable types are currently being implemented in the Dart language.</p> <p>So we can cross fingers and hope they arrive soon. 🤞</p> <h3 id="default-values">Default values</h3> <p>Sometimes it is useful to specify some <strong>sensible</strong> default values.</p> <p>This is easy in Dart:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nf">PlaceholderContent</span><span class="p">(</span><span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">title</span> <span class="o">=</span> <span class="s">'Nothing here'</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">message</span> <span class="o">=</span> <span class="s">'Add a new item to get started'</span><span class="o">,</span> <span class="o">})</span> <span class="o">:</span> <span class="k">assert</span><span class="o">(</span><span class="n">title</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">message</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">);</span> </code></pre></div></div> <p>With this syntax, if the <code class="language-plaintext highlighter-rouge">title</code> and <code class="language-plaintext highlighter-rouge">message</code> are omitted, then the default values are used.</p> <p>By the way, default values can be specified with positional parameters as well:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int sum([int a = 0, int b = 0]) { return a + b; } print(sum(10)); // prints 10 </code></pre></div></div> <h2 id="wrap-up">Wrap up</h2> <p>Code is written for machines to execute, and for other humans to read.</p> <p>Life is short. Be nice and write good code 😉</p> <ul> <li>It makes your apps more robust and performant.</li> <li>It helps teammates and your future self.</li> </ul> <p>Happy coding!</p> <script src="https://assets.convertkit.com/assets/CKJS4.js?v=21"></script> <div class="ck_form_container ck_inline" data-ck-version="7"> <div class="ck_form ck_minimal"> <div class="ck_form_fields"> <h3 class="ck_form_title">LEARN FLUTTER TODAY</h3> <div class="ck_description"> <p><img src="/img/coding-with-flutter-cheat-sheet-panel-noborder.png" style="display:block; margin-left: auto; margin-right: auto;" width="307" /></p> <p>Sign up for updates and get my free Flutter Layout Cheat Sheet.</p> </div> <div id="ck_success_msg" style="display:none;"> <p>Success! Now check your email to confirm your subscription.</p> </div> <!-- Form starts here --> <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/403521/subscribe" data-remote="true"> <input type="hidden" value="{&quot;form_style&quot;:&quot;minimal&quot;,&quot;converted_behavior&quot;:&quot;show&quot;,&quot;days_no_show&quot;:&quot;15&quot;,&quot;delay_seconds&quot;:&quot;10&quot;,&quot;display_devices&quot;:&quot;all&quot;,&quot;display_position&quot;:&quot;br&quot;,&quot;embed_style&quot;:&quot;inline&quot;,&quot;embed_trigger&quot;:&quot;scroll_percentage&quot;,&quot;scroll_percentage&quot;:&quot;70&quot;}" id="ck_form_options" /> <input type="hidden" name="id" value="403521" id="landing_page_id" /> <input type="hidden" name="ck_form_recaptcha" value="" id="ck_form_recaptcha" /> <div class="ck_errorArea"> <div id="ck_error_msg" style="display:none"> <p>There was an error submitting your subscription. Please try again.</p> </div> </div> <div class="ck_control_group ck_email_field_group"> <label class="ck_label" for="ck_emailField" style="display: none">Email Address</label> <input type="email" name="email" class="ck_email_address" id="ck_emailField" placeholder="Email Address" required="" /> </div> <div class="ck_control_group ck_captcha2_h_field_group ck-captcha2-h" style="position: absolute !important;left: -999em !important;"> <input type="text" name="captcha2_h" class="ck-captcha2-h" id="ck_captcha2_h" placeholder="We use this field to detect spam bots. If you fill this in, you will be marked as a spammer." /> </div> <label class="ck_checkbox" style="display:none;"> <input class="optIn ck_course_opted" name="course_opted" type="checkbox" id="optIn" checked="" /> <span class="ck_opt_in_prompt">I'd like to receive the free email course.</span> </label> <button class="subscribe_button ck_subscribe_button btn fields" id="ck_subscribe_button"> Subscribe </button> <span class="ck_guarantee"> No Spam. Ever. Unsubscribe at any time. </span> </form> </div> </div> </div> <style type="text/css">/* Layout */ .ck_form.ck_minimal { /* divider image */ background: #f9f9f9; font-family: 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; line-height: 1.5em; overflow: hidden; color: #202020; font-size: 16px; border: solid 1px #d1d1d1; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; clear: both; margin: 20px 0px; text-align: center; } .ck_form.ck_minimal h3.ck_form_title { text-align: center; margin: 0px 0px 10px; font-size: 28px; } .ck_form.ck_minimal h4 { text-align: center; font-family: 'Open Sans', Helvetica, Arial, sans-serif; text-transform: uppercase; font-size: 18px; font-weight: normal; padding-top: 0px; margin-top: 0px; } .ck_form.ck_minimal p { padding: 0px; } .ck_form, .ck_form * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .ck_form.ck_minimal .ck_form_fields { width: 100%; float: left; padding: 5%; } /* Form fields */ .ck_errorArea { display: none; /* temporary */ } #ck_success_msg { padding: 10px 10px 0px; border: solid 1px #ddd; background: #eee; } .ck_form.ck_minimal input[type="text"], .ck_form.ck_minimal input[type="email"] { font-size: 18px; padding: 10px 8px; width: 68%; border: 1px solid #d6d6d6; /* stroke */ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ background-color: #fff; /* layer fill content */ margin-bottom: 5px; height: auto; float: left; margin: 0px; margin-right: 2%; height: 42px; } .ck_form input[type="text"]:focus, .ck_form input[type="email"]:focus { outline: none; border-color: #aaa; } .ck_form.ck_minimal .ck_subscribe_button { width: 100%; color: #fff; margin: 0px; padding: 11px 0px; font-size: 18px; background: #0d6db8; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ cursor: pointer; border: none; text-shadow: none; width: 30%; float: left; height: 42px; } .ck_form.ck_minimal .ck_guarantee { color: #626262; font-size: 12px; text-align: center; padding: 15px 0px 0px; display: block; clear: both; } .ck_form .ck_powered_by { display: block; color: #aaa; font-size: 12px; } .ck_form .ck_powered_by:hover { display: block; color: #444; } .ck_converted_content { display: none; padding: 5%; background: #fff; } .ck_form.ck_minimal.width400 .ck_subscribe_button, .ck_form.ck_minimal.width400 input[type="email"] { width: 100%; float: none; margin-top: 5px; } .ck_slide_up, .ck_modal, .ck_slide_up .ck_minimal, .ck_modal .ck_minimal { min-width: 400px; } .page .ck_form.ck_minimal { margin: 50px auto; max-width: 600px; } /* v6 */ .ck_slide_up.ck_form_v6, .ck_modal.ck_form_v6, .ck_slide_up.ck_form_v6 .ck_minimal, .ck_modal.ck_form_v6 .ck_minimal { min-width: 0 !important; } @media all and (min-width: 801px) { .ck_modal.ck_form_v6 .ck_form.ck_minimal { margin-left: -300px; width: 600px; } } .ck_modal.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 20px; } .ck_slide_up.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 10px; } .ck_form_v6 #ck_success_msg { margin-top: 15px; padding: 0px 10px; } .ck_slide_up.ck_form_v6 .ck_minimal + .ck_close_link { top: 5px; } .ck_slide_up.ck_form_v6 .ck_minimal h3.ck_form_title { margin-top: 5px; } </style> Mon, 22 Jul 2019 03:00:18 +0000 http://bizz84.github.io/2019/07/22/dart-features-for-better-code-part1.html http://bizz84.github.io/2019/07/22/dart-features-for-better-code-part1.html Flutter, Android, iOS, Mobile App Development The Future of SwiftyStoreKit: Maintainers Needed <p><img src="/images/future-of-swiftystorekit-banner.png" alt="" /></p> <p>TL;DR:</p> <ul> <li><a href="https://github.com/bizz84/SwiftyStoreKit">SwiftyStoreKit</a> needs maintainers.</li> <li>The project has now a dedicated <a href="https://swiftystorekit.slack.com">Slack channel</a>. <a href="https://join.slack.com/t/swiftystorekit/shared_invite/enQtNjkzNTg5NTMyMTgwLTcyZGIzMTg0MWFmMTQyMDYxNDcyYWNhOTlmNjUyM2E0OTllNjE2ZDJiNDI0ZDAzMWU2Mzc3Nzk1YzJmMTE2NjI">Join here</a>.</li> </ul> <hr /> <p>Back in 2015, I created <a href="https://github.com/bizz84/SwiftyStoreKit">SwiftyStoreKit</a> to make in-app-purchases easier on Apple platforms.</p> <p>The project has been very popular, with thousands of developers using it in their own apps.</p> <p><img src="/images/future-of-swiftystorekit-4K.png" alt="" /></p> <p>And for a long time, I have managed to keep it going as a sole maintainer.</p> <p>However, I can no longer keep the project growing and deal with all the GitHub issues.</p> <p>Instead, I propose that <a href="https://github.com/bizz84/SwiftyStoreKit">SwiftyStoreKit</a> should be made for the community, <strong>by the community</strong>.</p> <h2 id="swiftystorekit-on-slack">SwiftyStoreKit on Slack</h2> <p>To that effect, I created a <a href="https://join.slack.com/t/swiftystorekit/shared_invite/enQtNjkzNTg5NTMyMTgwLTcyZGIzMTg0MWFmMTQyMDYxNDcyYWNhOTlmNjUyM2E0OTllNjE2ZDJiNDI0ZDAzMWU2Mzc3Nzk1YzJmMTE2NjI">public Slack channel</a> with three primary purposes:</p> <ol> <li>Finding new maintainers for the project</li> <li>Discuss new feature requests and how to best implement them</li> <li>General Q/A about the project</li> </ol> <p><strong>Finding new maintainers is the most pressing issue</strong>, as my involvement is very limited (and has been since the beginning of 2018).</p> <h2 id="maintainers-guide-to-swiftystorekit">Maintainer’s guide to SwiftyStoreKit</h2> <p>Implementing bug fixes and new features in SwiftyStoreKit is not easy:</p> <ul> <li>The project <strong>is used in production by thousands of apps</strong>, across iOS, macOS and tvOS.</li> <li>In-app purchases only work if configured correctly in App Store Connect.</li> <li><a href="https://developer.apple.com/documentation/storekit">StoreKit</a> itself is a very old framework <a href="https://stackoverflow.com/search?q=storekit">that has its own problems</a>.</li> </ul> <p>New code should be tested on all platforms and have adequate test coverage.</p> <p>But that’s not enough.</p> <p>Testing all the possible end-to-end configurations (iOS, macOS, tvOS in sandbox/production) is hard, before even considering iTunes downtime and possible account/IAP configuration issues.</p> <p>And this makes the job of the maintainer particularly hard.</p> <p>To find the best way forward, I added a <a href="https://app.slack.com/client/TL2JYQ458/CLG62K26A/details/">#maintainers</a> Slack channel. Use this to register your interest as a maintainer. 😉</p> <h2 id="wrap-up">Wrap Up</h2> <p><a href="https://github.com/bizz84/SwiftyStoreKit">SwiftyStoreKit</a> is a very popular project, and has served developers well over the years.</p> <p>I’d like for it to continue doing so, even as my involvement winds down.</p> <p>If you’d like to contribute, <a href="https://join.slack.com/t/swiftystorekit/shared_invite/enQtNjkzNTg5NTMyMTgwLTcyZGIzMTg0MWFmMTQyMDYxNDcyYWNhOTlmNjUyM2E0OTllNjE2ZDJiNDI0ZDAzMWU2Mzc3Nzk1YzJmMTE2NjI">join the Slack channel</a>.</p> <p>NOTE: I will not be moderating this workspace, so it’s up to you all to be nice with each other.</p> <p>PS. I’m considering to move <a href="https://github.com/bizz84/SwiftyStoreKit">SwiftyStoreKit</a> to its own organization on GitHub. Aside from this <a href="https://help.github.com/en/articles/transferring-a-repository">GitHub guide</a>, is there anything I should be aware of (installation issues etc.)?</p> <p>Happy coding!</p> Sun, 14 Jul 2019 06:00:18 +0000 http://bizz84.github.io/2019/07/14/future-of-swiftystorekit.html http://bizz84.github.io/2019/07/14/future-of-swiftystorekit.html Swift, StoreKit, ios, open source, Xcode, Cocoapods Flutter State Management: setState, BLoC, ValueNotifier, Provider <iframe width="560" height="315" src="https://www.youtube.com/embed/7eaV9gSnaXw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe> <p>This article is a write-up of the highlights in this video 👆, where we compare different state management techniques.</p> <hr /> <p>As an example, we use a simple authentication flow. This sets a loading state while a sign-in request is in progress.</p> <p>For simplicity, this flow is composed of three possible states:</p> <p><img src="/images/state-management-comparison-screens.png" alt="" /></p> <p>These are represented by the following state machine, which includes a <strong>loading</strong> state and an <strong>authentication</strong> state:</p> <p><img src="/images/state-management-comparison-state-machine.png" alt="" /></p> <p>When a sign-in request is in progress, we disable the sign-in button and show a progress indicator.</p> <p>This example app shows how to handle the loading state with various state management techniques.</p> <h2 id="main-navigation">Main Navigation</h2> <p>The main navigation for the sign-in page is implemented with a widget that uses a <a href="https://api.flutter.dev/flutter/material/Drawer-class.html">Drawer</a> menu to choose between different options:</p> <p><img src="/images/state-management-comparison-switcher.png" alt="" /></p> <p>The code for this is as follows:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInPageNavigation</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="kd">const</span> <span class="n">SignInPageNavigation</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">option</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span> <span class="kd">final</span> <span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="n">Option</span><span class="o">&gt;</span> <span class="n">option</span><span class="o">;</span> <span class="n">Option</span> <span class="kd">get</span> <span class="n">_option</span> <span class="o">=&gt;</span> <span class="n">option</span><span class="o">.</span><span class="na">value</span><span class="o">;</span> <span class="n">OptionData</span> <span class="kd">get</span> <span class="n">_optionData</span> <span class="o">=&gt;</span> <span class="n">optionsData</span><span class="o">[</span><span class="n">_option</span><span class="o">];</span> <span class="kt">void</span> <span class="n">_onSelectOption</span><span class="o">(</span><span class="n">Option</span> <span class="n">selectedOption</span><span class="o">)</span> <span class="o">{</span> <span class="n">option</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">selectedOption</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span> <span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="n">_optionData</span><span class="o">.</span><span class="na">title</span><span class="o">),</span> <span class="o">),</span> <span class="nl">drawer:</span> <span class="n">MenuSwitcher</span><span class="o">(</span> <span class="nl">options:</span> <span class="n">optionsData</span><span class="o">,</span> <span class="nl">selectedOption:</span> <span class="n">_option</span><span class="o">,</span> <span class="nl">onSelected:</span> <span class="n">_onSelectOption</span><span class="o">,</span> <span class="o">),</span> <span class="nl">body:</span> <span class="n">_buildContent</span><span class="o">(</span><span class="n">context</span><span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="n">Widget</span> <span class="n">_buildContent</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">switch</span> <span class="o">(</span><span class="n">_option</span><span class="o">)</span> <span class="o">{</span> <span class="k">case</span> <span class="n">Option</span><span class="o">.</span><span class="na">vanilla</span><span class="o">:</span> <span class="k">return</span> <span class="n">SignInPageVanilla</span><span class="o">();</span> <span class="k">case</span> <span class="n">Option</span><span class="o">.</span><span class="na">setState</span><span class="o">:</span> <span class="k">return</span> <span class="n">SignInPageSetState</span><span class="o">();</span> <span class="k">case</span> <span class="n">Option</span><span class="o">.</span><span class="na">bloc</span><span class="o">:</span> <span class="k">return</span> <span class="n">SignInPageBloc</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">context</span><span class="o">);</span> <span class="k">case</span> <span class="n">Option</span><span class="o">.</span><span class="na">valueNotifier</span><span class="o">:</span> <span class="k">return</span> <span class="n">SignInPageValueNotifier</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">context</span><span class="o">);</span> <span class="k">default</span><span class="o">:</span> <span class="k">return</span> <span class="n">Container</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>This widget shows a <code class="language-plaintext highlighter-rouge">Scaffold</code> where:</p> <ul> <li>the <code class="language-plaintext highlighter-rouge">AppBar</code>’s title is the name of the selected option</li> <li>the drawer uses a custom built <code class="language-plaintext highlighter-rouge">MenuSwitcher</code></li> <li>the body uses a switch to choose between different pages</li> </ul> <h2 id="reference-flow-vanilla">Reference flow (vanilla)</h2> <p>To enable sign-in, we can start with a simple vanilla implementation that doesn’t have a loading state:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInPageVanilla</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">auth</span> <span class="o">=</span> <span class="n">Provider</span><span class="o">.</span><span class="na">of</span><span class="o">&lt;</span><span class="n">AuthService</span><span class="o">&gt;(</span><span class="n">context</span><span class="o">);</span> <span class="n">await</span> <span class="n">auth</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="n">on</span> <span class="n">PlatformException</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">await</span> <span class="n">PlatformExceptionAlertDialog</span><span class="o">(</span> <span class="nl">title:</span> <span class="s">'Sign in failed'</span><span class="o">,</span> <span class="nl">exception:</span> <span class="n">e</span><span class="o">,</span> <span class="o">).</span><span class="na">show</span><span class="o">(</span><span class="n">context</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">SignInButton</span><span class="o">(</span> <span class="nl">text:</span> <span class="s">'Sign in'</span><span class="o">,</span> <span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">=&gt;</span> <span class="n">_signInAnonymously</span><span class="o">(</span><span class="n">context</span><span class="o">),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/e3866c7c268d5dc9b36d51712d1f1811 --> <p>When the <code class="language-plaintext highlighter-rouge">SignInButton</code> is pressed, we call the <code class="language-plaintext highlighter-rouge">_signInAnonymously</code> method.</p> <p>This uses <a href="https://pub.dev/packages/provider">Provider</a> to get an <code class="language-plaintext highlighter-rouge">AuthService</code> object, and uses it to sign-in.</p> <p><strong>NOTES</strong></p> <ul> <li><code class="language-plaintext highlighter-rouge">AuthService</code> is a simple wrapper for Firebase Authentication. See <a href="https://medium.com/coding-with-flutter/flutter-designing-an-authentication-api-with-service-classes-45ec8d55963e">this article</a> for more details.</li> <li>The authentication state is handled by an ancestor widget, that uses the <code class="language-plaintext highlighter-rouge">onAuthStateChanged</code> stream to decide which page to show. I covered this <a href="https://medium.com/coding-with-flutter/super-simple-authentication-flow-with-flutter-firebase-737bba04924c">in a previous article</a>.</li> </ul> <h2 id="setstate">setState</h2> <p>The loading state can be added to the previous implementation by:</p> <ul> <li>Converting our widget to a <code class="language-plaintext highlighter-rouge">StatefulWidget</code></li> <li>Declaring a local state variable</li> <li>Using it inside our build method</li> <li>Updating it before and after the call to sign in.</li> </ul> <p>This is the resulting code:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInPageSetState</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">_SignInPageSetStateState</span> <span class="n">createState</span><span class="o">()</span> <span class="o">=&gt;</span> <span class="n">_SignInPageSetStateState</span><span class="o">();</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">_SignInPageSetStateState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o">&lt;</span><span class="n">SignInPageSetState</span><span class="o">&gt;</span> <span class="o">{</span> <span class="kt">bool</span> <span class="n">_isLoading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">setState</span><span class="o">(()</span> <span class="o">=&gt;</span> <span class="n">_isLoading</span> <span class="o">=</span> <span class="kc">true</span><span class="o">);</span> <span class="kd">final</span> <span class="n">auth</span> <span class="o">=</span> <span class="n">Provider</span><span class="o">.</span><span class="na">of</span><span class="o">&lt;</span><span class="n">AuthService</span><span class="o">&gt;(</span><span class="n">context</span><span class="o">);</span> <span class="n">await</span> <span class="n">auth</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="n">on</span> <span class="n">PlatformException</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">await</span> <span class="n">PlatformExceptionAlertDialog</span><span class="o">(</span> <span class="nl">title:</span> <span class="s">'Sign in failed'</span><span class="o">,</span> <span class="nl">exception:</span> <span class="n">e</span><span class="o">,</span> <span class="o">).</span><span class="na">show</span><span class="o">(</span><span class="n">context</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">setState</span><span class="o">(()</span> <span class="o">=&gt;</span> <span class="n">_isLoading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">SignInButton</span><span class="o">(</span> <span class="nl">text:</span> <span class="s">'Sign in'</span><span class="o">,</span> <span class="nl">loading:</span> <span class="n">_isLoading</span><span class="o">,</span> <span class="nl">onPressed:</span> <span class="n">_isLoading</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="o">()</span> <span class="o">=&gt;</span> <span class="n">_signInAnonymously</span><span class="o">(),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/05db3aa286243f5cc5b5158d93edbce9 --> <p><strong>Top Tip</strong>: Note how we use a <a href="https://dart.dev/guides/language/language-tour#finally"><code class="language-plaintext highlighter-rouge">finally</code></a> clause. This can be used to execute some code, whether or not an exception was thrown.</p> <h2 id="bloc">BLoC</h2> <p>The loading state can be represented by the values of a stream inside a BLoC.</p> <p>And we need some extra boilerplate code to set things up:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInBloc</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">_loadingController</span> <span class="o">=</span> <span class="n">StreamController</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;();</span> <span class="n">Stream</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="kd">get</span> <span class="n">loadingStream</span> <span class="o">=&gt;</span> <span class="n">_loadingController</span><span class="o">.</span><span class="na">stream</span><span class="o">;</span> <span class="kt">void</span> <span class="n">setIsLoading</span><span class="o">(</span><span class="kt">bool</span> <span class="n">loading</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">_loadingController</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">loading</span><span class="o">);</span> <span class="n">dispose</span><span class="o">()</span> <span class="o">{</span> <span class="n">_loadingController</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">SignInPageBloc</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="kd">const</span> <span class="n">SignInPageBloc</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">,</span> <span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">bloc</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span> <span class="kd">final</span> <span class="n">SignInBloc</span> <span class="n">bloc</span><span class="o">;</span> <span class="kd">static</span> <span class="n">Widget</span> <span class="n">create</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Provider</span><span class="o">&lt;</span><span class="n">SignInBloc</span><span class="o">&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">SignInBloc</span><span class="o">(),</span> <span class="nl">dispose:</span> <span class="o">(</span><span class="n">_</span><span class="o">,</span> <span class="n">bloc</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">bloc</span><span class="o">.</span><span class="na">dispose</span><span class="o">(),</span> <span class="nl">child:</span> <span class="n">Consumer</span><span class="o">&lt;</span><span class="n">SignInBloc</span><span class="o">&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">,</span> <span class="n">bloc</span><span class="o">,</span> <span class="n">__</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">SignInPageBloc</span><span class="o">(</span><span class="nl">bloc:</span> <span class="n">bloc</span><span class="o">),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">bloc</span><span class="o">.</span><span class="na">setIsLoading</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> <span class="kd">final</span> <span class="n">auth</span> <span class="o">=</span> <span class="n">Provider</span><span class="o">.</span><span class="na">of</span><span class="o">&lt;</span><span class="n">AuthService</span><span class="o">&gt;(</span><span class="n">context</span><span class="o">);</span> <span class="n">await</span> <span class="n">auth</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="n">on</span> <span class="n">PlatformException</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">await</span> <span class="n">PlatformExceptionAlertDialog</span><span class="o">(</span> <span class="nl">title:</span> <span class="s">'Sign in failed'</span><span class="o">,</span> <span class="nl">exception:</span> <span class="n">e</span><span class="o">,</span> <span class="o">).</span><span class="na">show</span><span class="o">(</span><span class="n">context</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">bloc</span><span class="o">.</span><span class="na">setIsLoading</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">StreamBuilder</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;(</span> <span class="nl">stream:</span> <span class="n">bloc</span><span class="o">.</span><span class="na">loadingStream</span><span class="o">,</span> <span class="nl">initialData:</span> <span class="kc">false</span><span class="o">,</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">snapshot</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">isLoading</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">;</span> <span class="k">return</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">SignInButton</span><span class="o">(</span> <span class="nl">text:</span> <span class="s">'Sign in'</span><span class="o">,</span> <span class="nl">loading:</span> <span class="n">isLoading</span><span class="o">,</span> <span class="nl">onPressed:</span> <span class="n">isLoading</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="o">()</span> <span class="o">=&gt;</span> <span class="n">_signInAnonymously</span><span class="o">(</span><span class="n">context</span><span class="o">),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">},</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/af83aa37df59ab97f4ec8309d3e492e6 --> <p>In a nutshell, this code:</p> <ul> <li>Adds a <code class="language-plaintext highlighter-rouge">SignInBloc</code> with a <code class="language-plaintext highlighter-rouge">StreamController&lt;bool&gt;</code> that is used to handle the loading state</li> <li>Makes the <code class="language-plaintext highlighter-rouge">SignInBloc</code> accessible to our widget with a Provider/Consumer pair inside a <code class="language-plaintext highlighter-rouge">static create</code> method.</li> <li>Calls <code class="language-plaintext highlighter-rouge">bloc.setIsLoading(value)</code> to update the stream, inside the <code class="language-plaintext highlighter-rouge">_signInAnonymously</code> method</li> <li>Retrieves the loading state via a <code class="language-plaintext highlighter-rouge">StreamBuilder</code>, and uses it to configure the sign-in button.</li> </ul> <h2 id="note-about-rxdart">Note about RxDart</h2> <p><code class="language-plaintext highlighter-rouge">BehaviourSubject</code> is special stream controller that gives us <strong>synchronous</strong> access to the last value of the stream.</p> <p>As an alternative to BloC, we could use a <code class="language-plaintext highlighter-rouge">BehaviourSubject</code> to keep track of the loading state, and update it as needed.</p> <p>I will update the <a href="https://github.com/bizz84/simple_auth_comparison_flutter">GitHub project</a> to show how to do this.</p> <h2 id="valuenotifier">ValueNotifier</h2> <p>A <a href="https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html"><code class="language-plaintext highlighter-rouge">ValueNotifier</code></a> can be used to hold a single value, and notify its listeners when this changes.</p> <p>This is used to implement the same flow:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInPageValueNotifier</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="kd">const</span> <span class="n">SignInPageValueNotifier</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">loading</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span> <span class="kd">final</span> <span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="n">loading</span><span class="o">;</span> <span class="kd">static</span> <span class="n">Widget</span> <span class="n">create</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">ChangeNotifierProvider</span><span class="o">&lt;</span><span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;(</span><span class="kc">false</span><span class="o">),</span> <span class="nl">child:</span> <span class="n">Consumer</span><span class="o">&lt;</span><span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">,</span> <span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="n">isLoading</span><span class="o">,</span> <span class="n">__</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">SignInPageValueNotifier</span><span class="o">(</span> <span class="nl">loading:</span> <span class="n">isLoading</span><span class="o">,</span> <span class="o">),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">loading</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="kd">final</span> <span class="n">auth</span> <span class="o">=</span> <span class="n">Provider</span><span class="o">.</span><span class="na">of</span><span class="o">&lt;</span><span class="n">AuthService</span><span class="o">&gt;(</span><span class="n">context</span><span class="o">);</span> <span class="n">await</span> <span class="n">auth</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="n">on</span> <span class="n">PlatformException</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">await</span> <span class="n">PlatformExceptionAlertDialog</span><span class="o">(</span> <span class="nl">title:</span> <span class="s">'Sign in failed'</span><span class="o">,</span> <span class="nl">exception:</span> <span class="n">e</span><span class="o">,</span> <span class="o">).</span><span class="na">show</span><span class="o">(</span><span class="n">context</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">loading</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">SignInButton</span><span class="o">(</span> <span class="nl">text:</span> <span class="s">'Sign in'</span><span class="o">,</span> <span class="nl">loading:</span> <span class="n">loading</span><span class="o">.</span><span class="na">value</span><span class="o">,</span> <span class="nl">onPressed:</span> <span class="n">loading</span><span class="o">.</span><span class="na">value</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="o">()</span> <span class="o">=&gt;</span> <span class="n">_signInAnonymously</span><span class="o">(</span><span class="n">context</span><span class="o">),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/2c87167cd25ea46e3a191119629c50f4 --> <p>Inside the <code class="language-plaintext highlighter-rouge">static create</code> method, we use a <a href="https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider-class.html"><code class="language-plaintext highlighter-rouge">ChangeNotifierProvider</code></a>/<a href="https://pub.dev/documentation/provider/latest/provider/Consumer-class.html"><code class="language-plaintext highlighter-rouge">Consumer</code></a> with a <code class="language-plaintext highlighter-rouge">ValueNotifier&lt;bool&gt;</code>. This gives us a way to represent the loading state, and rebuild the widget when it changes.</p> <h2 id="valuenotifier-vs-changenotifier">ValueNotifier vs ChangeNotifier</h2> <p><a href="https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html"><code class="language-plaintext highlighter-rouge">ValueNotifier</code></a> and <a href="https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html"><code class="language-plaintext highlighter-rouge">ChangeNotifier</code></a> are closely related.</p> <p>In fact, <code class="language-plaintext highlighter-rouge">ValueNotifier</code> is a subclass of <code class="language-plaintext highlighter-rouge">ChangeNotifier</code> that implements <code class="language-plaintext highlighter-rouge">ValueListenable&lt;T&gt;</code>.</p> <p>This is the implementation of <code class="language-plaintext highlighter-rouge">ValueNotifier</code> in the Flutter SDK:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// A [ChangeNotifier] that holds a single value.</span> <span class="c1">///</span> <span class="c1">/// When [value] is replaced with something that is not equal to the old</span> <span class="c1">/// value as evaluated by the equality operator ==, this class notifies its</span> <span class="c1">/// listeners.</span> <span class="kd">class</span> <span class="nc">ValueNotifier</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="kd">extends</span> <span class="n">ChangeNotifier</span> <span class="kd">implements</span> <span class="n">ValueListenable</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="o">{</span> <span class="c1">/// Creates a [ChangeNotifier] that wraps this value.</span> <span class="n">ValueNotifier</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">_value</span><span class="o">);</span> <span class="c1">/// The current value stored in this notifier.</span> <span class="c1">///</span> <span class="c1">/// When the value is replaced with something that is not equal to the old</span> <span class="c1">/// value as evaluated by the equality operator ==, this class notifies its</span> <span class="c1">/// listeners.</span> <span class="nd">@override</span> <span class="n">T</span> <span class="kd">get</span> <span class="n">value</span> <span class="o">=&gt;</span> <span class="n">_value</span><span class="o">;</span> <span class="n">T</span> <span class="n">_value</span><span class="o">;</span> <span class="kd">set</span> <span class="n">value</span><span class="o">(</span><span class="n">T</span> <span class="n">newValue</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">_value</span> <span class="o">==</span> <span class="n">newValue</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span> <span class="n">_value</span> <span class="o">=</span> <span class="n">newValue</span><span class="o">;</span> <span class="n">notifyListeners</span><span class="o">();</span> <span class="o">}</span> <span class="nd">@override</span> <span class="kt">String</span> <span class="n">toString</span><span class="o">()</span> <span class="o">=&gt;</span> <span class="s">'</span><span class="si">${describeIdentity(this)}</span><span class="s">(</span><span class="si">$value</span><span class="s">)'</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/ab1b56367a24ab300f16bd47486b356e --> <p>So, when should we use <code class="language-plaintext highlighter-rouge">ValueNotifier</code> vs <code class="language-plaintext highlighter-rouge">ChangeNotifier</code>?</p> <ul> <li>Use <code class="language-plaintext highlighter-rouge">ValueNotifier</code> if you need widgets to rebuild when a simple value changes.</li> <li>Use <code class="language-plaintext highlighter-rouge">ChangeNotifier</code> if you want more control on when <code class="language-plaintext highlighter-rouge">notifyListeners()</code> is called.</li> </ul> <h2 id="note-about-scopedmodel">Note about ScopedModel</h2> <p><code class="language-plaintext highlighter-rouge">ChangeNotifierProvider</code> is very similar to <a href="https://pub.dev/packages/scoped_model">ScopedModel</a>. In fact these pairs are almost equivalent:</p> <ul> <li><code class="language-plaintext highlighter-rouge">ScopedModel</code> ↔︎ <code class="language-plaintext highlighter-rouge">ChangeNotifierProvider</code></li> <li><code class="language-plaintext highlighter-rouge">ScopedModelDescendant</code> ↔︎ <code class="language-plaintext highlighter-rouge">Consumer</code></li> </ul> <p>So you don’t need ScopedModel if you are already using Provider, as <code class="language-plaintext highlighter-rouge">ChangeNotifierProvider</code> offers the same functionality.</p> <h2 id="final-comparison">Final comparison</h2> <p>The three implementations (setState, BLoC, ValueNotifier) are very similar, and only differ in how the loading state is handled.</p> <p>Here is how they compare:</p> <ul> <li>setState ↔︎ <strong>least</strong> amount of code</li> <li>BLoC ↔︎ <strong>most</strong> amount of code</li> <li>ValueNotifier ↔︎ <strong>middle ground</strong></li> </ul> <p>So <code class="language-plaintext highlighter-rouge">setState</code> works best <strong>for this use case</strong>, as we need to handle state that is <strong>local to a single widget</strong>.</p> <p>You can evaluate which one is more suitable on a case-by-case basis, as you build your own apps 😉</p> <h2 id="bonus-implementing-the-drawer-menu">Bonus: Implementing the Drawer Menu</h2> <p>Keeping track of the currently selected option is also a state management problem:</p> <p><img src="/images/state-management-comparison-switcher.png" alt="" /></p> <p>I first implemented this with a local state variable and <code class="language-plaintext highlighter-rouge">setState</code>, inside the custom drawer menu.</p> <p>However, the state was lost after sign-in in, because the drawer was removed from the widget tree.</p> <p>As a solution, I decided to store the state with a <code class="language-plaintext highlighter-rouge">ChangeNotifierProvider&lt;ValueNotifier&lt;Option&gt;&gt;</code> inside the <code class="language-plaintext highlighter-rouge">LandingPage</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">LandingPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Used to keep track of the selected option across sign-in events</span> <span class="kd">final</span> <span class="n">authService</span> <span class="o">=</span> <span class="n">Provider</span><span class="o">.</span><span class="na">of</span><span class="o">&lt;</span><span class="n">AuthService</span><span class="o">&gt;(</span><span class="n">context</span><span class="o">);</span> <span class="k">return</span> <span class="n">ChangeNotifierProvider</span><span class="o">&lt;</span><span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="n">Option</span><span class="o">&gt;&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="n">Option</span><span class="o">&gt;(</span><span class="n">Option</span><span class="o">.</span><span class="na">vanilla</span><span class="o">),</span> <span class="nl">child:</span> <span class="n">StreamBuilder</span><span class="o">&lt;</span><span class="n">User</span><span class="o">&gt;(</span> <span class="nl">stream:</span> <span class="n">authService</span><span class="o">.</span><span class="na">onAuthStateChanged</span><span class="o">,</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">snapshot</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">snapshot</span><span class="o">.</span><span class="na">connectionState</span> <span class="o">==</span> <span class="n">ConnectionState</span><span class="o">.</span><span class="na">active</span><span class="o">)</span> <span class="o">{</span> <span class="n">User</span> <span class="n">user</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">user</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Consumer</span><span class="o">&lt;</span><span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="n">Option</span><span class="o">&gt;&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">,</span> <span class="n">ValueNotifier</span><span class="o">&lt;</span><span class="n">Option</span><span class="o">&gt;</span> <span class="n">option</span><span class="o">,</span> <span class="n">__</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">SignInPageNavigation</span><span class="o">(</span><span class="nl">option:</span> <span class="n">option</span><span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="n">HomePage</span><span class="o">();</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">body:</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">(),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">},</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/997b77964cdbf086a5ea67dac1de1591 --> <p>Here, the <a href="https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html"><code class="language-plaintext highlighter-rouge">StreamBuilder</code></a> controls the authentication state of the user.</p> <p>And by wrapping this with a <code class="language-plaintext highlighter-rouge">ChangeNotifierProvider&lt;ValueNotifier&lt;Option&gt;&gt;</code>, I’m able to retain the selected option even after the <code class="language-plaintext highlighter-rouge">SignInPageNavigation</code> is removed.</p> <p>In summary:</p> <ul> <li>StatefulWidgets don’t <strong>remember</strong> their state <strong>after</strong> they are removed.</li> <li>With Provider, we can choose <strong>where</strong> to store state in the widget tree.</li> <li>This way, the state is <strong>retained</strong> even when the widgets that use it are removed.</li> </ul> <p><code class="language-plaintext highlighter-rouge">ValueNotifier</code> requires a bit more code than <code class="language-plaintext highlighter-rouge">setState</code>. But it can be used to <strong>remember</strong> the state, by placing a Provider where appropriate in the widget tree.</p> <h2 id="source-code">Source code</h2> <p>The example code from this tutorial can be found here:</p> <ul> <li><a href="https://github.com/bizz84/simple_auth_comparison_flutter">State Management Comparison: [ setState ❖ BLoC ❖ ValueNotifier ❖ Provider ]</a></li> </ul> <p>All these state management techniques are covered in-depth in my Flutter &amp; Firebase Udemy course. This is available for early access at this link (discount code included):</p> <ul> <li><a href="https://www.udemy.com/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=DART15&amp;password=codingwithflutter">Flutter &amp; Firebase: Build a Complete App for iOS &amp; Android</a></li> </ul> <p>Happy coding!</p> <script src="https://assets.convertkit.com/assets/CKJS4.js?v=21"></script> <div class="ck_form_container ck_inline" data-ck-version="7"> <div class="ck_form ck_minimal"> <div class="ck_form_fields"> <h3 class="ck_form_title">LEARN FLUTTER TODAY</h3> <div class="ck_description"> <p><img src="/img/coding-with-flutter-cheat-sheet-panel-noborder.png" style="display:block; margin-left: auto; margin-right: auto;" width="307" /></p> <p>Sign up for updates and get my free Flutter Layout Cheat Sheet.</p> </div> <div id="ck_success_msg" style="display:none;"> <p>Success! Now check your email to confirm your subscription.</p> </div> <!-- Form starts here --> <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/403521/subscribe" data-remote="true"> <input type="hidden" value="{&quot;form_style&quot;:&quot;minimal&quot;,&quot;converted_behavior&quot;:&quot;show&quot;,&quot;days_no_show&quot;:&quot;15&quot;,&quot;delay_seconds&quot;:&quot;10&quot;,&quot;display_devices&quot;:&quot;all&quot;,&quot;display_position&quot;:&quot;br&quot;,&quot;embed_style&quot;:&quot;inline&quot;,&quot;embed_trigger&quot;:&quot;scroll_percentage&quot;,&quot;scroll_percentage&quot;:&quot;70&quot;}" id="ck_form_options" /> <input type="hidden" name="id" value="403521" id="landing_page_id" /> <input type="hidden" name="ck_form_recaptcha" value="" id="ck_form_recaptcha" /> <div class="ck_errorArea"> <div id="ck_error_msg" style="display:none"> <p>There was an error submitting your subscription. Please try again.</p> </div> </div> <div class="ck_control_group ck_email_field_group"> <label class="ck_label" for="ck_emailField" style="display: none">Email Address</label> <input type="email" name="email" class="ck_email_address" id="ck_emailField" placeholder="Email Address" required="" /> </div> <div class="ck_control_group ck_captcha2_h_field_group ck-captcha2-h" style="position: absolute !important;left: -999em !important;"> <input type="text" name="captcha2_h" class="ck-captcha2-h" id="ck_captcha2_h" placeholder="We use this field to detect spam bots. If you fill this in, you will be marked as a spammer." /> </div> <label class="ck_checkbox" style="display:none;"> <input class="optIn ck_course_opted" name="course_opted" type="checkbox" id="optIn" checked="" /> <span class="ck_opt_in_prompt">I'd like to receive the free email course.</span> </label> <button class="subscribe_button ck_subscribe_button btn fields" id="ck_subscribe_button"> Subscribe </button> <span class="ck_guarantee"> No Spam. Ever. Unsubscribe at any time. </span> </form> </div> </div> </div> <style type="text/css">/* Layout */ .ck_form.ck_minimal { /* divider image */ background: #f9f9f9; font-family: 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; line-height: 1.5em; overflow: hidden; color: #202020; font-size: 16px; border: solid 1px #d1d1d1; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; clear: both; margin: 20px 0px; text-align: center; } .ck_form.ck_minimal h3.ck_form_title { text-align: center; margin: 0px 0px 10px; font-size: 28px; } .ck_form.ck_minimal h4 { text-align: center; font-family: 'Open Sans', Helvetica, Arial, sans-serif; text-transform: uppercase; font-size: 18px; font-weight: normal; padding-top: 0px; margin-top: 0px; } .ck_form.ck_minimal p { padding: 0px; } .ck_form, .ck_form * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .ck_form.ck_minimal .ck_form_fields { width: 100%; float: left; padding: 5%; } /* Form fields */ .ck_errorArea { display: none; /* temporary */ } #ck_success_msg { padding: 10px 10px 0px; border: solid 1px #ddd; background: #eee; } .ck_form.ck_minimal input[type="text"], .ck_form.ck_minimal input[type="email"] { font-size: 18px; padding: 10px 8px; width: 68%; border: 1px solid #d6d6d6; /* stroke */ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ background-color: #fff; /* layer fill content */ margin-bottom: 5px; height: auto; float: left; margin: 0px; margin-right: 2%; height: 42px; } .ck_form input[type="text"]:focus, .ck_form input[type="email"]:focus { outline: none; border-color: #aaa; } .ck_form.ck_minimal .ck_subscribe_button { width: 100%; color: #fff; margin: 0px; padding: 11px 0px; font-size: 18px; background: #0d6db8; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ cursor: pointer; border: none; text-shadow: none; width: 30%; float: left; height: 42px; } .ck_form.ck_minimal .ck_guarantee { color: #626262; font-size: 12px; text-align: center; padding: 15px 0px 0px; display: block; clear: both; } .ck_form .ck_powered_by { display: block; color: #aaa; font-size: 12px; } .ck_form .ck_powered_by:hover { display: block; color: #444; } .ck_converted_content { display: none; padding: 5%; background: #fff; } .ck_form.ck_minimal.width400 .ck_subscribe_button, .ck_form.ck_minimal.width400 input[type="email"] { width: 100%; float: none; margin-top: 5px; } .ck_slide_up, .ck_modal, .ck_slide_up .ck_minimal, .ck_modal .ck_minimal { min-width: 400px; } .page .ck_form.ck_minimal { margin: 50px auto; max-width: 600px; } /* v6 */ .ck_slide_up.ck_form_v6, .ck_modal.ck_form_v6, .ck_slide_up.ck_form_v6 .ck_minimal, .ck_modal.ck_form_v6 .ck_minimal { min-width: 0 !important; } @media all and (min-width: 801px) { .ck_modal.ck_form_v6 .ck_form.ck_minimal { margin-left: -300px; width: 600px; } } .ck_modal.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 20px; } .ck_slide_up.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 10px; } .ck_form_v6 #ck_success_msg { margin-top: 15px; padding: 0px 10px; } .ck_slide_up.ck_form_v6 .ck_minimal + .ck_close_link { top: 5px; } .ck_slide_up.ck_form_v6 .ck_minimal h3.ck_form_title { margin-top: 5px; } </style> Tue, 09 Jul 2019 02:00:18 +0000 http://bizz84.github.io/2019/07/09/state-management-setState-bloc-valueNotifier.html http://bizz84.github.io/2019/07/09/state-management-setState-bloc-valueNotifier.html Flutter, Android, iOS, Mobile App Development Flutter: Designing an Authentication API with Service Classes <p>In the previous articles we have seen how to create a simple authentication flow with <a href="https://pub.dev/packages/firebase_auth"><code class="language-plaintext highlighter-rouge">firebase_auth</code></a> and the <a href="https://pub.dev/packages/provider">Provider</a> package:</p> <ul> <li><a href="https://medium.com/coding-with-flutter/super-simple-authentication-flow-with-flutter-firebase-737bba04924c">Super Simple Authentication Flow with Flutter &amp; Firebase</a></li> <li><a href="https://medium.com/coding-with-flutter/flutter-global-access-vs-scoped-access-with-provider-8d6b94393bdf">Flutter: Global Access vs Scoped Access with Provider</a></li> </ul> <p>These techniques are the basis for my <a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a>.</p> <p>We will now see how use service classes to encapsulate 3rd party libraries and APIs, and decouple them from the rest of the application. We will use authentication as a concrete example of this.</p> <p>TL;DR:</p> <ul> <li>Write a service class as an API wrapper that hides any implementation details.</li> <li>This includes API methods with all their input and output (return) arguments.</li> <li>(optional) create a base abstract class for the service class, so that it’s easier to swap this with a different implementation</li> </ul> <h2 id="problem-statement">Problem Statement</h2> <p>In the previous article, we used this code to sign in the user with <code class="language-plaintext highlighter-rouge">FirebaseAuth</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// retrieve firebaseAuth from above in the widget tree</span> <span class="kd">final</span> <span class="n">firebaseAuth</span> <span class="o">=</span> <span class="n">Provider</span><span class="o">.</span><span class="na">of</span><span class="o">&lt;</span><span class="n">FirebaseAuth</span><span class="o">&gt;(</span><span class="n">context</span><span class="o">);</span> <span class="n">await</span> <span class="n">firebaseAuth</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="c1">// TODO: show dialog with error</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/754d92ed29672920b54ba03e32775f14 --> <p>Here we use <code class="language-plaintext highlighter-rouge">Provider.of&lt;FirebaseAuth&gt;(context)</code> to retrieve an instance of <code class="language-plaintext highlighter-rouge">FirebaseAuth</code>.</p> <p>This avoids the usual problems with global access (see my previous article about <a href="https://medium.com/coding-with-flutter/flutter-global-access-vs-scoped-access-with-provider-8d6b94393bdf">global access vs scoped access</a> for more details).</p> <p>However, we are still accessing the <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> API directly in our code.</p> <p>This can lead to some problems:</p> <ul> <li>How to deal with breaking changes in future versions of <code class="language-plaintext highlighter-rouge">FirebaseAuth</code>?</li> <li>What if we want to swap <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> with a different auth provider in the future?</li> </ul> <p>Either way, we would have to update or replace calls to <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> across our codebase.</p> <hr /> <p>And once our project grows, we may add a lot of additional packages. These may include <a href="https://pub.dev/packages/shared_preferences">shared preferences</a>, <a href="https://pub.dev/packages/permission_handler">permissions</a>, <a href="https://pub.dev/packages/firebase_analytics">analytics</a>, <a href="https://pub.dev/packages/local_auth">local authentication</a>, to name a few common ones.</p> <p>This multiplies the effort required to maintain our code as APIs change.</p> <h2 id="solution-create-service-classes">Solution: create service classes</h2> <p>A service class is simply a wrapper.</p> <p>Here is how we can create a generic authentication service based on <code class="language-plaintext highlighter-rouge">FirebaseAuth</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">User</span> <span class="o">{</span> <span class="kd">const</span> <span class="n">User</span><span class="o">({</span><span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">uid</span><span class="o">});</span> <span class="kd">final</span> <span class="kt">String</span> <span class="n">uid</span><span class="o">;</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">FirebaseAuthService</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">FirebaseAuth</span> <span class="n">_firebaseAuth</span> <span class="o">=</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">;</span> <span class="c1">// private method to create `User` from `FirebaseUser`</span> <span class="n">User</span> <span class="n">_userFromFirebase</span><span class="o">(</span><span class="n">FirebaseUser</span> <span class="n">user</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">user</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="n">User</span><span class="o">(</span><span class="nl">uid:</span> <span class="n">user</span><span class="o">.</span><span class="na">uid</span><span class="o">);</span> <span class="o">}</span> <span class="n">Stream</span><span class="o">&lt;</span><span class="n">User</span><span class="o">&gt;</span> <span class="kd">get</span> <span class="n">onAuthStateChanged</span> <span class="o">{</span> <span class="c1">// map all `FirebaseUser` objects to `User`, using the `_userFromFirebase` method</span> <span class="k">return</span> <span class="n">_firebaseAuth</span><span class="o">.</span><span class="na">onAuthStateChanged</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">_userFromFirebase</span><span class="o">);</span> <span class="o">}</span> <span class="n">Future</span><span class="o">&lt;</span><span class="n">User</span><span class="o">&gt;</span> <span class="n">signInAnonymously</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">user</span> <span class="o">=</span> <span class="n">await</span> <span class="n">_firebaseAuth</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="k">return</span> <span class="n">_userFromFirebase</span><span class="o">(</span><span class="n">user</span><span class="o">);</span> <span class="o">}</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">signOut</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">return</span> <span class="n">_firebaseAuth</span><span class="o">.</span><span class="na">signOut</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/f7ca097f0558fd64124983ff149770b0 --> <p>In this example, we create a <code class="language-plaintext highlighter-rouge">FirebaseAuthService</code> class that implements the API methods that we need from <code class="language-plaintext highlighter-rouge">FirebaseAuth</code>.</p> <p>Note how we have created a simple <code class="language-plaintext highlighter-rouge">User</code> class, which we use in the return type of all methods in the <code class="language-plaintext highlighter-rouge">FirebaseAuthService</code>.</p> <p>This way, the client code doesn’t depend on the <code class="language-plaintext highlighter-rouge">firebase_auth</code> package at all, because it can work with <code class="language-plaintext highlighter-rouge">User</code> objects, rather than <code class="language-plaintext highlighter-rouge">FirebaseUser</code>.</p> <hr /> <p>With this setup, we can update our top-level widget to use the new service class:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Provider</span><span class="o">&lt;</span><span class="n">FirebaseAuthService</span><span class="o">&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">FirebaseAuthService</span><span class="o">(),</span> <span class="nl">child:</span> <span class="n">MaterialApp</span><span class="o">(</span> <span class="nl">theme:</span> <span class="n">ThemeData</span><span class="o">(</span> <span class="nl">primarySwatch:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="o">,</span> <span class="o">),</span> <span class="nl">home:</span> <span class="n">LandingPage</span><span class="o">(),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/5b873734d1c196a3d1d3537389b35e1b --> <p>And then, we can replace all calls to <code class="language-plaintext highlighter-rouge">Provider.of&lt;FirebaseAuth&gt;(context)</code> with <code class="language-plaintext highlighter-rouge">Provider.of&lt;FirebaseAuthService&gt;(context)</code>.</p> <p>As a result, all our client code no longer needs to import this line:</p> <p><code class="language-plaintext highlighter-rouge">import 'package:firebase_auth/firebase_auth.dart';</code></p> <p>This means that if any breaking changes are introduced in <code class="language-plaintext highlighter-rouge">firebase_auth</code>, any compile errors can only appear inside our <code class="language-plaintext highlighter-rouge">FirebaseAuthService</code> class.</p> <p>As we add more and more packages, this approach makes our app more maintainable.</p> <hr /> <p>As an additional (and optional) step, we could also define an abstract base class:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AuthService</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="n">User</span><span class="o">&gt;</span> <span class="n">signInAnonymously</span><span class="o">();</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">signOut</span><span class="o">();</span> <span class="n">Stream</span><span class="o">&lt;</span><span class="n">User</span><span class="o">&gt;</span> <span class="kd">get</span> <span class="n">onAuthStateChanged</span><span class="o">;</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">FirebaseAuthService</span> <span class="kd">implements</span> <span class="n">AuthService</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/1c19299fc277c2ca034dfbeb11edeca5 --> <p>With this setup, we can use the base class type when we create and use our <code class="language-plaintext highlighter-rouge">Provider</code>, while creating an instance of the subclass in the <code class="language-plaintext highlighter-rouge">builder</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Provider</span><span class="o">&lt;</span><span class="n">AuthService</span><span class="o">&gt;(</span> <span class="c1">// base class</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">FirebaseAuthService</span><span class="o">(),</span> <span class="c1">// concrete subclass</span> <span class="nl">child:</span> <span class="n">MaterialApp</span><span class="o">(</span> <span class="nl">theme:</span> <span class="n">ThemeData</span><span class="o">(</span> <span class="nl">primarySwatch:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="o">,</span> <span class="o">),</span> <span class="nl">home:</span> <span class="n">LandingPage</span><span class="o">(),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/ef940375dd3baf8fe775744a56116309 --> <p>Creating a base class is extra work though. It may only be worth when we know that we need to have multiple implementations at the same time.</p> <p>If this is not the case, I recommend writing only one concrete service class. And since modern IDEs make refactoring tasks easy, you can rename the class and its usages without pain if needed.</p> <hr /> <p>For reference, I’m using a base <code class="language-plaintext highlighter-rouge">AuthService</code> class with two implementations in my <a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow</a> project.</p> <p>This is so that I can toggle between a Firebase and a mock authentication service at runtime, which is useful for testing and demo purposes.</p> <h2 id="showtime">Showtime</h2> <p>Here is a video showing all these techniques in practice, using my <a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow</a> as an example:</p> <p><img src="https://youtu.be/MjY1_LaXyd8" alt="" /></p> <h2 id="conclusion">Conclusion</h2> <p>Service classes are a good way of hiding the implementation details of 3rd party code in your app.</p> <p>They can be particularly useful when you need to call an API method in multiple places in your codebase (analytics and logging libraries are a good example of this).</p> <p>In a nutshell:</p> <ul> <li>Write a service class as an API wrapper that hides any implementation details.</li> <li>This includes API methods with all their input and output (return) arguments.</li> <li>(optional) create a base abstract class for the service class, so that it’s easier to swap this with a different implementation</li> </ul> <p>Happy coding!</p> <h2 id="source-code">Source code</h2> <p>The example code from this article was taken from my Reference Authentication Flow with Flutter &amp; Firebase:</p> <ul> <li><a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a></li> </ul> <p>In turn, this project complements all the in-depth material from my Flutter &amp; Firebase Udemy course. This is available for early access at this link (discount code included):</p> <ul> <li><a href="https://www.udemy.com/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=DART15&amp;password=codingwithflutter">Flutter &amp; Firebase: Build a Complete App for iOS &amp; Android</a></li> </ul> <script src="https://assets.convertkit.com/assets/CKJS4.js?v=21"></script> <div class="ck_form_container ck_inline" data-ck-version="7"> <div class="ck_form ck_minimal"> <div class="ck_form_fields"> <h3 class="ck_form_title">LEARN FLUTTER TODAY</h3> <div class="ck_description"> <p><img src="/img/coding-with-flutter-cheat-sheet-panel-noborder.png" style="display:block; margin-left: auto; margin-right: auto;" width="307" /></p> <p>Sign up for updates and get my free Flutter Layout Cheat Sheet.</p> </div> <div id="ck_success_msg" style="display:none;"> <p>Success! Now check your email to confirm your subscription.</p> </div> <!-- Form starts here --> <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/403521/subscribe" data-remote="true"> <input type="hidden" value="{&quot;form_style&quot;:&quot;minimal&quot;,&quot;converted_behavior&quot;:&quot;show&quot;,&quot;days_no_show&quot;:&quot;15&quot;,&quot;delay_seconds&quot;:&quot;10&quot;,&quot;display_devices&quot;:&quot;all&quot;,&quot;display_position&quot;:&quot;br&quot;,&quot;embed_style&quot;:&quot;inline&quot;,&quot;embed_trigger&quot;:&quot;scroll_percentage&quot;,&quot;scroll_percentage&quot;:&quot;70&quot;}" id="ck_form_options" /> <input type="hidden" name="id" value="403521" id="landing_page_id" /> <input type="hidden" name="ck_form_recaptcha" value="" id="ck_form_recaptcha" /> <div class="ck_errorArea"> <div id="ck_error_msg" style="display:none"> <p>There was an error submitting your subscription. Please try again.</p> </div> </div> <div class="ck_control_group ck_email_field_group"> <label class="ck_label" for="ck_emailField" style="display: none">Email Address</label> <input type="email" name="email" class="ck_email_address" id="ck_emailField" placeholder="Email Address" required="" /> </div> <div class="ck_control_group ck_captcha2_h_field_group ck-captcha2-h" style="position: absolute !important;left: -999em !important;"> <input type="text" name="captcha2_h" class="ck-captcha2-h" id="ck_captcha2_h" placeholder="We use this field to detect spam bots. If you fill this in, you will be marked as a spammer." /> </div> <label class="ck_checkbox" style="display:none;"> <input class="optIn ck_course_opted" name="course_opted" type="checkbox" id="optIn" checked="" /> <span class="ck_opt_in_prompt">I'd like to receive the free email course.</span> </label> <button class="subscribe_button ck_subscribe_button btn fields" id="ck_subscribe_button"> Subscribe </button> <span class="ck_guarantee"> No Spam. Ever. Unsubscribe at any time. </span> </form> </div> </div> </div> <style type="text/css">/* Layout */ .ck_form.ck_minimal { /* divider image */ background: #f9f9f9; font-family: 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; line-height: 1.5em; overflow: hidden; color: #202020; font-size: 16px; border: solid 1px #d1d1d1; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; clear: both; margin: 20px 0px; text-align: center; } .ck_form.ck_minimal h3.ck_form_title { text-align: center; margin: 0px 0px 10px; font-size: 28px; } .ck_form.ck_minimal h4 { text-align: center; font-family: 'Open Sans', Helvetica, Arial, sans-serif; text-transform: uppercase; font-size: 18px; font-weight: normal; padding-top: 0px; margin-top: 0px; } .ck_form.ck_minimal p { padding: 0px; } .ck_form, .ck_form * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .ck_form.ck_minimal .ck_form_fields { width: 100%; float: left; padding: 5%; } /* Form fields */ .ck_errorArea { display: none; /* temporary */ } #ck_success_msg { padding: 10px 10px 0px; border: solid 1px #ddd; background: #eee; } .ck_form.ck_minimal input[type="text"], .ck_form.ck_minimal input[type="email"] { font-size: 18px; padding: 10px 8px; width: 68%; border: 1px solid #d6d6d6; /* stroke */ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ background-color: #fff; /* layer fill content */ margin-bottom: 5px; height: auto; float: left; margin: 0px; margin-right: 2%; height: 42px; } .ck_form input[type="text"]:focus, .ck_form input[type="email"]:focus { outline: none; border-color: #aaa; } .ck_form.ck_minimal .ck_subscribe_button { width: 100%; color: #fff; margin: 0px; padding: 11px 0px; font-size: 18px; background: #0d6db8; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ cursor: pointer; border: none; text-shadow: none; width: 30%; float: left; height: 42px; } .ck_form.ck_minimal .ck_guarantee { color: #626262; font-size: 12px; text-align: center; padding: 15px 0px 0px; display: block; clear: both; } .ck_form .ck_powered_by { display: block; color: #aaa; font-size: 12px; } .ck_form .ck_powered_by:hover { display: block; color: #444; } .ck_converted_content { display: none; padding: 5%; background: #fff; } .ck_form.ck_minimal.width400 .ck_subscribe_button, .ck_form.ck_minimal.width400 input[type="email"] { width: 100%; float: none; margin-top: 5px; } .ck_slide_up, .ck_modal, .ck_slide_up .ck_minimal, .ck_modal .ck_minimal { min-width: 400px; } .page .ck_form.ck_minimal { margin: 50px auto; max-width: 600px; } /* v6 */ .ck_slide_up.ck_form_v6, .ck_modal.ck_form_v6, .ck_slide_up.ck_form_v6 .ck_minimal, .ck_modal.ck_form_v6 .ck_minimal { min-width: 0 !important; } @media all and (min-width: 801px) { .ck_modal.ck_form_v6 .ck_form.ck_minimal { margin-left: -300px; width: 600px; } } .ck_modal.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 20px; } .ck_slide_up.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 10px; } .ck_form_v6 #ck_success_msg { margin-top: 15px; padding: 0px 10px; } .ck_slide_up.ck_form_v6 .ck_minimal + .ck_close_link { top: 5px; } .ck_slide_up.ck_form_v6 .ck_minimal h3.ck_form_title { margin-top: 5px; } </style> Mon, 17 Jun 2019 02:00:18 +0000 http://bizz84.github.io/2019/06/17/designing-an-authentication-service-api.html http://bizz84.github.io/2019/06/17/designing-an-authentication-service-api.html Flutter, Android, iOS, Mobile App Development Flutter: Global Access vs Scoped Access with Provider <p>In the <a href="https://medium.com/coding-with-flutter/super-simple-authentication-flow-with-flutter-firebase-737bba04924c">previous article</a> we have seen how to build a simple authentication flow with Flutter &amp; Firebase.</p> <p>As part of this, we created three widgets:</p> <ul> <li>A <code class="language-plaintext highlighter-rouge">SignInPage</code>, used to sign in the user</li> <li>A <code class="language-plaintext highlighter-rouge">HomePage</code>, used to sign out the user</li> <li>A <code class="language-plaintext highlighter-rouge">LandingPage</code>, used to decide which page to show</li> </ul> <p>And these are composed together in the following widget tree:</p> <p><img src="/images/simple-auth-flow-widget-tree-firebase-auth.png" alt="" /></p> <p>As the diagram shows, all these widgets access <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> as a global singleton variable:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SignInPage</span> <span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="c1">// HomePage</span> <span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signOut</span><span class="o">();</span> <span class="c1">// LandingPage</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">onAuthStateChanged</span><span class="o">;</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/55b83bcc01941b5c97a3912075213981 --> <p>Many 3rd party libraries expose their APIs via singletons, so it is not uncommon to see them used like this.</p> <p>However, <strong>global access</strong> via singletons leads to code that is <strong>not testable</strong>.</p> <p>And in this case, our widgets become <strong>tightly coupled</strong> to <code class="language-plaintext highlighter-rouge">FirebaseAuth</code>.</p> <p>In practice, this means that:</p> <ul> <li>we cannot write a test to verify that <code class="language-plaintext highlighter-rouge">signInAnonymously()</code> is called when the sign-in button is pressed.</li> <li>we can’t easily swap <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> with a different authentication service if we want to.</li> </ul> <p>And we need to address these issues if we want to write clean, testable code.</p> <p>So in this article we will explore a better alternative to global access.</p> <p>And I will show how to write an API wrapper for <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> in a follow-up article.</p> <h2 id="global-access-vs-scoped-access">Global Access vs Scoped Access</h2> <p>There is one main problem with global access. And we can use our example code to illustrate this:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="c1">// TODO: show dialog with error</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/80a0900116943a409e781f9e8673ac3b --> <p>Here, the <code class="language-plaintext highlighter-rouge">SignInPage</code> <strong>asks</strong> for an instance of <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> directly.</p> <p>A better approach would be to <strong>tell</strong> our <code class="language-plaintext highlighter-rouge">SignInPage</code> what it needs, by passing a <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> object to its constructor:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">SignInPage</span><span class="o">({</span><span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">firebaseAuth</span><span class="o">});</span> <span class="kd">final</span> <span class="n">FirebaseAuth</span> <span class="n">firebaseAuth</span><span class="o">;</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">await</span> <span class="n">firebaseAuth</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="c1">// TODO: show dialog with error</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/66d0704ff9d98c8efd062ab948960645 --> <p>This approach is called <strong>constructor injection</strong>.</p> <p><strong>Note</strong>: Widgets in the Flutter SDK are created this way. Arguments are passed to the constructor, and used inside the widget. This guarantees that there are no side effects, because widgets are <strong>pure</strong> components that only depend on arguments that are passed <strong>explicitly</strong>.</p> <hr /> <p>So, should we always pass dependencies to the constructors of our widgets?</p> <p>Not so fast.</p> <p>Flutter uses composition heavily.</p> <p>This often results in very <strong>nested</strong> widget trees, and can lead to this scenario:</p> <p><img src="/images/global-access-scoped-access-big-widget-tree.png" alt="" /></p> <p>Here, we have three consumer widgets that require <strong>synchronous</strong> access to a <code class="language-plaintext highlighter-rouge">FirebaseUser</code> object.</p> <p>This object is only available inside the <code class="language-plaintext highlighter-rouge">StreamBuilder</code> of the <code class="language-plaintext highlighter-rouge">LandingPage</code>.</p> <p>In order for our consumer widgets to access <code class="language-plaintext highlighter-rouge">FirebaseUser</code>, we would have to inject it to all intermediate widgets, even if they don’t need it directly.</p> <p>I’ve been there. Trust me, it’s not fun.</p> <p><em>To avoid this, you may be tempted to get the user with a call to <code class="language-plaintext highlighter-rouge">FirebaseAuth.instance.currentUser()</code>. However, this is an <strong>asynchronous</strong> method that returns a <code class="language-plaintext highlighter-rouge">Future&lt;FirebaseUser&gt;</code>. And we shouldn’t need to call an <strong>async</strong> API to get an object that we already retrieved.</em></p> <hr /> <p>So constructor injection doesn’t scale well when we need to <strong>propagate</strong> data down the widget tree.</p> <p>And it is also very impractical with widgets that require a lot of arguments. Example:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SomeComplexWidget</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">SignInPage</span><span class="o">({</span><span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">firebaseAuth</span><span class="o">,</span> <span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">firestore</span><span class="o">,</span> <span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">sharedPreferences</span><span class="o">});</span> <span class="kd">final</span> <span class="n">FirebaseAuth</span> <span class="n">firebaseAuth</span><span class="o">;</span> <span class="kd">final</span> <span class="n">Firestore</span> <span class="n">firestore</span><span class="o">;</span> <span class="kd">final</span> <span class="n">SharedPreferences</span> <span class="n">sharedPreferences</span><span class="o">;</span> <span class="c1">// complex logic here</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/a4e46170be84d5189bab5dfdcb1431e8 --> <p>I love a lot of boilerplate code - nobody ever said.</p> <p>Luckily, there is a solution.</p> <h2 id="scoped-access">Scoped Access</h2> <p>Scoped access is about making objects available to an entire widget <strong>sub-tree</strong>.</p> <p>Flutter already uses this technique. If you ever called <code class="language-plaintext highlighter-rouge">Navigator.of(context)</code>, <code class="language-plaintext highlighter-rouge">MediaQuery.of(context)</code> or <code class="language-plaintext highlighter-rouge">Theme.of(context)</code>, then you have used scoped access.</p> <p>Under the hood, this is implemented with a special widget called <a href="https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html"><code class="language-plaintext highlighter-rouge">InheritedWidget</code></a>.</p> <p>And while you could build your own widgets based on <code class="language-plaintext highlighter-rouge">InheritedWidget</code>, this can also lead to a lot of boilerplate code.</p> <p>Well, thanks to the community, there is a better way.</p> <h2 id="enter-provider">Enter Provider</h2> <p><a href="https://pub.dev/packages/provider">This library</a> by <a href="https://github.com/rrousselGit">Remi Rousselet</a> and the Flutter team makes life easier.</p> <p>In a nutshell, Provider is a dependency injection system for your Flutter apps. We can use it to expose objects of any kind (values, streams, models, BLoCs) to our widgets.</p> <p>So let’s see how to use it.</p> <hr /> <p>We can add Provider to our <code class="language-plaintext highlighter-rouge">pubspec.yaml</code> file:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// pubspec.yaml dependencies: provider: ^3.0.0 </code></pre></div></div> <p>And we can update our example app to use it, by making two changes:</p> <ul> <li>Add a <code class="language-plaintext highlighter-rouge">Provider&lt;FirebaseAuth&gt;</code> to the top of our widget tree</li> <li>Use <code class="language-plaintext highlighter-rouge">Provider.of&lt;FirebaseAuth&gt;(context)</code> where needed, instead of <code class="language-plaintext highlighter-rouge">FirebaseAuth.instance</code>.</li> </ul> <p><em>Note: As an alternative to <code class="language-plaintext highlighter-rouge">Provider.of</code>, you could use <code class="language-plaintext highlighter-rouge">Consumer</code>, which is also part of the Provider package. <a href="https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple">This page</a> on the official Flutter documentation covers their usage and differences in detail.</em></p> <p>The updated widget tree for our example app looks like this:</p> <p><img src="/images/global-access-scoped-access-widget-tree-provider.png" alt="" /></p> <p>The top three widgets (<code class="language-plaintext highlighter-rouge">MyApp</code>, <code class="language-plaintext highlighter-rouge">Provider</code> and <code class="language-plaintext highlighter-rouge">MaterialApp</code>), can be composed like this:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:provider/provider.dart'</span><span class="o">;</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">runApp</span><span class="o">(</span><span class="n">MyApp</span><span class="o">());</span> <span class="kd">class</span> <span class="nc">MyApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Provider</span><span class="o">&lt;</span><span class="n">FirebaseAuth</span><span class="o">&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">,</span> <span class="nl">child:</span> <span class="n">MaterialApp</span><span class="o">(</span> <span class="nl">title:</span> <span class="s">'Flutter Demo'</span><span class="o">,</span> <span class="nl">theme:</span> <span class="n">ThemeData</span><span class="o">(</span> <span class="nl">primarySwatch:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="o">,</span> <span class="o">),</span> <span class="nl">home:</span> <span class="n">LandingPage</span><span class="o">(),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/0024647f17ed4f76f6a6d79fa8550e13 --> <p>Then, we can update our <code class="language-plaintext highlighter-rouge">LandingPage</code> to use <code class="language-plaintext highlighter-rouge">Provider</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">LandingPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// retrieve firebaseAuth from above in the widget tree</span> <span class="kd">final</span> <span class="n">firebaseAuth</span> <span class="o">=</span> <span class="n">Provider</span><span class="o">.</span><span class="na">of</span><span class="o">&lt;</span><span class="n">FirebaseAuth</span><span class="o">&gt;(</span><span class="n">context</span><span class="o">);</span> <span class="k">return</span> <span class="n">StreamBuilder</span><span class="o">&lt;</span><span class="n">FirebaseUser</span><span class="o">&gt;(</span> <span class="nl">stream:</span> <span class="n">firebaseAuth</span><span class="o">.</span><span class="na">onAuthStateChanged</span><span class="o">,</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">snapshot</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">snapshot</span><span class="o">.</span><span class="na">connectionState</span> <span class="o">==</span> <span class="n">ConnectionState</span><span class="o">.</span><span class="na">active</span><span class="o">)</span> <span class="o">{</span> <span class="n">FirebaseUser</span> <span class="n">user</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">user</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">SignInPage</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="n">HomePage</span><span class="o">();</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">body:</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">(),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">},</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/b96b1f2437d83926fae2520ec6e44019 --> <p>And do the same with the <code class="language-plaintext highlighter-rouge">SignInPage</code> and <code class="language-plaintext highlighter-rouge">HomePage</code> to use <code class="language-plaintext highlighter-rouge">Provider</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// retrieve firebaseAuth from above in the widget tree</span> <span class="kd">final</span> <span class="n">firebaseAuth</span> <span class="o">=</span> <span class="n">Provider</span><span class="o">.</span><span class="na">of</span><span class="o">&lt;</span><span class="n">FirebaseAuth</span><span class="o">&gt;(</span><span class="n">context</span><span class="o">);</span> <span class="n">await</span> <span class="n">firebaseAuth</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="c1">// TODO: show dialog with error</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">HomePage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signOut</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// retrieve firebaseAuth from above in the widget tree</span> <span class="kd">final</span> <span class="n">firebaseAuth</span> <span class="o">=</span> <span class="n">Provider</span><span class="o">.</span><span class="na">of</span><span class="o">&lt;</span><span class="n">FirebaseAuth</span><span class="o">&gt;(</span><span class="n">context</span><span class="o">);</span> <span class="n">await</span> <span class="n">firebaseAuth</span><span class="o">.</span><span class="na">signOut</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="c1">// TODO: show dialog with error</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/4d5ddb081d622a4661b75e0a5e1ecf50 --> <p><strong>Note about BuildContext</strong>: We can retrieve the <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> object because we pass a <code class="language-plaintext highlighter-rouge">BuildContext</code> variable to <code class="language-plaintext highlighter-rouge">Provider.of&lt;FirebaseAuth&gt;(context)</code>. You can think of <code class="language-plaintext highlighter-rouge">context</code> as the <strong>position</strong> of a widget in the widget tree.</p> <hr /> <p>Remember when I said that constructor injection is impractical with deeply nested widget trees?</p> <p>Well, Provider makes objects accessible to an entire widget sub-tree.</p> <p>So every time you need to propagate data down the widget tree, Provider should light-up in your brain. 💡</p> <!-- https://gist.github.com/bizz84/c7062be1689a56f37bbd68f11a4b6a0c --> <h2 id="wrap-up">Wrap up</h2> <p>In this article we have learned about <strong>scoped access</strong> as an alternative to <strong>global access</strong>.</p> <p>And we have seen how to use <code class="language-plaintext highlighter-rouge">Provider</code> as a dependency injection system for Flutter.</p> <p>By the way, this was just a basic introduction.</p> <p>There are many advanced use cases where we need to make multiple objects accessible to our widgets. Some objects may have inter-dependencies. And we may need <a href="https://github.com/rrousselGit/provider#existing-providers">different <strong>kinds</strong></a> of providers.</p> <p>Provider supports all these cases. I will write more about it in the future.</p> <p>For now, you can see how I’m using it in my Reference Authentication Flow with Flutter &amp; Firebase on GitHub:</p> <ul> <li><a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a></li> </ul> <p>And for a more in-depth coverage of Provider and how to use it for state management, you can check my Flutter &amp; Firebase Udemy course. This is available for early access at this link (discount code included):</p> <ul> <li><a href="https://www.udemy.com/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=DART15&amp;password=codingwithflutter">Flutter &amp; Firebase: Build a Complete App for iOS &amp; Android</a></li> </ul> <p>Happy coding!</p> <h2 id="references">References</h2> <ul> <li><a href="https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple">Flutter docs - Simple app state management</a></li> <li><a href="https://pub.dev/packages/provider">Provider package</a></li> </ul> <script src="https://assets.convertkit.com/assets/CKJS4.js?v=21"></script> <div class="ck_form_container ck_inline" data-ck-version="7"> <div class="ck_form ck_minimal"> <div class="ck_form_fields"> <h3 class="ck_form_title">LEARN FLUTTER TODAY</h3> <div class="ck_description"> <p><img src="/img/coding-with-flutter-cheat-sheet-panel-noborder.png" style="display:block; margin-left: auto; margin-right: auto;" width="307" /></p> <p>Sign up for updates and get my free Flutter Layout Cheat Sheet.</p> </div> <div id="ck_success_msg" style="display:none;"> <p>Success! Now check your email to confirm your subscription.</p> </div> <!-- Form starts here --> <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/403521/subscribe" data-remote="true"> <input type="hidden" value="{&quot;form_style&quot;:&quot;minimal&quot;,&quot;converted_behavior&quot;:&quot;show&quot;,&quot;days_no_show&quot;:&quot;15&quot;,&quot;delay_seconds&quot;:&quot;10&quot;,&quot;display_devices&quot;:&quot;all&quot;,&quot;display_position&quot;:&quot;br&quot;,&quot;embed_style&quot;:&quot;inline&quot;,&quot;embed_trigger&quot;:&quot;scroll_percentage&quot;,&quot;scroll_percentage&quot;:&quot;70&quot;}" id="ck_form_options" /> <input type="hidden" name="id" value="403521" id="landing_page_id" /> <input type="hidden" name="ck_form_recaptcha" value="" id="ck_form_recaptcha" /> <div class="ck_errorArea"> <div id="ck_error_msg" style="display:none"> <p>There was an error submitting your subscription. Please try again.</p> </div> </div> <div class="ck_control_group ck_email_field_group"> <label class="ck_label" for="ck_emailField" style="display: none">Email Address</label> <input type="email" name="email" class="ck_email_address" id="ck_emailField" placeholder="Email Address" required="" /> </div> <div class="ck_control_group ck_captcha2_h_field_group ck-captcha2-h" style="position: absolute !important;left: -999em !important;"> <input type="text" name="captcha2_h" class="ck-captcha2-h" id="ck_captcha2_h" placeholder="We use this field to detect spam bots. If you fill this in, you will be marked as a spammer." /> </div> <label class="ck_checkbox" style="display:none;"> <input class="optIn ck_course_opted" name="course_opted" type="checkbox" id="optIn" checked="" /> <span class="ck_opt_in_prompt">I'd like to receive the free email course.</span> </label> <button class="subscribe_button ck_subscribe_button btn fields" id="ck_subscribe_button"> Subscribe </button> <span class="ck_guarantee"> No Spam. Ever. Unsubscribe at any time. </span> </form> </div> </div> </div> <style type="text/css">/* Layout */ .ck_form.ck_minimal { /* divider image */ background: #f9f9f9; font-family: 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; line-height: 1.5em; overflow: hidden; color: #202020; font-size: 16px; border: solid 1px #d1d1d1; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; clear: both; margin: 20px 0px; text-align: center; } .ck_form.ck_minimal h3.ck_form_title { text-align: center; margin: 0px 0px 10px; font-size: 28px; } .ck_form.ck_minimal h4 { text-align: center; font-family: 'Open Sans', Helvetica, Arial, sans-serif; text-transform: uppercase; font-size: 18px; font-weight: normal; padding-top: 0px; margin-top: 0px; } .ck_form.ck_minimal p { padding: 0px; } .ck_form, .ck_form * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .ck_form.ck_minimal .ck_form_fields { width: 100%; float: left; padding: 5%; } /* Form fields */ .ck_errorArea { display: none; /* temporary */ } #ck_success_msg { padding: 10px 10px 0px; border: solid 1px #ddd; background: #eee; } .ck_form.ck_minimal input[type="text"], .ck_form.ck_minimal input[type="email"] { font-size: 18px; padding: 10px 8px; width: 68%; border: 1px solid #d6d6d6; /* stroke */ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ background-color: #fff; /* layer fill content */ margin-bottom: 5px; height: auto; float: left; margin: 0px; margin-right: 2%; height: 42px; } .ck_form input[type="text"]:focus, .ck_form input[type="email"]:focus { outline: none; border-color: #aaa; } .ck_form.ck_minimal .ck_subscribe_button { width: 100%; color: #fff; margin: 0px; padding: 11px 0px; font-size: 18px; background: #0d6db8; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ cursor: pointer; border: none; text-shadow: none; width: 30%; float: left; height: 42px; } .ck_form.ck_minimal .ck_guarantee { color: #626262; font-size: 12px; text-align: center; padding: 15px 0px 0px; display: block; clear: both; } .ck_form .ck_powered_by { display: block; color: #aaa; font-size: 12px; } .ck_form .ck_powered_by:hover { display: block; color: #444; } .ck_converted_content { display: none; padding: 5%; background: #fff; } .ck_form.ck_minimal.width400 .ck_subscribe_button, .ck_form.ck_minimal.width400 input[type="email"] { width: 100%; float: none; margin-top: 5px; } .ck_slide_up, .ck_modal, .ck_slide_up .ck_minimal, .ck_modal .ck_minimal { min-width: 400px; } .page .ck_form.ck_minimal { margin: 50px auto; max-width: 600px; } /* v6 */ .ck_slide_up.ck_form_v6, .ck_modal.ck_form_v6, .ck_slide_up.ck_form_v6 .ck_minimal, .ck_modal.ck_form_v6 .ck_minimal { min-width: 0 !important; } @media all and (min-width: 801px) { .ck_modal.ck_form_v6 .ck_form.ck_minimal { margin-left: -300px; width: 600px; } } .ck_modal.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 20px; } .ck_slide_up.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 10px; } .ck_form_v6 #ck_success_msg { margin-top: 15px; padding: 0px 10px; } .ck_slide_up.ck_form_v6 .ck_minimal + .ck_close_link { top: 5px; } .ck_slide_up.ck_form_v6 .ck_minimal h3.ck_form_title { margin-top: 5px; } </style> Mon, 10 Jun 2019 02:00:18 +0000 http://bizz84.github.io/2019/06/10/global-access-vs-scoped-access.html http://bizz84.github.io/2019/06/10/global-access-vs-scoped-access.html Flutter, Android, iOS, Mobile App Development Super Simple Authentication Flow with Flutter & Firebase <p>User authentication is a very common requirement for a lot of apps.</p> <p>In this article we implement a simple authentication flow in Flutter, in less than 100 lines of code.</p> <p>As part of this, we will see how to:</p> <ul> <li>use <a href="https://pub.dev/packages/firebase_auth">FirebaseAuth</a> to sign in anonymously.</li> <li>use <a href="https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html"><code class="language-plaintext highlighter-rouge">StreamBuilder</code></a> to present different screens depending on the authentication status of the user.</li> </ul> <p>This is the basis of my <a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a> on GitHub.</p> <p>So, let’s start from the basics.</p> <h2 id="initial-setup">Initial setup</h2> <p>We will use Firebase Authentication for this example.</p> <p>After creating a new Flutter project, we can add <a href="https://pub.dev/packages/firebase_auth"><code class="language-plaintext highlighter-rouge">firebase_auth</code></a> to the dependencies section of our <code class="language-plaintext highlighter-rouge">pubspec.yaml</code> file:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// pubspec.yaml dependencies: flutter: sdk: flutter firebase_auth: 0.11.1+3 </code></pre></div></div> <p>Then, we need to configure our Flutter app to use Firebase. This guide explains what to do step-by-step:</p> <ul> <li><a href="https://firebase.google.com/docs/flutter/setup">Add Firebase to your Flutter app</a></li> </ul> <p>The two most important steps are:</p> <ul> <li>Add <code class="language-plaintext highlighter-rouge">GoogleServices-info.plist</code> and <code class="language-plaintext highlighter-rouge">google-services.json</code> to the iOS and Android projects, otherwise the app will crash at startup.</li> <li>Enable anonymous sign-in in the Firebase console, as we will use it in this example.</li> </ul> <p>I cover all these steps in detail in my Flutter &amp; Firebase course.</p> <h2 id="lets-code">Let’s code</h2> <p>Our application will have two pages, called <code class="language-plaintext highlighter-rouge">SignInPage</code> and <code class="language-plaintext highlighter-rouge">HomePage</code>, which are both stateless widgets:</p> <p><img src="/images/simple-auth-flow-screens.gif" alt="" /></p> <p>Then we will have another widget called <code class="language-plaintext highlighter-rouge">LandingPage</code>. We will use this to decide which page to show depending on the <strong>authentication status</strong> of the user.</p> <p>Here is the entire widget tree of this app:</p> <p><img src="/images/simple-auth-flow-widget-tree.png" alt="" /></p> <p>Let’s implement this in code.</p> <h2 id="signinpage">SignInPage</h2> <p>First, the <code class="language-plaintext highlighter-rouge">SignInPage</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span> <span class="kd">class</span> <span class="nc">SignInPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="c1">// TODO: show dialog with error</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Sign in'</span><span class="o">)),</span> <span class="nl">body:</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">RaisedButton</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Sign in anonymously'</span><span class="o">),</span> <span class="nl">onPressed:</span> <span class="n">_signInAnonymously</span><span class="o">,</span> <span class="o">),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/b559270a3d8c6ce63d0c4513998ff5bb --> <p>All this does is to show a centered <code class="language-plaintext highlighter-rouge">RaisedButton</code>, which calls <code class="language-plaintext highlighter-rouge">_signInAnonymously()</code> when pressed.</p> <p>This method calls <code class="language-plaintext highlighter-rouge">FirebaseAuth.instance.signInAnonymously()</code> and <strong>awaits</strong> for the result.</p> <p><strong>NOTES</strong></p> <ul> <li><code class="language-plaintext highlighter-rouge">try/catch</code> is used to catch any exceptions. We can use this to alert the user if sign-in fails.</li> <li><code class="language-plaintext highlighter-rouge">await FirebaseAuth.instance.signInAnonymously()</code> returns a <code class="language-plaintext highlighter-rouge">FirebaseUser</code>, but our code doesn’t use the return value. That’s because we will handle the authentication status of the user somewhere else.</li> </ul> <p>Speaking of which…</p> <h2 id="landingpage">LandingPage</h2> <p>We use this widget class to decide which page to show:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span> <span class="kd">class</span> <span class="nc">LandingPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">StreamBuilder</span><span class="o">&lt;</span><span class="n">FirebaseUser</span><span class="o">&gt;(</span> <span class="nl">stream:</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">onAuthStateChanged</span><span class="o">,</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">snapshot</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">snapshot</span><span class="o">.</span><span class="na">connectionState</span> <span class="o">==</span> <span class="n">ConnectionState</span><span class="o">.</span><span class="na">active</span><span class="o">)</span> <span class="o">{</span> <span class="n">FirebaseUser</span> <span class="n">user</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">user</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">SignInPage</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="n">HomePage</span><span class="o">();</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">body:</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">(),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">},</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/e9785afad6ed32b7308e562b9c269acd --> <p>This page uses two main ingredients:</p> <ul> <li>The <code class="language-plaintext highlighter-rouge">FirebaseAuth.instance.onAuthStateChanged</code> stream. This receives a new value each time the user signs in or out.</li> <li>A <code class="language-plaintext highlighter-rouge">StreamBuilder</code> of type <code class="language-plaintext highlighter-rouge">FirebaseUser</code>. This takes <code class="language-plaintext highlighter-rouge">onAuthStateChanged</code> as an input stream, and calls the <code class="language-plaintext highlighter-rouge">builder</code> when the stream is updated.</li> </ul> <p>So when a call to <code class="language-plaintext highlighter-rouge">FirebaseAuth.instance.signInAnonymously()</code> succeeds, a new <code class="language-plaintext highlighter-rouge">FirebaseUser</code> is added to <code class="language-plaintext highlighter-rouge">onAuthStateChanged</code>.</p> <p>As a result, the builder is called and we can extract the <code class="language-plaintext highlighter-rouge">FirebaseUser</code> from <code class="language-plaintext highlighter-rouge">snapshot.data</code>. And we use this to decide which page to show:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">FirebaseUser</span> <span class="n">user</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">user</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">SignInPage</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="nf">HomePage</span><span class="p">(</span><span class="o">);</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/8accda924b82601a0a9077ee9d13c822 --> <p>Also note how we’re checking the <code class="language-plaintext highlighter-rouge">connectionState</code> of the snapshot:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">snapshot</span><span class="o">.</span><span class="na">connectionState</span> <span class="o">==</span> <span class="n">ConnectionState</span><span class="o">.</span><span class="na">active</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// do something</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/62e5f5eb85be0c84fbdf73d01777b7ab --> <p>This can be any of four possible values: <code class="language-plaintext highlighter-rouge">none</code>, <code class="language-plaintext highlighter-rouge">waiting</code>, <code class="language-plaintext highlighter-rouge">active</code>, <code class="language-plaintext highlighter-rouge">done</code>.</p> <p>When the application starts, the builder is first called with <code class="language-plaintext highlighter-rouge">ConnectionState.waiting</code>. We can use this to show a centered <code class="language-plaintext highlighter-rouge">CircularProgressIndicator()</code>.</p> <p>Once the authentication status is determined, the <code class="language-plaintext highlighter-rouge">connectionState</code> becomes <code class="language-plaintext highlighter-rouge">active</code>, and our builder is called again.</p> <p>In summary, we have <strong>three</strong> possible authentication states:</p> <ul> <li>unknown</li> <li>user signed-in</li> <li>user not signed-in</li> </ul> <p>And this code is all we need to handle them:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">snapshot</span><span class="o">.</span><span class="na">connectionState</span> <span class="o">==</span> <span class="n">ConnectionState</span><span class="o">.</span><span class="na">active</span><span class="o">)</span> <span class="o">{</span> <span class="n">FirebaseUser</span> <span class="n">user</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">user</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">SignInPage</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="n">HomePage</span><span class="o">();</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">body:</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">(),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/ab1db7244bffd18e53f61137ba2eea87 --> <p>Moving on…</p> <h2 id="homepage">HomePage</h2> <p>This class is similar to the <code class="language-plaintext highlighter-rouge">SignInPage</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'dart:async'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span> <span class="kd">class</span> <span class="nc">HomePage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signOut</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signOut</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="c1">// TODO: show dialog with error</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span> <span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Home Page'</span><span class="o">),</span> <span class="nl">actions:</span> <span class="o">&lt;</span><span class="n">Widget</span><span class="o">&gt;[</span> <span class="n">FlatButton</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span> <span class="s">'Logout'</span><span class="o">,</span> <span class="nl">style:</span> <span class="n">TextStyle</span><span class="o">(</span> <span class="nl">fontSize:</span> <span class="mf">18.0</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span> <span class="o">),</span> <span class="o">),</span> <span class="nl">onPressed:</span> <span class="n">_signOut</span><span class="o">,</span> <span class="o">),</span> <span class="o">],</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/b876cb194dffdeb338f1d8d341ec9aa7 --> <p>This code calls <code class="language-plaintext highlighter-rouge">FirebaseAuth.instance.signOut()</code> when the logout button is pressed.</p> <p>On success, a <code class="language-plaintext highlighter-rouge">null</code> value is added to <code class="language-plaintext highlighter-rouge">onAuthStateChanged</code>. As a result, the builder in our <code class="language-plaintext highlighter-rouge">LandingPage</code> is called again, and this time we return a <code class="language-plaintext highlighter-rouge">SignInPage()</code>.</p> <h2 id="finishing-up">Finishing up</h2> <p>Almost there. Now we just need to update our <code class="language-plaintext highlighter-rouge">main.dart</code> file, to pass the <code class="language-plaintext highlighter-rouge">LandingPage()</code> to the <code class="language-plaintext highlighter-rouge">home</code> argument of <code class="language-plaintext highlighter-rouge">MaterialApp</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">runApp</span><span class="o">(</span><span class="n">MyApp</span><span class="o">());</span> <span class="kd">class</span> <span class="nc">MyApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">MaterialApp</span><span class="o">(</span> <span class="nl">title:</span> <span class="s">'Flutter Demo'</span><span class="o">,</span> <span class="nl">theme:</span> <span class="n">ThemeData</span><span class="o">(</span> <span class="nl">primarySwatch:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="o">,</span> <span class="o">),</span> <span class="nl">home:</span> <span class="n">LandingPage</span><span class="o">(),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/c4f8b56a67f7526aeec286fecd86217c --> <p>All in all, this entire flow takes less than 100 lines of code.</p> <p>Here is the code for the entire example:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">runApp</span><span class="o">(</span><span class="n">MyApp</span><span class="o">());</span> <span class="kd">class</span> <span class="nc">MyApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">MaterialApp</span><span class="o">(</span> <span class="nl">title:</span> <span class="s">'Flutter Demo'</span><span class="o">,</span> <span class="nl">theme:</span> <span class="n">ThemeData</span><span class="o">(</span> <span class="nl">primarySwatch:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="o">,</span> <span class="o">),</span> <span class="nl">home:</span> <span class="n">LandingPage</span><span class="o">(),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">LandingPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">StreamBuilder</span><span class="o">&lt;</span><span class="n">FirebaseUser</span><span class="o">&gt;(</span> <span class="nl">stream:</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">onAuthStateChanged</span><span class="o">,</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">snapshot</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">snapshot</span><span class="o">.</span><span class="na">connectionState</span> <span class="o">==</span> <span class="n">ConnectionState</span><span class="o">.</span><span class="na">active</span><span class="o">)</span> <span class="o">{</span> <span class="n">FirebaseUser</span> <span class="n">user</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">user</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">SignInPage</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="n">HomePage</span><span class="o">();</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">body:</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">(),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">},</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">SignInPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInAnonymously</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInAnonymously</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="c1">// TODO: show dialog with error</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Sign in'</span><span class="o">)),</span> <span class="nl">body:</span> <span class="n">Center</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">RaisedButton</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Sign in anonymously'</span><span class="o">),</span> <span class="nl">onPressed:</span> <span class="n">_signInAnonymously</span><span class="o">,</span> <span class="o">),</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">HomePage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signOut</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signOut</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="c1">// TODO: show dialog with error</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span> <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span> <span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Home Page'</span><span class="o">),</span> <span class="nl">actions:</span> <span class="o">&lt;</span><span class="n">Widget</span><span class="o">&gt;[</span> <span class="n">FlatButton</span><span class="o">(</span> <span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span> <span class="s">'Logout'</span><span class="o">,</span> <span class="nl">style:</span> <span class="n">TextStyle</span><span class="o">(</span> <span class="nl">fontSize:</span> <span class="mf">18.0</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span> <span class="o">),</span> <span class="o">),</span> <span class="nl">onPressed:</span> <span class="n">_signOut</span><span class="o">,</span> <span class="o">),</span> <span class="o">],</span> <span class="o">),</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/e95de31df8d21f35b223c106d5f9d3c2 --> <p>This uses just a single <code class="language-plaintext highlighter-rouge">main.dart</code> file. I advise to put widget classes in separate files in your own projects ;)</p> <h2 id="conclusion">Conclusion</h2> <p>We have seen how to build a simple authentication flow with Firebase.</p> <p>This example doesn’t use any fancy app architecture.</p> <p>And sometimes, keeping things simple is a good idea. As Albert Einstein once said:</p> <blockquote> <p>Everything Should Be Made as Simple as Possible, But Not Simpler</p> </blockquote> <p>However, Einstein wasn’t a software developer. 😄</p> <p>And the code I presented has two major drawbacks:</p> <p><strong>1) Global Access</strong></p> <p>The <code class="language-plaintext highlighter-rouge">LandingPage</code>, <code class="language-plaintext highlighter-rouge">SignInPage</code>, <code class="language-plaintext highlighter-rouge">HomePage</code> all access <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> via the <code class="language-plaintext highlighter-rouge">instance</code> singleton variable.</p> <p><img src="/images/simple-auth-flow-widget-tree-firebase-auth.png" alt="" /></p> <p>This is not recommended because the resulting code is <strong>not testable</strong>.</p> <p><strong>2) Direct use of FirebaseAuth</strong></p> <p>Using <code class="language-plaintext highlighter-rouge">FirebaseAuth</code> directly in our widgets is not a good idea.</p> <p>This can cause problems if our application grows, and we decide to use a different authentication provider in the future.</p> <hr /> <p>In the next article we will see how to address these concerns. We will do this by:</p> <ul> <li>moving from <strong>global access</strong> to <strong>scoped access</strong> with Provider</li> <li>writing an authentication service class as a wrapper for <code class="language-plaintext highlighter-rouge">FirebaseAuth</code></li> </ul> <p>By the way, I cover all these topics (and much more) in my Flutter &amp; Firebase Udemy course. This is available for early access at this link (discount code included):</p> <ul> <li><a href="https://www.udemy.com/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=DART15&amp;password=codingwithflutter">Flutter &amp; Firebase: Build a Complete App for iOS &amp; Android</a></li> </ul> <p>Happy coding!</p> <script src="https://assets.convertkit.com/assets/CKJS4.js?v=21"></script> <div class="ck_form_container ck_inline" data-ck-version="7"> <div class="ck_form ck_minimal"> <div class="ck_form_fields"> <h3 class="ck_form_title">LEARN FLUTTER TODAY</h3> <div class="ck_description"> <p><img src="/img/coding-with-flutter-cheat-sheet-panel-noborder.png" style="display:block; margin-left: auto; margin-right: auto;" width="307" /></p> <p>Sign up for updates and get my free Flutter Layout Cheat Sheet.</p> </div> <div id="ck_success_msg" style="display:none;"> <p>Success! Now check your email to confirm your subscription.</p> </div> <!-- Form starts here --> <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/403521/subscribe" data-remote="true"> <input type="hidden" value="{&quot;form_style&quot;:&quot;minimal&quot;,&quot;converted_behavior&quot;:&quot;show&quot;,&quot;days_no_show&quot;:&quot;15&quot;,&quot;delay_seconds&quot;:&quot;10&quot;,&quot;display_devices&quot;:&quot;all&quot;,&quot;display_position&quot;:&quot;br&quot;,&quot;embed_style&quot;:&quot;inline&quot;,&quot;embed_trigger&quot;:&quot;scroll_percentage&quot;,&quot;scroll_percentage&quot;:&quot;70&quot;}" id="ck_form_options" /> <input type="hidden" name="id" value="403521" id="landing_page_id" /> <input type="hidden" name="ck_form_recaptcha" value="" id="ck_form_recaptcha" /> <div class="ck_errorArea"> <div id="ck_error_msg" style="display:none"> <p>There was an error submitting your subscription. Please try again.</p> </div> </div> <div class="ck_control_group ck_email_field_group"> <label class="ck_label" for="ck_emailField" style="display: none">Email Address</label> <input type="email" name="email" class="ck_email_address" id="ck_emailField" placeholder="Email Address" required="" /> </div> <div class="ck_control_group ck_captcha2_h_field_group ck-captcha2-h" style="position: absolute !important;left: -999em !important;"> <input type="text" name="captcha2_h" class="ck-captcha2-h" id="ck_captcha2_h" placeholder="We use this field to detect spam bots. If you fill this in, you will be marked as a spammer." /> </div> <label class="ck_checkbox" style="display:none;"> <input class="optIn ck_course_opted" name="course_opted" type="checkbox" id="optIn" checked="" /> <span class="ck_opt_in_prompt">I'd like to receive the free email course.</span> </label> <button class="subscribe_button ck_subscribe_button btn fields" id="ck_subscribe_button"> Subscribe </button> <span class="ck_guarantee"> No Spam. Ever. Unsubscribe at any time. </span> </form> </div> </div> </div> <style type="text/css">/* Layout */ .ck_form.ck_minimal { /* divider image */ background: #f9f9f9; font-family: 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; line-height: 1.5em; overflow: hidden; color: #202020; font-size: 16px; border: solid 1px #d1d1d1; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; clear: both; margin: 20px 0px; text-align: center; } .ck_form.ck_minimal h3.ck_form_title { text-align: center; margin: 0px 0px 10px; font-size: 28px; } .ck_form.ck_minimal h4 { text-align: center; font-family: 'Open Sans', Helvetica, Arial, sans-serif; text-transform: uppercase; font-size: 18px; font-weight: normal; padding-top: 0px; margin-top: 0px; } .ck_form.ck_minimal p { padding: 0px; } .ck_form, .ck_form * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .ck_form.ck_minimal .ck_form_fields { width: 100%; float: left; padding: 5%; } /* Form fields */ .ck_errorArea { display: none; /* temporary */ } #ck_success_msg { padding: 10px 10px 0px; border: solid 1px #ddd; background: #eee; } .ck_form.ck_minimal input[type="text"], .ck_form.ck_minimal input[type="email"] { font-size: 18px; padding: 10px 8px; width: 68%; border: 1px solid #d6d6d6; /* stroke */ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ background-color: #fff; /* layer fill content */ margin-bottom: 5px; height: auto; float: left; margin: 0px; margin-right: 2%; height: 42px; } .ck_form input[type="text"]:focus, .ck_form input[type="email"]:focus { outline: none; border-color: #aaa; } .ck_form.ck_minimal .ck_subscribe_button { width: 100%; color: #fff; margin: 0px; padding: 11px 0px; font-size: 18px; background: #0d6db8; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ cursor: pointer; border: none; text-shadow: none; width: 30%; float: left; height: 42px; } .ck_form.ck_minimal .ck_guarantee { color: #626262; font-size: 12px; text-align: center; padding: 15px 0px 0px; display: block; clear: both; } .ck_form .ck_powered_by { display: block; color: #aaa; font-size: 12px; } .ck_form .ck_powered_by:hover { display: block; color: #444; } .ck_converted_content { display: none; padding: 5%; background: #fff; } .ck_form.ck_minimal.width400 .ck_subscribe_button, .ck_form.ck_minimal.width400 input[type="email"] { width: 100%; float: none; margin-top: 5px; } .ck_slide_up, .ck_modal, .ck_slide_up .ck_minimal, .ck_modal .ck_minimal { min-width: 400px; } .page .ck_form.ck_minimal { margin: 50px auto; max-width: 600px; } /* v6 */ .ck_slide_up.ck_form_v6, .ck_modal.ck_form_v6, .ck_slide_up.ck_form_v6 .ck_minimal, .ck_modal.ck_form_v6 .ck_minimal { min-width: 0 !important; } @media all and (min-width: 801px) { .ck_modal.ck_form_v6 .ck_form.ck_minimal { margin-left: -300px; width: 600px; } } .ck_modal.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 20px; } .ck_slide_up.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 10px; } .ck_form_v6 #ck_success_msg { margin-top: 15px; padding: 0px 10px; } .ck_slide_up.ck_form_v6 .ck_minimal + .ck_close_link { top: 5px; } .ck_slide_up.ck_form_v6 .ck_minimal h3.ck_form_title { margin-top: 5px; } </style> Mon, 03 Jun 2019 02:00:18 +0000 http://bizz84.github.io/2019/06/03/simple-authentication-flow-with-flutter.html http://bizz84.github.io/2019/06/03/simple-authentication-flow-with-flutter.html Flutter, Android, iOS, Mobile App Development Widget-Async-Bloc-Service: A Practical Architecture for Flutter Apps <p><em>Difficulty level: Intermediate/advanced. All opinions are my own.</em></p> <h2 id="introduction">Introduction</h2> <p>State management is a hot topic in Flutter right now.</p> <p>Over the last year, various state management techniques were proposed.</p> <p>The Flutter team and community have not (yet) settled on a single “go-to” solution.</p> <p>This makes sense, because <strong>different apps have different requirements</strong>. And choosing the most appropriate technique depends on what we’re trying to build.</p> <p>Truth be told, some state management techniques have proven very popular.</p> <ul> <li>Scoped Model is known for its simplicity.</li> <li>BLoCs are also widely used, and they work well with Streams and RxDart for more complex apps.</li> <li>Most recently at Google I/O, the Flutter team showed us how to use the <a href="https://pub.dev/packages/provider"><code class="language-plaintext highlighter-rouge">Provider</code></a> package and <a href="https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html"><code class="language-plaintext highlighter-rouge">ChangeNotifier</code></a> to propagate state changes across widgets.</li> </ul> <p>Having multiple choices can be a good thing.</p> <p>But it can also be confusing. And choosing a technique that will work and scale well as our apps grow is important.</p> <p>What’s more, <strong>making the right choice early on can save us a lot of time and effort</strong>.</p> <h1 id="my-take-on-state-management-and-app-architecture">My take on state management and app architecture</h1> <p>Over the last year I’ve been building a lot of Flutter apps, big and small.</p> <p>During this time, I have encountered and solved many problems.</p> <p>And I have learned that there is no silver bullet for state management.</p> <p>However, after building things and taking them apart over and over, I have fine-tuned a technique that works well in all my projects.</p> <p>So in this article I introduce a new architectural pattern that I have defined by:</p> <ul> <li>borrowing a lot of ideas from existing patterns</li> <li>tweaking them to suit the needs of real-world Flutter apps</li> </ul> <p>Before we see what this pattern looks like, let me define some goals.</p> <p>This pattern should:</p> <ul> <li>Be <strong>easy to understand</strong>, once the fundamental building blocks are clear</li> <li>Be <strong>easy to replicate</strong> when adding new features</li> <li>Build upon clean architecture principles</li> <li>Work well when writing <strong>reactive</strong> Flutter apps</li> <li>Require little or no boilerplate code</li> <li>Lead to testable code</li> <li>Lead to portable code</li> <li>Favour small, composable widgets and classes</li> <li>Integrate easily with asynchronous APIs (Futures and Streams)</li> <li>Scale well with apps that increase in size and complexity</li> </ul> <hr /> <p>Out of the existing state management techniques for Flutter, this pattern builds most heavily on <strong>BLoCs</strong>, and is quite similar to the <a href="https://www.burkharts.net/apps/blog/rxvms-a-practical-architecture-for-flutter-apps/">RxVMS architecture</a>.</p> <p>Without further ado, I am pleased to introduce:</p> <!-- I don't have an official name for this pattern, but if I had to choose one, it would be: --> <h1 id="the-widget-async-bloc-service-pattern">The Widget-Async-BLoC-Service Pattern</h1> <p>Shorthand: <strong>WABS</strong> (which is cool because it contains my initials :D)</p> <p>This architectural pattern comes in four variants.</p> <p><strong>Widget-Bloc-Service</strong></p> <p><img src="/images/wabs-diagram-widget-bloc-service.png" alt="" /></p> <p><strong>Widget-Service</strong></p> <p><img src="/images/wabs-diagram-widget-service.png" alt="" /></p> <p><strong>Widget-Bloc</strong></p> <p><img src="/images/wabs-diagram-widget-bloc.png" alt="" /></p> <p><strong>Widget only</strong></p> <p><img src="/images/wabs-diagram-widget.png" alt="" /></p> <p>NOTE: aside from the Widget item, the BLoC and Service items are <strong>both optional</strong>.</p> <p>In other words: you can <strong>use or omit them as appropriate on a case-by-case basis</strong>.</p> <p>Now, let’s explore the full implementation with a more detailed diagram:</p> <p><img src="/images/wabs-detail.png" alt="" /></p> <p>First of all, this diagram defines <strong>three application layers</strong>:</p> <ul> <li><strong>The UI layer</strong>: this is always necessary as it is where our widgets live.</li> <li><strong>The data layer</strong> (optional): this is where we add our logic and modify state.</li> <li><strong>The service layer</strong> (optional): this is what we use to communicate to external services.</li> </ul> <p>Next, let’s define some rules for what each layer can (and cannot) do.</p> <h1 id="rules-of-play">Rules of play</h1> <h2 id="ui-layer">UI Layer</h2> <p>This is where we put our widgets.</p> <p>Widgets can be stateless or stateful, but they should not include any <strong>explicit</strong> state management logic.</p> <p>An example of <strong>explicit</strong> state management is the Flutter counter example, where we increment the counter with <code class="language-plaintext highlighter-rouge">setState()</code> when the increment button is pressed.</p> <p>An example of <strong>implicit</strong> state management is a <code class="language-plaintext highlighter-rouge">StatefulWidget</code> that contains a <code class="language-plaintext highlighter-rouge">TextField</code> managed by a <code class="language-plaintext highlighter-rouge">TextEditingController</code>. In this case we need a <code class="language-plaintext highlighter-rouge">StatefulWidget</code> because <code class="language-plaintext highlighter-rouge">TextEditingController</code> introduces side effects (<a href="https://stackoverflow.com/questions/52249578/how-to-deal-with-unwanted-widget-build">I discovered this</a> <a href="https://github.com/flutter/flutter/issues/29542">the hard way</a>), but we are not managing any state <strong>explicitly</strong>.</p> <hr /> <p>Widgets in the UI layer are free to call <strong>sync</strong> or <strong>async</strong> methods defined by blocs or services, and can subscribe to streams via <code class="language-plaintext highlighter-rouge">StreamBuilder</code>.</p> <p>Note how the diagram above connects a single widget to both the input and output of the BLoC. But we can use this pattern to connect one widget to the input, and <strong>another</strong> widget to the output:</p> <p><img src="/images/wabs-async-bloc-widgets.png" alt="" /></p> <p>In other words, we can implement a <strong>producer → consumer</strong> data flow.</p> <hr /> <p>The WABS pattern encourages us to move any state management logic to the data layer. So let’s take a look at it.</p> <h2 id="data-layer">Data Layer</h2> <p>In this layer we can define <strong>local</strong> or <strong>global</strong> application state, as well as the code to modify it.</p> <p>This is done with business logic components (BLoCs), a pattern <a href="https://youtu.be/PLHln7wHgPE">first introduced</a> during DartConf 2018.</p> <p>BLoC was conceived to <strong>separate the business logic from the UI layer</strong>, and increase code reuse across multiple platforms.</p> <p>When using the BLoC pattern, widgets can:</p> <ul> <li>dispatch <strong>events</strong> to a <strong>sink</strong></li> <li>be notified of <strong>state updates</strong> via a <strong>stream</strong></li> </ul> <p>According to the original definition, we can only communicate with BLoCs via <strong>sinks</strong> and <strong>streams</strong>.</p> <hr /> <p>While I like this definition, I find it too restrictive in a number of use cases. So in WABS I use a variant of BLoC called <strong>Async BLoC</strong>.</p> <p>Just like with BLoCs, we have output stream(s) that can be subscribed to.</p> <p>However, the BLoC input(s) can include a <strong>synchronous sink</strong>, an <strong>asynchronous method</strong>, or both.</p> <p>In other words, we go from this:</p> <p><img src="/images/wabs-bloc.png" alt="" /></p> <p>to this:</p> <p><img src="/images/wabs-async-bloc.png" alt="" /></p> <p>The asyncrhonous method(s) can:</p> <ul> <li>Add zero, one or more values to the input sink.</li> <li>Return a <code class="language-plaintext highlighter-rouge">Future&lt;T&gt;</code> with a result. The calling code can <code class="language-plaintext highlighter-rouge">await</code> for the result and do something accordingly.</li> <li>Throw an exception. The calling code can detect this with <code class="language-plaintext highlighter-rouge">try/catch</code> and show an alert if desired.</li> </ul> <p><em>Later on, we will see a full example of how useful this is in practice.</em></p> <hr /> <p><strong>More on BLoCs</strong></p> <p>An Async BLoC can define a <code class="language-plaintext highlighter-rouge">StreamController</code>/<code class="language-plaintext highlighter-rouge">Stream</code> pair, or the equivalent <code class="language-plaintext highlighter-rouge">BehaviorSubject</code>/<code class="language-plaintext highlighter-rouge">Observable</code> if using RxDart.</p> <p>If we want, we can even perform advanced stream operations, like combining streams with <code class="language-plaintext highlighter-rouge">combineLatest</code>. And just to be clear:</p> <ul> <li> <p>I recommend having multiple streams in a single BLoC if they need to be combined in some way.</p> </li> <li> <p>I discourage having multiple <code class="language-plaintext highlighter-rouge">StreamController</code>s in a single BLoC. Instead, I prefer breaking up the code in two or more BLoC classes, for better separation of concerns.</p> </li> </ul> <p><strong>Things we should/shouldn’t do in the data layer / BLoCs</strong></p> <ul> <li><strong>BLoCs should only contain pure Dart code</strong>. No UI code, no importing Flutter files, or using a <a href="https://api.flutter.dev/flutter/widgets/BuildContext-class.html"><code class="language-plaintext highlighter-rouge">BuildContext</code></a> inside BLoCs.</li> <li>BLoCs should not call 3rd party code <strong>directly</strong>. This is the job of <strong>service</strong> classes.</li> <li>The interface between Widgets and BLoCs is the same as the interface between BLoCs and services. That is, BloCs can communicate directly with service classes via sync/async methods, and be notified of updates via streams.</li> </ul> <h2 id="service-layer">Service layer</h2> <p>Service classes have the same input/output interface as BLoCs.</p> <p>However, there is one fundamental distinction between services and BLoCs.</p> <ul> <li>BLoCs can hold and modify state.</li> <li>Services can’t.</li> </ul> <p>In other words, we can think of services as <strong>pure</strong>, functional components.</p> <p>They can modify and transform data they receive from 3rd party libraries.</p> <p><strong>Example: Firestore service</strong></p> <ul> <li>We can implement a <code class="language-plaintext highlighter-rouge">FirestoreDatabase</code> service as a domain-specific API-wrapper for Firestore.</li> <li><strong>Data in (read)</strong>: This transforms streams of key-value pairs from Firestore documents into strongly-typed immutable data models.</li> <li><strong>Data out (write)</strong>: This converts data models back to key-value pairs for writing to Firestore.</li> </ul> <p>In this case, the service class performs simple data manipulation. Unlike BLoCs, it doesn’t hold any state.</p> <p><em><strong>A note about terminology</strong>: Other articles use the term Repository when referring to classes that talk to 3rd party libraries. Even the definition of the Repository pattern has evolved over time (see <a href="http://hannesdorfmann.com/android/evolution-of-the-repository-pattern">this article</a> for more info). In this article, I don’t make a clear distinction between Service and Repository.</em></p> <h1 id="putting-things-together-the-provider-package">Putting things together: the Provider package</h1> <p>Once we have defined our BLoCs and services, we need to make them available to our widgets.</p> <p>For some time now, I have been using the <a href="https://pub.dev/packages/provider"><code class="language-plaintext highlighter-rouge">provider</code></a> package by <a href="https://github.com/rrousselGit">Remi Rousselet</a>. This is a full <strong>dependency injection system</strong> for Flutter, based on <code class="language-plaintext highlighter-rouge">InheritedWidget</code>.</p> <p>I really like its simplicity. Here is how to use it to add an authentication service:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="n">Provider</span><span class="o">&lt;</span><span class="n">AuthService</span><span class="o">&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">FirebaseAuthService</span><span class="o">(),</span> <span class="c1">// FirebaseAuthService implements AuthService</span> <span class="nl">child:</span> <span class="n">MaterialApp</span><span class="o">(...),</span> <span class="o">);</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/9b6d09db4e3427a2cfb7389ca3a1206c --> <p>And this is how we could use it to create a BLoC:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="n">Provider</span><span class="o">&lt;</span><span class="n">SignInBloc</span><span class="o">&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">SignInBloc</span><span class="o">(</span><span class="nl">auth:</span> <span class="n">auth</span><span class="o">),</span> <span class="nl">dispose:</span> <span class="o">(</span><span class="n">_</span><span class="o">,</span> <span class="n">bloc</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">bloc</span><span class="o">.</span><span class="na">dispose</span><span class="o">(),</span> <span class="nl">child:</span> <span class="n">Consumer</span><span class="o">&lt;</span><span class="n">SignInBloc</span><span class="o">&gt;(</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">_</span><span class="o">,</span> <span class="n">bloc</span><span class="o">,</span> <span class="n">__</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">SignInPage</span><span class="o">(</span><span class="nl">bloc:</span> <span class="n">bloc</span><span class="o">),</span> <span class="o">),</span> <span class="o">);</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/22d39cc7aaf94c515985573613dc1082 --> <p>Note how the <code class="language-plaintext highlighter-rouge">Provider</code> widget takes an optional <code class="language-plaintext highlighter-rouge">dispose</code> callback. We use this to dispose BLoCs and close the corresponding <code class="language-plaintext highlighter-rouge">StreamController</code>s.</p> <p><code class="language-plaintext highlighter-rouge">Provider</code> gives us a simple and flexible API that we can use to add anything we want to our widget tree. It works great with BLoCs, services, values and more.</p> <p><img src="/images/wabs-provider-all-things.png" alt="" /></p> <p>I’ll talk in more detail about how to use <code class="language-plaintext highlighter-rouge">Provider</code> in some of my upcoming articles. For now, I highly recommend this talk from Google I/O:</p> <ul> <li><a href="https://www.youtube.com/watch?v=d_m5csmrf7I">Pragmatic State Management in Flutter (Google I/O’19)</a></li> </ul> <hr /> <h1 id="a-real-world-example-sign-in-page">A real-world example: Sign-In Page</h1> <p>Now that we have seen how WABS works conceptually, let’s use it to build a sign in flow with Firebase authentication.</p> <p>Here is a sample interaction from my <a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a>:</p> <p><img src="/images/wabs-sign-in-example.gif" alt="" /></p> <p>A few observations:</p> <ul> <li>When sign-in is triggered, we disable all buttons and show a <code class="language-plaintext highlighter-rouge">CircularProgressIndicator</code>. We set a <code class="language-plaintext highlighter-rouge">loading</code> state to <code class="language-plaintext highlighter-rouge">true</code> to do this.</li> <li>When sign-in succeeds or fails, we re-enable all buttons and restore the title <code class="language-plaintext highlighter-rouge">Text</code>. We set <code class="language-plaintext highlighter-rouge">loading=false</code> to do this.</li> <li>When sign-in fails, we present an alert dialog.</li> </ul> <p>Here is a simplified version of the <code class="language-plaintext highlighter-rouge">SignInBloc</code> used to drive this logic:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'dart:async'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:firebase_auth_demo_flutter/services/auth_service.dart'</span><span class="o">;</span> <span class="kn">import</span> <span class="s">'package:meta/meta.dart'</span><span class="o">;</span> <span class="kd">class</span> <span class="nc">SignInBloc</span> <span class="o">{</span> <span class="n">SignInBloc</span><span class="o">({</span><span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">auth</span><span class="o">});</span> <span class="kd">final</span> <span class="n">AuthService</span> <span class="n">auth</span><span class="o">;</span> <span class="kd">final</span> <span class="n">StreamController</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="n">_isLoadingController</span> <span class="o">=</span> <span class="n">StreamController</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;();</span> <span class="n">Stream</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="kd">get</span> <span class="n">isLoadingStream</span> <span class="o">=&gt;</span> <span class="n">_isLoadingController</span><span class="o">.</span><span class="na">stream</span><span class="o">;</span> <span class="kt">void</span> <span class="n">_setIsLoading</span><span class="o">(</span><span class="kt">bool</span> <span class="n">isLoading</span><span class="o">)</span> <span class="o">=&gt;</span> <span class="n">_isLoadingController</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">isLoading</span><span class="o">);</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">signInWithGoogle</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">_setIsLoading</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> <span class="k">return</span> <span class="n">await</span> <span class="n">auth</span><span class="o">.</span><span class="na">signInWithGoogle</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="k">rethrow</span><span class="o">;</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">_setIsLoading</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kt">void</span> <span class="n">dispose</span><span class="o">()</span> <span class="o">=&gt;</span> <span class="n">_isLoadingController</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/ed0821b3a4c23418221ec4cb7a48eff7 --> <p>Note how the public API of this BLoC only exposes a <code class="language-plaintext highlighter-rouge">Stream</code> and a <code class="language-plaintext highlighter-rouge">Future</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Stream</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="kd">get</span> <span class="n">isLoadingStream</span><span class="o">;</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">signInWithGoogle</span><span class="o">();</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/a589e39604fc1d82a7d37e80211257e1 --> <p>This is in line with our definition of Async BLoC.</p> <p>All the magic happens in the <code class="language-plaintext highlighter-rouge">signInWithGoogle()</code> method. So let’s review this again with comments:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">signInWithGoogle</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// first, add loading = true to the stream sink</span> <span class="n">_setIsLoading</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> <span class="c1">// then sign-in and await for the result</span> <span class="k">return</span> <span class="n">await</span> <span class="n">auth</span><span class="o">.</span><span class="na">signInWithGoogle</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// if sign in failed, rethrow the exception to the calling code</span> <span class="k">rethrow</span><span class="o">;</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="c1">// on success or failure, add loading = false to the stream sink</span> <span class="n">_setIsLoading</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/05bd4f14afa4983c9b2c9e10f6f20e35 --> <p>Just like a normal BLoC, this method adds values to a sink.</p> <p>But in addition to that, it can <strong>return a value</strong> asynchronously, or <strong>throw an exception</strong>.</p> <p>This means that we can write code like this in our <code class="language-plaintext highlighter-rouge">SignInPage</code>:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// called by the `onPressed` callback of our button</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInWithGoogle</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">await</span> <span class="n">bloc</span><span class="o">.</span><span class="na">signInWithGoogle</span><span class="o">();</span> <span class="c1">// handle success</span> <span class="o">}</span> <span class="n">on</span> <span class="n">PlatformException</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// handle error (show alert)</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/7ecf84f88742841d1e81b539a88f9d9e --> <p>This code looks simple enough. And it should be, because all we need here is <code class="language-plaintext highlighter-rouge">async/await</code> and <code class="language-plaintext highlighter-rouge">try/catch</code>.</p> <p>And yet, this is not possible with the “strict” version of BLoC that only uses a sink and a stream. For reference, implementing something like this in Redux is… uhm… not fun! 😅</p> <p>Async-BLoC may seem like a small improvement to BLoC, <strong>but it makes all the difference</strong>.</p> <hr /> <p><strong>A note on handling exceptions</strong></p> <p>By the way, another possible way of handling exceptions would be to add an error object to the stream, like so:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">signInWithGoogle</span><span class="o">()</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// first, add loading = true to the stream sink</span> <span class="n">_setIsLoading</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> <span class="c1">// then sign-in and await for the result</span> <span class="k">return</span> <span class="n">await</span> <span class="n">auth</span><span class="o">.</span><span class="na">signInWithGoogle</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// add error to the stream</span> <span class="n">_isLoadingController</span><span class="o">.</span><span class="na">addError</span><span class="o">(</span><span class="n">e</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="c1">// on success or failure, add loading = false to the stream sink</span> <span class="n">_setIsLoading</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/d1a43825e1daeeb6808e30b521515808 --> <p>Then, in the widget class we could write code like this:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignInPage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span> <span class="n">SignInPage</span><span class="o">({</span><span class="nd">@required</span> <span class="k">this</span><span class="o">.</span><span class="na">bloc</span><span class="o">});</span> <span class="kd">final</span> <span class="n">SignInBloc</span> <span class="n">bloc</span><span class="o">;</span> <span class="c1">// called by the `onPressed` callback of our button</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_signInWithGoogle</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span> <span class="n">await</span> <span class="n">bloc</span><span class="o">.</span><span class="na">signInWithGoogle</span><span class="o">();</span> <span class="o">}</span> <span class="kt">void</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">StreamBuilder</span><span class="o">(</span> <span class="nl">stream:</span> <span class="n">isLoadingStream</span><span class="o">,</span> <span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">snapshot</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">snapshot</span><span class="o">.</span><span class="na">hasError</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// show error</span> <span class="n">showDialog</span><span class="o">(...);</span> <span class="o">}</span> <span class="c1">// build UI based on snapshot</span> <span class="o">}</span> <span class="o">)</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/2fc9f948b4ef40715ce201281a71fa25 --> <p>However, this is bad for two reasons:</p> <ul> <li>It shows a dialog inside the builder of the <code class="language-plaintext highlighter-rouge">StreamBuilder</code>. This is not great because the builder is only supposed to return a widget, and not execute any imperative code.</li> <li>This code lacks clarity. The place where we show the error is completely different from the place where we sign in.</li> </ul> <p>So, don’t do this, and use <code class="language-plaintext highlighter-rouge">try/catch</code> as shown above. 😉</p> <p>Moving on…</p> <h2 id="can-we-use-wabs-to-create-an-async-service">Can we use WABS to create an <code class="language-plaintext highlighter-rouge">Async-Service</code>?</h2> <p>Of course. As I said before:</p> <ul> <li>BLoCs can hold and modify state.</li> <li>Services can’t.</li> </ul> <p>However, their public-facing APIs obey the same rules.</p> <p>Here’s an example of a service class for a database API:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">Database</span> <span class="o">{</span> <span class="c1">// CRUD operations for Job</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">setJob</span><span class="o">(</span><span class="n">Job</span> <span class="n">job</span><span class="o">);</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">deleteJob</span><span class="o">(</span><span class="n">Job</span> <span class="n">job</span><span class="o">);</span> <span class="n">Stream</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Job</span><span class="o">&gt;&gt;</span> <span class="n">jobsStream</span><span class="o">();</span> <span class="c1">// CRUD operations for Entry</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">setEntry</span><span class="o">(</span><span class="n">Entry</span> <span class="n">entry</span><span class="o">);</span> <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">deleteEntry</span><span class="o">(</span><span class="n">Entry</span> <span class="n">entry</span><span class="o">);</span> <span class="n">Stream</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Entry</span><span class="o">&gt;&gt;</span> <span class="n">entriesStream</span><span class="o">({</span><span class="n">Job</span> <span class="n">job</span><span class="o">});</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/4b205c8a4d6d816ce7dbef19b51b8ead --> <p>We could use this API to write and read data to/from Cloud Firestore.</p> <p>The calling code could define this method to write a new <code class="language-plaintext highlighter-rouge">Job</code> to the database:</p> <div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_submit</span><span class="o">(</span><span class="n">Job</span> <span class="n">job</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">await</span> <span class="n">database</span><span class="o">.</span><span class="na">setJob</span><span class="o">(</span><span class="n">job</span><span class="o">);</span> <span class="c1">// handle success</span> <span class="o">}</span> <span class="n">on</span> <span class="n">PlatformException</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// handle error (show alert) </span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <!-- https://gist.github.com/bizz84/1b57e601288e63bd54a277e3161c4499 --> <p>Same pattern, very simple error handling.</p> <h2 id="comparison-with-rxvms">Comparison with RxVMS</h2> <p>In this article, I have introduced Widget-Async-BLoC-Service as an adaptation of existing architectural patterns in Flutter.</p> <p>WABS is most similar to the <a href="https://www.burkharts.net/apps/blog/rxvms-a-practical-architecture-for-flutter-apps/">RxVMS pattern</a> by Thomas Burkhart.</p> <p>There is even a close match between the individual layers:</p> <table> <thead> <tr> <th>WABS</th> <th>RxVMS</th> </tr> </thead> <tbody> <tr> <td>Widget</td> <td>View</td> </tr> <tr> <td>Async</td> <td>RxCommand</td> </tr> <tr> <td>BLoC</td> <td>Manager</td> </tr> <tr> <td>Service</td> <td>Service</td> </tr> </tbody> </table> <p>The main differences between the two are that:</p> <ul> <li>WABS uses the <a href="https://pub.dev/packages/provider">Provider</a> package, while RxVMS uses the <a href="https://pub.dev/packages/get_it">GetIt</a> service locator</li> <li>WABS uses simple <code class="language-plaintext highlighter-rouge">async</code> methods to handle UI events, while RxVMS uses <a href="https://pub.dev/packages/rx_command">RxCommand</a>.</li> </ul> <p>RxCommand is a powerful abstraction to handle UI events and updates. It removes the boilerplate code required to create a <code class="language-plaintext highlighter-rouge">StreamController</code>/<code class="language-plaintext highlighter-rouge">Stream</code> pair with BLoCs.</p> <p>It does however come with a bigger learning curve. For my use cases Async-Bloc does the job and is simpler, despite a bit of extra boilerplate.</p> <p>I also like that WABS can be implemented without any external libraries (aside from the Provider package).</p> <p>Ultimately choosing one or the other depends on your use cases, but also personal preference and taste.</p> <h2 id="should-i-use-blocs-in-my-apps">Should I use BLoCs in my apps?</h2> <p>BLoCs come with a steep learning curve. To understand them, you need to also be familiar with Streams and <code class="language-plaintext highlighter-rouge">StreamBuilder</code>.</p> <p>When working with streams, there are various considerations to make:</p> <ul> <li>what is the connection state of the stream? (<code class="language-plaintext highlighter-rouge">none</code>, <code class="language-plaintext highlighter-rouge">waiting</code>, <code class="language-plaintext highlighter-rouge">active</code>, <code class="language-plaintext highlighter-rouge">done</code>)</li> <li>is the stream single or multiple subscription?</li> <li><code class="language-plaintext highlighter-rouge">StreamController</code> and <code class="language-plaintext highlighter-rouge">StreamSubscription</code> always need to be disposed</li> <li>dealing with nested <code class="language-plaintext highlighter-rouge">StreamBuilder</code>s can lead to thorny debugging issues when Flutter rebuilds the widget tree</li> </ul> <p>All of this adds more overhead to our code.</p> <p>And when updating local application state (e.g. propagating state from one widget to another), there are simpler alternatives to BLoC. I plan to write about this on a follow-up article.</p> <p>In any case, I found BLoCs very effective when building realtime apps with Firestore, where the data flows from the backend into the app via streams.</p> <p>In this scenario, it is common to combine streams or perform transformations with RxDart. BLoCs are a great place to do this.</p> <h2 id="conclusion">Conclusion</h2> <p>This article was an in-depth introduction to WABS, an architectural pattern that I have been using for some time in multiple projects.</p> <p>Truth be told, I have been been refining this over time, and I didn’t even have a name for it until I wrote this article.</p> <p>As I said before, architectural patterns are just tools to get our job done. My advice: choose the tools that make more sense for you and your projects.</p> <p>And if you use WABS in your projects, let me know how it works for you. 😉</p> <p>Happy coding!</p> <h2 id="source-code">Source code</h2> <p>The example code from this article was taken from my Reference Authentication Flow with Flutter &amp; Firebase:</p> <ul> <li><a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a></li> </ul> <p>In turn, this project complements all the in-depth material from my Flutter &amp; Firebase Udemy course. This is available for early access at this link (discount code included):</p> <ul> <li><a href="https://www.udemy.com/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=DART15&amp;password=codingwithflutter">Flutter &amp; Firebase: Build a Complete App for iOS &amp; Android</a></li> </ul> <h2 id="references">References</h2> <ul> <li><a href="https://pub.dev/packages/provider">Provider package</a></li> <li><a href="https://www.youtube.com/watch?v=d_m5csmrf7I">Pragmatic State Management in Flutter (Google I/O’19)</a></li> <li><a href="https://www.burkharts.net/apps/blog/rxvms-a-practical-architecture-for-flutter-apps/">RxVMS a practical architecture for Flutter Apps</a></li> <li><a href="https://www.burkharts.net/apps/blog/rxvms-foundations-rxcommand-and-getit/">RxVMS foundations: RxCommand and GetIt</a></li> <li><a href="https://youtu.be/PLHln7wHgPE">Flutter / AngularDart – Code sharing, better together (DartConf 2018)</a></li> </ul> <script src="https://assets.convertkit.com/assets/CKJS4.js?v=21"></script> <div class="ck_form_container ck_inline" data-ck-version="7"> <div class="ck_form ck_minimal"> <div class="ck_form_fields"> <h3 class="ck_form_title">LEARN FLUTTER TODAY</h3> <div class="ck_description"> <p><img src="/img/coding-with-flutter-cheat-sheet-panel-noborder.png" style="display:block; margin-left: auto; margin-right: auto;" width="307" /></p> <p>Sign up for updates and get my free Flutter Layout Cheat Sheet.</p> </div> <div id="ck_success_msg" style="display:none;"> <p>Success! Now check your email to confirm your subscription.</p> </div> <!-- Form starts here --> <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/403521/subscribe" data-remote="true"> <input type="hidden" value="{&quot;form_style&quot;:&quot;minimal&quot;,&quot;converted_behavior&quot;:&quot;show&quot;,&quot;days_no_show&quot;:&quot;15&quot;,&quot;delay_seconds&quot;:&quot;10&quot;,&quot;display_devices&quot;:&quot;all&quot;,&quot;display_position&quot;:&quot;br&quot;,&quot;embed_style&quot;:&quot;inline&quot;,&quot;embed_trigger&quot;:&quot;scroll_percentage&quot;,&quot;scroll_percentage&quot;:&quot;70&quot;}" id="ck_form_options" /> <input type="hidden" name="id" value="403521" id="landing_page_id" /> <input type="hidden" name="ck_form_recaptcha" value="" id="ck_form_recaptcha" /> <div class="ck_errorArea"> <div id="ck_error_msg" style="display:none"> <p>There was an error submitting your subscription. Please try again.</p> </div> </div> <div class="ck_control_group ck_email_field_group"> <label class="ck_label" for="ck_emailField" style="display: none">Email Address</label> <input type="email" name="email" class="ck_email_address" id="ck_emailField" placeholder="Email Address" required="" /> </div> <div class="ck_control_group ck_captcha2_h_field_group ck-captcha2-h" style="position: absolute !important;left: -999em !important;"> <input type="text" name="captcha2_h" class="ck-captcha2-h" id="ck_captcha2_h" placeholder="We use this field to detect spam bots. If you fill this in, you will be marked as a spammer." /> </div> <label class="ck_checkbox" style="display:none;"> <input class="optIn ck_course_opted" name="course_opted" type="checkbox" id="optIn" checked="" /> <span class="ck_opt_in_prompt">I'd like to receive the free email course.</span> </label> <button class="subscribe_button ck_subscribe_button btn fields" id="ck_subscribe_button"> Subscribe </button> <span class="ck_guarantee"> No Spam. Ever. Unsubscribe at any time. </span> </form> </div> </div> </div> <style type="text/css">/* Layout */ .ck_form.ck_minimal { /* divider image */ background: #f9f9f9; font-family: 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; line-height: 1.5em; overflow: hidden; color: #202020; font-size: 16px; border: solid 1px #d1d1d1; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; clear: both; margin: 20px 0px; text-align: center; } .ck_form.ck_minimal h3.ck_form_title { text-align: center; margin: 0px 0px 10px; font-size: 28px; } .ck_form.ck_minimal h4 { text-align: center; font-family: 'Open Sans', Helvetica, Arial, sans-serif; text-transform: uppercase; font-size: 18px; font-weight: normal; padding-top: 0px; margin-top: 0px; } .ck_form.ck_minimal p { padding: 0px; } .ck_form, .ck_form * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .ck_form.ck_minimal .ck_form_fields { width: 100%; float: left; padding: 5%; } /* Form fields */ .ck_errorArea { display: none; /* temporary */ } #ck_success_msg { padding: 10px 10px 0px; border: solid 1px #ddd; background: #eee; } .ck_form.ck_minimal input[type="text"], .ck_form.ck_minimal input[type="email"] { font-size: 18px; padding: 10px 8px; width: 68%; border: 1px solid #d6d6d6; /* stroke */ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ background-color: #fff; /* layer fill content */ margin-bottom: 5px; height: auto; float: left; margin: 0px; margin-right: 2%; height: 42px; } .ck_form input[type="text"]:focus, .ck_form input[type="email"]:focus { outline: none; border-color: #aaa; } .ck_form.ck_minimal .ck_subscribe_button { width: 100%; color: #fff; margin: 0px; padding: 11px 0px; font-size: 18px; background: #0d6db8; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ cursor: pointer; border: none; text-shadow: none; width: 30%; float: left; height: 42px; } .ck_form.ck_minimal .ck_guarantee { color: #626262; font-size: 12px; text-align: center; padding: 15px 0px 0px; display: block; clear: both; } .ck_form .ck_powered_by { display: block; color: #aaa; font-size: 12px; } .ck_form .ck_powered_by:hover { display: block; color: #444; } .ck_converted_content { display: none; padding: 5%; background: #fff; } .ck_form.ck_minimal.width400 .ck_subscribe_button, .ck_form.ck_minimal.width400 input[type="email"] { width: 100%; float: none; margin-top: 5px; } .ck_slide_up, .ck_modal, .ck_slide_up .ck_minimal, .ck_modal .ck_minimal { min-width: 400px; } .page .ck_form.ck_minimal { margin: 50px auto; max-width: 600px; } /* v6 */ .ck_slide_up.ck_form_v6, .ck_modal.ck_form_v6, .ck_slide_up.ck_form_v6 .ck_minimal, .ck_modal.ck_form_v6 .ck_minimal { min-width: 0 !important; } @media all and (min-width: 801px) { .ck_modal.ck_form_v6 .ck_form.ck_minimal { margin-left: -300px; width: 600px; } } .ck_modal.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 20px; } .ck_slide_up.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 10px; } .ck_form_v6 #ck_success_msg { margin-top: 15px; padding: 0px 10px; } .ck_slide_up.ck_form_v6 .ck_minimal + .ck_close_link { top: 5px; } .ck_slide_up.ck_form_v6 .ck_minimal h3.ck_form_title { margin-top: 5px; } </style> Tue, 21 May 2019 06:00:00 +0000 http://bizz84.github.io/2019/05/21/wabs-practical-architecture-flutter-apps.html http://bizz84.github.io/2019/05/21/wabs-practical-architecture-flutter-apps.html Flutter, Android, iOS, Mobile App Development Flutter & Firebase: Reference Authentication Demo <iframe width="560" height="315" src="https://www.youtube.com/embed/-Za1MspEt5I" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe> <p>Today I’m sharing a new open source project, showing how to implement a full authentication flow with Flutter &amp; Firebase:</p> <ul> <li><a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a></li> </ul> <p>I created this as a follow up from my <a href="https://www.youtube.com/watch?v=u_Lyx8KJWpg&amp;list=PLNnAcB93JKV_NIGSneTazb9yMpILapEjo">YouTube series</a> on Flutter &amp; Firebase authentication.</p> <p>This is also a complement to my <a href="https://www.udemy.com/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=DART15&amp;password=codingwithflutter">Flutter &amp; Firebase Udemy course</a> (now available for early access - <a href="https://medium.com/coding-with-flutter/flutter-firebase-udemy-course-now-available-for-early-access-4073a29b92b0">read the full announcement here</a>).</p> <p>This project shows how to:</p> <ul> <li>use the various Firebase sign-in methods</li> <li>build a robust authentication flow</li> <li>use appropriate state management techniques to separate UI, logic and Firebase authentication code</li> <li>handle errors and present user-friendly error messages</li> <li>write production-ready code following best practices</li> </ul> <h2 id="preview">Preview</h2> <p><img src="/images/firebase-auth-screens.png" alt="" /></p> <p><strong>Google Sign-in</strong></p> <p><img src="/images/firebase-auth-google-sign-in.gif" alt="" /></p> <p><strong>Facebook Sign-in</strong></p> <p><img src="/images/firebase-auth-facebook-sign-in.gif" alt="" /></p> <p><strong>Email &amp; Password auth</strong></p> <p><img src="/images/firebase-auth-email-password-sign-in.gif" alt="" /></p> <p>Note that this is a <strong>reference implementation</strong>. You can use it to add Firebase authentication to your own projects.</p> <p>The code is very modular, so you can pick &amp; choose which parts you need.</p> <p>It is also meant to be a good testing ground for various state management techniques.</p> <h2 id="whats-next">What’s next</h2> <p>In follow-up articles I will show how to implement existing flows in this app with different state management techniques.</p> <p>This will lead to a detailed comparison of BLoCs, <a href="https://docs.flutter.io/flutter/foundation/ValueNotifier-class.html">ValueNotifier</a> and <a href="https://docs.flutter.io/flutter/foundation/ChangeNotifier-class.html">ChangeNotifier</a>, on a real-world codebase that you could use in production.</p> <p>I’ll keep it short for today. You can grab the source code here:</p> <ul> <li><a href="https://github.com/bizz84/firebase_auth_demo_flutter">Reference Authentication Flow with Flutter &amp; Firebase</a></li> </ul> <p>By the way, a lot of the techniques used in this project are explained in great detail, and implemented step-by-step in my Flutter &amp; Firebase Udemy course.</p> <p><strong>This course is now available for Early Access. Use this link to enroll (discount code included):</strong></p> <ul> <li><a href="https://www.udemy.com/flutter-firebase-build-a-complete-app-for-ios-android/?couponCode=DART15&amp;password=codingwithflutter">Flutter &amp; Firebase: Build a Complete App for iOS &amp; Android</a></li> </ul> <p>Happy Coding!</p> <script src="https://assets.convertkit.com/assets/CKJS4.js?v=21"></script> <div class="ck_form_container ck_inline" data-ck-version="7"> <div class="ck_form ck_minimal"> <div class="ck_form_fields"> <h3 class="ck_form_title">LEARN FLUTTER TODAY</h3> <div class="ck_description"> <p><img src="/img/coding-with-flutter-cheat-sheet-panel-noborder.png" style="display:block; margin-left: auto; margin-right: auto;" width="307" /></p> <p>Sign up for updates and get my free Flutter Layout Cheat Sheet.</p> </div> <div id="ck_success_msg" style="display:none;"> <p>Success! Now check your email to confirm your subscription.</p> </div> <!-- Form starts here --> <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/403521/subscribe" data-remote="true"> <input type="hidden" value="{&quot;form_style&quot;:&quot;minimal&quot;,&quot;converted_behavior&quot;:&quot;show&quot;,&quot;days_no_show&quot;:&quot;15&quot;,&quot;delay_seconds&quot;:&quot;10&quot;,&quot;display_devices&quot;:&quot;all&quot;,&quot;display_position&quot;:&quot;br&quot;,&quot;embed_style&quot;:&quot;inline&quot;,&quot;embed_trigger&quot;:&quot;scroll_percentage&quot;,&quot;scroll_percentage&quot;:&quot;70&quot;}" id="ck_form_options" /> <input type="hidden" name="id" value="403521" id="landing_page_id" /> <input type="hidden" name="ck_form_recaptcha" value="" id="ck_form_recaptcha" /> <div class="ck_errorArea"> <div id="ck_error_msg" style="display:none"> <p>There was an error submitting your subscription. Please try again.</p> </div> </div> <div class="ck_control_group ck_email_field_group"> <label class="ck_label" for="ck_emailField" style="display: none">Email Address</label> <input type="email" name="email" class="ck_email_address" id="ck_emailField" placeholder="Email Address" required="" /> </div> <div class="ck_control_group ck_captcha2_h_field_group ck-captcha2-h" style="position: absolute !important;left: -999em !important;"> <input type="text" name="captcha2_h" class="ck-captcha2-h" id="ck_captcha2_h" placeholder="We use this field to detect spam bots. If you fill this in, you will be marked as a spammer." /> </div> <label class="ck_checkbox" style="display:none;"> <input class="optIn ck_course_opted" name="course_opted" type="checkbox" id="optIn" checked="" /> <span class="ck_opt_in_prompt">I'd like to receive the free email course.</span> </label> <button class="subscribe_button ck_subscribe_button btn fields" id="ck_subscribe_button"> Subscribe </button> <span class="ck_guarantee"> No Spam. Ever. Unsubscribe at any time. </span> </form> </div> </div> </div> <style type="text/css">/* Layout */ .ck_form.ck_minimal { /* divider image */ background: #f9f9f9; font-family: 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; line-height: 1.5em; overflow: hidden; color: #202020; font-size: 16px; border: solid 1px #d1d1d1; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; clear: both; margin: 20px 0px; text-align: center; } .ck_form.ck_minimal h3.ck_form_title { text-align: center; margin: 0px 0px 10px; font-size: 28px; } .ck_form.ck_minimal h4 { text-align: center; font-family: 'Open Sans', Helvetica, Arial, sans-serif; text-transform: uppercase; font-size: 18px; font-weight: normal; padding-top: 0px; margin-top: 0px; } .ck_form.ck_minimal p { padding: 0px; } .ck_form, .ck_form * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .ck_form.ck_minimal .ck_form_fields { width: 100%; float: left; padding: 5%; } /* Form fields */ .ck_errorArea { display: none; /* temporary */ } #ck_success_msg { padding: 10px 10px 0px; border: solid 1px #ddd; background: #eee; } .ck_form.ck_minimal input[type="text"], .ck_form.ck_minimal input[type="email"] { font-size: 18px; padding: 10px 8px; width: 68%; border: 1px solid #d6d6d6; /* stroke */ -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ background-color: #fff; /* layer fill content */ margin-bottom: 5px; height: auto; float: left; margin: 0px; margin-right: 2%; height: 42px; } .ck_form input[type="text"]:focus, .ck_form input[type="email"]:focus { outline: none; border-color: #aaa; } .ck_form.ck_minimal .ck_subscribe_button { width: 100%; color: #fff; margin: 0px; padding: 11px 0px; font-size: 18px; background: #0d6db8; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; /* border radius */ cursor: pointer; border: none; text-shadow: none; width: 30%; float: left; height: 42px; } .ck_form.ck_minimal .ck_guarantee { color: #626262; font-size: 12px; text-align: center; padding: 15px 0px 0px; display: block; clear: both; } .ck_form .ck_powered_by { display: block; color: #aaa; font-size: 12px; } .ck_form .ck_powered_by:hover { display: block; color: #444; } .ck_converted_content { display: none; padding: 5%; background: #fff; } .ck_form.ck_minimal.width400 .ck_subscribe_button, .ck_form.ck_minimal.width400 input[type="email"] { width: 100%; float: none; margin-top: 5px; } .ck_slide_up, .ck_modal, .ck_slide_up .ck_minimal, .ck_modal .ck_minimal { min-width: 400px; } .page .ck_form.ck_minimal { margin: 50px auto; max-width: 600px; } /* v6 */ .ck_slide_up.ck_form_v6, .ck_modal.ck_form_v6, .ck_slide_up.ck_form_v6 .ck_minimal, .ck_modal.ck_form_v6 .ck_minimal { min-width: 0 !important; } @media all and (min-width: 801px) { .ck_modal.ck_form_v6 .ck_form.ck_minimal { margin-left: -300px; width: 600px; } } .ck_modal.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 20px; } .ck_slide_up.ck_form_v6 .ck_minimal .ck_subscribe_form { padding-top: 10px; } .ck_form_v6 #ck_success_msg { margin-top: 15px; padding: 0px 10px; } .ck_slide_up.ck_form_v6 .ck_minimal + .ck_close_link { top: 5px; } .ck_slide_up.ck_form_v6 .ck_minimal h3.ck_form_title { margin-top: 5px; } </style> Fri, 17 May 2019 04:00:18 +0000 http://bizz84.github.io/2019/05/17/flutter-firebase-reference-demo.html http://bizz84.github.io/2019/05/17/flutter-firebase-reference-demo.html Flutter, Android, iOS, Mobile App Development