<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abdur Rafay Saleem</title>
    <description>The latest articles on DEV Community by Abdur Rafay Saleem (@arafaysaleem).</description>
    <link>https://dev.to/arafaysaleem</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F886998%2F4f654d85-6b64-4bb4-a5d0-41075cc8c73f.jpeg</url>
      <title>DEV Community: Abdur Rafay Saleem</title>
      <link>https://dev.to/arafaysaleem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arafaysaleem"/>
    <language>en</language>
    <item>
      <title>Building a Robust Local Storage Service in Flutter</title>
      <dc:creator>Abdur Rafay Saleem</dc:creator>
      <pubDate>Mon, 23 Dec 2024 23:46:26 +0000</pubDate>
      <link>https://dev.to/arafaysaleem/building-a-robust-local-storage-service-in-flutter-2gkj</link>
      <guid>https://dev.to/arafaysaleem/building-a-robust-local-storage-service-in-flutter-2gkj</guid>
      <description>&lt;p&gt;Modern mobile applications often need to store various types of data locally - from user preferences to authentication tokens. While Flutter provides &lt;code&gt;SharedPreferences&lt;/code&gt; for basic storage and &lt;code&gt;FlutterSecureStorage&lt;/code&gt; for encrypted storage, managing these effectively in a large application requires careful architectural planning.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how to build a robust key-value storage system that separates concerns, provides type safety, and makes storage operations maintainable and secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two-Layer Architecture
&lt;/h2&gt;

&lt;p&gt;Our implementation uses a two-layer architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;KeyValueStorageBase&lt;/code&gt;: A low-level base class that directly interfaces with storage plugins&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KeyValueStorageService&lt;/code&gt;: A high-level service that provides typed, domain-specific storage operations&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why Two Layers?
&lt;/h3&gt;

&lt;p&gt;This separation serves several important purposes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separation of Concerns&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base class is more generic and handles raw storage operations&lt;/li&gt;
&lt;li&gt;Service class handles business logic and data transformation according to the app domain&lt;/li&gt;
&lt;li&gt;Clear boundary between storage mechanism and business logic&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Single Responsibility&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base class: How to store and retrieve data&lt;/li&gt;
&lt;li&gt;Service class: What to store and when as well as managing the keys.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dependency Isolation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only the base class knows about &lt;code&gt;SharedPreferences&lt;/code&gt; and &lt;code&gt;FlutterSecureStorage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Application code only interacts with the service layer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Base Layer: KeyValueStorageBase
&lt;/h2&gt;

&lt;p&gt;Let's look at our base class &lt;code&gt;key_value_storage_base.dart&lt;/code&gt; implementation:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Initialization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;WidgetsBinding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ensureInitialized&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;// For preparing the key-value mem cache&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;KeyValueStorageBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;init()&lt;/code&gt; method is a crucial part of our base layer design. By initializing both storage systems (&lt;code&gt;SharedPreferences&lt;/code&gt; and &lt;code&gt;FlutterSecureStorage&lt;/code&gt;) at app startup, we ensure that the storage instances are ready before any part of the app tries to access them. It allows us to perform synchronous reads from &lt;code&gt;SharedPreferences&lt;/code&gt; throughout our app's lifecycle.&lt;/p&gt;

&lt;p&gt;Without this initialization pattern, we'd need to handle async operations for every read operation, which would significantly complicate our storage API and force unnecessary complexity onto the rest of our application code. This "initialize once, use synchronously" pattern is particularly valuable for accessing critical data like user authentication state or app configuration during app startup.&lt;/p&gt;
&lt;h3&gt;
  
  
  Key Features of the Base Layer
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unified Storage Interface&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;   &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;getCommon&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getEncrypted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCommon&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setEncrypted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Type-Safe Operations&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;   &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;getCommon&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sharedPrefs&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sharedPrefs&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;getStringList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sharedPrefs&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;getInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sharedPrefs&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;getBool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sharedPrefs&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;getDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sharedPrefs&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
     &lt;span class="p"&gt;};&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;   &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_secureStorage&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;on&lt;/span&gt; &lt;span class="n"&gt;PlatformException&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;appLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$ex&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  The Service Layer: KeyValueStorageService
&lt;/h2&gt;

&lt;p&gt;The service layer provides domain-specific storage operations:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Features of the Service Layer
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Domain-Specific Operations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Methods map directly to business needs&lt;/li&gt;
&lt;li&gt;Handles serialization/deserialization&lt;/li&gt;
&lt;li&gt;Provides type safety at the domain level&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Centralized Key Management&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;   &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_authTokenKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'authToken'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_authUserKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'authUserKey'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Intelligent Storage Decisions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sensitive data uses encrypted storage&lt;/li&gt;
&lt;li&gt;Regular data uses shared preferences&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bulk Operations&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;   &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;clearUserData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;_keyValueStorage&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;removeCommon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_authUserKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;removeCommon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_firstMapLoadKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;removeEncrypted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_authTokenKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benefits of This Architecture
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compile-time type checking for stored values&lt;/li&gt;
&lt;li&gt;Domain models are properly serialized/deserialized&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear separation between secure and non-secure storage&lt;/li&gt;
&lt;li&gt;Encrypted storage for sensitive data&lt;/li&gt;
&lt;li&gt;Centralized security decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single source of truth for storage operations&lt;/li&gt;
&lt;li&gt;Easy to add new storage operations&lt;/li&gt;
&lt;li&gt;Consistent error handling&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Testability&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base class can be mocked for testing&lt;/li&gt;
&lt;li&gt;Service layer can be tested independently&lt;/li&gt;
&lt;li&gt;Clear boundaries for unit tests&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Async operations where needed&lt;/li&gt;
&lt;li&gt;Synchronous operations where possible&lt;/li&gt;
&lt;li&gt;Background execution for non-critical writes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Usage in Application Code
&lt;/h2&gt;

&lt;p&gt;The service is easily accessible through a Riverpod provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;keyValueStorageServiceProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;KeyValueStorageService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// In application code&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyValueStorageServiceProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAuthUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, you don't have to use &lt;code&gt;riverpod&lt;/code&gt; and you can easily change it to some other DI service or simply make it a singleton pattern as well.&lt;/p&gt;

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

&lt;p&gt;This two-layer architecture provides a robust foundation for key-value storage in Flutter applications. By following this pattern you  create a clean, maintainable, and secure storage system that can grow with your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credit
&lt;/h2&gt;

&lt;p&gt;If you like it, please also take a look at the &lt;a href="https://dev.to/arafaysaleem/flutter-analytics-service-using-facade-design-pattern-3877"&gt;Analytics Service&lt;/a&gt; article I wrote to see it in action.&lt;/p&gt;

&lt;p&gt;Finally, please keep following me as I will be posting about many more advanced stuff like &lt;code&gt;PermissionService&lt;/code&gt;, &lt;code&gt;LocationService&lt;/code&gt;, and &lt;code&gt;NotificationService&lt;/code&gt; etc.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Flutter Analytics Service using Facade Design Pattern</title>
      <dc:creator>Abdur Rafay Saleem</dc:creator>
      <pubDate>Mon, 23 Dec 2024 19:19:29 +0000</pubDate>
      <link>https://dev.to/arafaysaleem/flutter-analytics-service-using-facade-design-pattern-3877</link>
      <guid>https://dev.to/arafaysaleem/flutter-analytics-service-using-facade-design-pattern-3877</guid>
      <description>&lt;p&gt;&lt;em&gt;Previous Article: &lt;a href="https://dev.to/arafaysaleem/posthog-analytics-flutter-tutorial-20k5"&gt;Part 3: Implementing PostHog Analytics in Flutter&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this final part, we'll create a unified analytics service that orchestrates multiple analytics clients using the Facade design pattern, demonstrating how to manage complex subsystems through a simplified interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Facade Pattern
&lt;/h2&gt;

&lt;p&gt;The Facade pattern provides a unified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use. In our analytics architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The subsystem consists of multiple analytics clients (Logger, Server, PostHog)&lt;/li&gt;
&lt;li&gt;Each client has its own complex implementation and dependencies&lt;/li&gt;
&lt;li&gt;The facade (&lt;code&gt;AnalyticsService&lt;/code&gt;) provides a single, simplified interface to use all clients&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Use a Facade?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Interface&lt;/strong&gt;: Clients of the analytics system don't need to know about the complexity of multiple analytics providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decoupling&lt;/strong&gt;: The application code is decoupled from specific analytics implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralized Control&lt;/strong&gt;: Analytics configuration and dispatching logic is centralized in one place&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier Testing&lt;/strong&gt;: The facade can be mocked or replaced easily for testing&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Analytics Facade Implementation
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;AnalyticsService&lt;/code&gt; implements the Facade pattern while also adhering to the &lt;code&gt;AnalyticsClientBase&lt;/code&gt; contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnalyticsService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;AnalyticsClientBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// List of analytics clients managed by the facade&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_clients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isDebug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;LoggerAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isStaging&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ServerAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="c1"&gt;// insert your api service or dio/http etc.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isProd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;PosthogAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// Singleton instance&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AnalyticsService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AnalyticsService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Local storage for analytics preferences&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_collectUsageStatisticsKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'collectUsageStatisticsKey'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_keyValueStorage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KeyValueStorageBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It has the following characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implements the base contract to ensure the unified service contains all methods that the clients have.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;_clients&lt;/code&gt; list contains all of the client implementations we wanna use. Analytics will be sent to all these for their own handling.&lt;/li&gt;
&lt;li&gt;We can add/remove the clients based on our debug/dev/prod logic.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Managing Analytics State
&lt;/h3&gt;

&lt;p&gt;The facade handles analytics state management using &lt;strong&gt;KeyValueStorageService&lt;/strong&gt;, which is just a wrapper around &lt;em&gt;SharedPreferences&lt;/em&gt;. You can read about it &lt;a href="https://dev.to/arafaysaleem/building-a-robust-local-storage-service-in-flutter-2gkj"&gt;here in a seperate guide&lt;/a&gt;. Here's how it handles collection:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;_setCollectUsageStatistics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_keyValueStorage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCommon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_collectUsageStatisticsKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;collectUsageStatistics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_keyValueStorage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCommon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_collectUsageStatisticsKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@override&lt;/span&gt;
&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toggleAnalyticsCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;_setCollectUsageStatistics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toggleAnalyticsCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  The Dispatch Method
&lt;/h3&gt;

&lt;p&gt;The key to our facade is the &lt;code&gt;_dispatch&lt;/code&gt; method which is used by every overriden method in the service:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnalyticsClientBase&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;worksList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_clients&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;worksList&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This method:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Takes a function that operates on an analytics client&lt;/li&gt;
&lt;li&gt;Applies that function to &lt;strong&gt;all&lt;/strong&gt; registered clients&lt;/li&gt;
&lt;li&gt;Runs all operations concurrently&lt;/li&gt;
&lt;li&gt;Waits for all operations to complete&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Using the Facade
&lt;/h3&gt;

&lt;p&gt;The facade makes analytics calls simple and consistent:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In application code&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onUserAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;AnalyticsService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trackButtonPressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;'submit_button'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'screen'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'/checkout'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;

&lt;p&gt;Here is the full code for the service:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits of the Facade Pattern
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single Point of Entry&lt;/strong&gt;: Application code only needs to interact with one service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent Execution&lt;/strong&gt;: Analytics events are dispatched to all clients simultaneously for better performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation Hiding&lt;/strong&gt;: Clients of the facade don't need to know about the dispatch mechanism. Each client handles its own implementation details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Isolation&lt;/strong&gt;: Failures in one client don't affect others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility&lt;/strong&gt;: New analytics providers can be added without changing existing code based on build configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Testing&lt;/strong&gt;: The facade can be easily mocked for testing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Advanced Facade Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adding New Analytics Providers
&lt;/h3&gt;

&lt;p&gt;The facade pattern makes it easy to add new analytics providers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_clients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="c1"&gt;// Add new clients here&lt;/span&gt;
  &lt;span class="n"&gt;FirebaseAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;MixpanelAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conditional Client Activation
&lt;/h3&gt;

&lt;p&gt;The facade can conditionally activate clients based on configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AnalyticsClientBase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_getClients&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isDebug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;LoggerAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isProd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;PosthogAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isStaging&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ServerAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;p&gt;The facade can implement centralized error handling. I haven't done this for the sole reason that I don't care about catching errors on logs. If you want to, here's how you do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnalyticsClientBase&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;worksList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_clients&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Handle individual client errors&lt;/span&gt;
        &lt;span class="n"&gt;appLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Analytics client error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;worksList&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Handle catastrophic errors&lt;/span&gt;
    &lt;span class="n"&gt;appLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Analytics dispatch error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Event Batching
&lt;/h3&gt;

&lt;p&gt;The facade could implement event batching for better performance. However, &lt;strong&gt;Posthog&lt;/strong&gt; does batch dispatching internally, so I avoided re-inventing the wheel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnalyticsService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;AnalyticsClientBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;_eventQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AnalyticsEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;[];&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_batchDispatch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_eventQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[..&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_eventQueue&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;_eventQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_dispatch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trackBatchEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Our analytics architecture demonstrates several key software design principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to simplify complex subsystems&lt;/li&gt;
&lt;li&gt;Abstract base classes for contract enforcement&lt;/li&gt;
&lt;li&gt;Interface segregation for clean client implementations&lt;/li&gt;
&lt;li&gt;Maintain flexibility while providing a consistent API&lt;/li&gt;
&lt;li&gt;Handle cross-cutting concerns like error handling and performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This robust foundation allows for easy addition of new analytics providers while maintaining clean, maintainable code. The facade pattern proves particularly valuable in analytics implementations where multiple providers need to coexist without increasing complexity for the rest of the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;If you like it, please keep following me as I will be posting about many more advanced stuff like &lt;strong&gt;PermissionService&lt;/strong&gt;, &lt;strong&gt;LocationService&lt;/strong&gt;, and &lt;strong&gt;NotificationService&lt;/strong&gt; etc.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>tutorial</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Implementing Posthog Analytics in Flutter Tutorial</title>
      <dc:creator>Abdur Rafay Saleem</dc:creator>
      <pubDate>Mon, 23 Dec 2024 09:52:32 +0000</pubDate>
      <link>https://dev.to/arafaysaleem/posthog-analytics-flutter-tutorial-20k5</link>
      <guid>https://dev.to/arafaysaleem/posthog-analytics-flutter-tutorial-20k5</guid>
      <description>&lt;p&gt;&lt;em&gt;Previous Article: &lt;a href="https://dev.to/arafaysaleem/implementing-logger-and-server-analytics-in-flutter-5389"&gt;Part 2: Implementing Logger and Server Analytics&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;PostHog is a powerful open-source analytics platform that provides comprehensive user behavior tracking. In this article, we'll implement PostHog analytics in Flutter using our analytics architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  PostHog Setup
&lt;/h2&gt;

&lt;p&gt;You need to follow the following series of steps in order to prepare posthog for flutter.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. PostHog account creation
&lt;/h3&gt;

&lt;p&gt;Visit &lt;a href="https://posthog.com/" rel="noopener noreferrer"&gt;Posthog Website&lt;/a&gt; and signup to create your account using either email, google, or github etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Project setup
&lt;/h3&gt;

&lt;p&gt;Once you open you new account, you will see a default project already opened. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Click&lt;/strong&gt; the dropdown arrow next to its name &amp;gt; &lt;strong&gt;Click&lt;/strong&gt; the gear icon to open settings.&lt;/li&gt;
&lt;li&gt;Set a custom name for your project.&lt;/li&gt;
&lt;li&gt;Scroll down and copy the &lt;code&gt;API_KEY&lt;/code&gt; value&lt;/li&gt;
&lt;li&gt;Also note the if the region is &lt;strong&gt;US&lt;/strong&gt; or &lt;strong&gt;EU&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  3. Flutter package setup + configuration
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Add the package &lt;a href="https://pub.dev/packages/posthog_flutter" rel="noopener noreferrer"&gt;posthog_flutter&lt;/a&gt; to your &lt;code&gt;pubspec.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the following line to your &lt;code&gt;AndroidManifest.xml&lt;/code&gt; inside &lt;code&gt;&amp;lt;application&amp;gt;&lt;/code&gt; tags
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;application&amp;gt;&lt;/span&gt;
....
    &lt;span class="nt"&gt;&amp;lt;meta-data&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"com.posthog.posthog.AUTO_INIT"&lt;/span&gt; &lt;span class="na"&gt;android:value=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The reason we are disabling AUTO_INIT is because we will provide the config inside flutter code. That allows us to apply dynamic logic for flavors.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do the same for ios inside &lt;code&gt;Info.plist&lt;/code&gt; file:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plist"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;d&lt;/span&gt;&lt;span class="err"&gt;i&lt;/span&gt;&lt;span class="mh"&gt;c&lt;/span&gt;&lt;span class="err"&gt;t&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="l"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;k&lt;/span&gt;&lt;span class="mh"&gt;e&lt;/span&gt;&lt;span class="err"&gt;y&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="l"&gt;com.posthog.posthog.AUTO_INIT&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/k&lt;/span&gt;&lt;span class="mh"&gt;e&lt;/span&gt;&lt;span class="err"&gt;y&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;fa&lt;/span&gt;&lt;span class="err"&gt;ls&lt;/span&gt;&lt;span class="mh"&gt;e&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="mh"&gt;d&lt;/span&gt;&lt;span class="err"&gt;i&lt;/span&gt;&lt;span class="mh"&gt;c&lt;/span&gt;&lt;span class="err"&gt;t&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;In &lt;code&gt;main.dart&lt;/code&gt; or whatever your entry file is, add the following code:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:posthog_flutter/posthog_flutter.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;....&lt;/span&gt;
&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;WidgetsBinding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ensureInitialized&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PostHogConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;captureApplicationLifecycleEvents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// Allows tracking backgrounded, foregrounded etc. events&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://us.i.posthog.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// or for EU Region 'https://eu.i.posthog.com'&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Posthog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;

&lt;h2&gt;
  
  
  PostHog Analytics Implementation
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;PosthogAnalyticsClient&lt;/code&gt; implements the &lt;code&gt;AnalyticsClientBase&lt;/code&gt; contract while utilizing PostHog's specific APIs:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;User identification&lt;/li&gt;
&lt;li&gt;Event tracking&lt;/li&gt;
&lt;li&gt;Screen tracking&lt;/li&gt;
&lt;li&gt;Custom properties&lt;/li&gt;
&lt;li&gt;User properties&lt;/li&gt;
&lt;li&gt;Session management&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Attaching user to analytic events
&lt;/h3&gt;

&lt;p&gt;When you originally track events, they are done so anonymously. Posthog provides methods to attach events to users and maintain nice sessions. To attach events to logged in user, you can use the identify() method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Posthog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="nl"&gt;userId:&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nl"&gt;userProperties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// optional&lt;/span&gt;
     &lt;span class="s"&gt;'email'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="s"&gt;'role'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, all tracked events will automatically be attached to your logged in user, based on their &lt;code&gt;userId&lt;/code&gt;. If you want to logout user then make sure to call &lt;code&gt;reset()&lt;/code&gt; so you can identify the next logged in user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Posthog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tracking Screen Views
&lt;/h3&gt;

&lt;p&gt;If you want to track screen changes use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;_posthog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;screenName:&lt;/span&gt; &lt;span class="n"&gt;routeName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'action'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom Events
&lt;/h3&gt;

&lt;p&gt;Or for custom events use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;_posthog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;eventName:&lt;/span&gt; &lt;span class="s"&gt;'button_pressed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;'button_name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;buttonName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Using posthog this way allows maximum flexibility and a nice wrapper around the original methods. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/arafaysaleem/flutter-analytics-service-using-facade-design-pattern-3877"&gt;Part 4: Creating a Unified Analytics Service Facade&lt;/a&gt;, we'll bring everything together with a facade service that manages multiple analytics clients seamlessly.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>tutorial</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Implementing Logger and Server Analytics in Flutter</title>
      <dc:creator>Abdur Rafay Saleem</dc:creator>
      <pubDate>Mon, 23 Dec 2024 09:06:47 +0000</pubDate>
      <link>https://dev.to/arafaysaleem/implementing-logger-and-server-analytics-in-flutter-5389</link>
      <guid>https://dev.to/arafaysaleem/implementing-logger-and-server-analytics-in-flutter-5389</guid>
      <description>&lt;p&gt;&lt;em&gt;Previous Article: &lt;a href="https://dev.to/arafaysaleem/flutter-analytics-like-production-93k"&gt;Part 1: Building a Robust Analytics Architecture in Flutter&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that we have our analytics foundation in place, let's implement two concrete analytics clients: a &lt;strong&gt;logger client&lt;/strong&gt; for development and a &lt;strong&gt;server client&lt;/strong&gt; for custom backend analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Logger Service
&lt;/h2&gt;

&lt;p&gt;First we will setup our logging service using the &lt;a href="https://pub.dev/packages/logger" rel="noopener noreferrer"&gt;logger&lt;/a&gt; package. Here is the code for a service wrapper around this package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:clock/clock.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:logger/logger.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;appLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LogService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'[DEBUG]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;level:&lt;/span&gt; &lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LogService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;LogService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;LogService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;printer:&lt;/span&gt; &lt;span class="n"&gt;_SimpleLogPrinter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;level:&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;StackTrace&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;stackTrace&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;stackTrace:&lt;/span&gt; &lt;span class="n"&gt;stackTrace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;wtf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_SimpleLogPrinter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;PrettyPrinter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;_SimpleLogPrinter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LogEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PrettyPrinter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultLevelColors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;emoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PrettyPrinter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultLevelEmojis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${date.year}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;${date.month}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;${date.day}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;' &lt;/span&gt;&lt;span class="si"&gt;${date.hour}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${date.minute}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${date.second}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;' &lt;/span&gt;&lt;span class="si"&gt;$message&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$emoji&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;$className&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;$msg&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This wrapper allow nicely formatted messages to be logged to the console and also provides utility methods to log events of different priorities.&lt;/p&gt;

&lt;p&gt;Finally there is a global &lt;code&gt;appLogger&lt;/code&gt; that we use for debugging purposes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Logger Analytics Client
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;LoggerAnalyticsClient&lt;/code&gt; is a development-focused implementation that logs all analytics events to the console:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggerAnalyticsClient&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;AnalyticsClientBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;LoggerAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;trackEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;eventName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;eventData&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;appLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'trackEvent(&lt;/span&gt;&lt;span class="si"&gt;$eventName&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="si"&gt;$eventData&lt;/span&gt;&lt;span class="s"&gt;)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other implementations&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This client has the following characteristics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement the &lt;code&gt;AnalyticsClientBase&lt;/code&gt; class to be injectable later on.&lt;/li&gt;
&lt;li&gt;Override all the contract methods to provide own logic for logging using the &lt;code&gt;appLogger&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This implementation serves several purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development debugging and testing&lt;/li&gt;
&lt;li&gt;Documentation of analytics events in logs&lt;/li&gt;
&lt;li&gt;Verification of analytics integration during development&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Benefits of Logger Analytics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Immediate feedback during development&lt;/li&gt;
&lt;li&gt;No external service dependencies for testing&lt;/li&gt;
&lt;li&gt;Clear visibility of all tracked events&lt;/li&gt;
&lt;li&gt;Easy debugging of analytics implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full code for the service is found here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Server Analytics Client
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ServerAnalyticsClient&lt;/code&gt; sends analytics data to your custom backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServerAnalyticsClient&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;AnalyticsClientBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;ApiService&lt;/span&gt; &lt;span class="n"&gt;_apiService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ServerAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_apiService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;trackEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;eventName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;eventData&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_apiService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;endpoint:&lt;/span&gt; &lt;span class="s"&gt;'/analytics'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;data:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;'event'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;eventName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'data'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;eventData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nl"&gt;converter:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other implementations&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This client has the same characteristics as above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement the &lt;code&gt;AnalyticsClientBase&lt;/code&gt; class to be injectable later on.&lt;/li&gt;
&lt;li&gt;Override all the contract methods to provide own logic for sending analytics to the backend using your own api service. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It uses my custom implementation of &lt;code&gt;ApiService&lt;/code&gt; that I have explained in detail in my &lt;a href="https://medium.com/flutter-app-development/advanced-generic-network-layer-in-flutter-using-dio-2022-2f362e22e6c9" rel="noopener noreferrer"&gt;Clean Flutter Networking Architecture&lt;/a&gt; series. Or you can customize it to use either the Dio or http package for simplicity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Server Analytics Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Custom backend integration&lt;/li&gt;
&lt;li&gt;Full control over analytics data&lt;/li&gt;
&lt;li&gt;Consistent event format&lt;/li&gt;
&lt;li&gt;Error handling and response validation&lt;/li&gt;
&lt;li&gt;Dependency injection for API service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the entire code for the client:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  Design Pattern Benefits
&lt;/h3&gt;

&lt;p&gt;Both implementations demonstrate the power of our base class approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Interface&lt;/strong&gt;: Both clients implement the same methods, ensuring consistent usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation Freedom&lt;/strong&gt;: Each client handles events in its own way while maintaining the contract&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single Responsibility&lt;/strong&gt;: Each client focuses on its specific analytics target&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Testing&lt;/strong&gt;: Mock implementations can be created for testing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Closed Principle&lt;/strong&gt;: New services can be added by simple implementation of the interface, without modifying existing code.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/arafaysaleem/posthog-analytics-flutter-tutorial-20k5"&gt;Part 3: Implementing PostHog Analytics in Flutter&lt;/a&gt;, we'll integrate PostHog, a popular open-source analytics platform. We'll cover PostHog setup, configuration, and implementation details.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>tutorial</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Flutter Analytics Like Production</title>
      <dc:creator>Abdur Rafay Saleem</dc:creator>
      <pubDate>Mon, 23 Dec 2024 08:34:02 +0000</pubDate>
      <link>https://dev.to/arafaysaleem/flutter-analytics-like-production-93k</link>
      <guid>https://dev.to/arafaysaleem/flutter-analytics-like-production-93k</guid>
      <description>&lt;h2&gt;
  
  
  Part 1: Building a Robust Analytics Service in Flutter
&lt;/h2&gt;

&lt;p&gt;Analytics is a crucial part of any production application, helping teams understand user behavior, track important metrics, and make data-driven decisions. However, implementing analytics properly requires careful architectural planning to ensure maintainability, extensibility, and clean separation of concerns.&lt;/p&gt;

&lt;p&gt;There are several tutorials out there showing how to setup analytics in flutter but lets be honest, they are all just a mess of random buggy functions and lead to spagetti code. Moreoever, they are so strongly focused on one platform that it leads to more painful refactoring down the line for adding more services. In reality, such tutorials are of no great value while making production quality apps.&lt;/p&gt;

&lt;p&gt;In this series, we'll build a robust analytics system for Flutter applications that can handle multiple analytics providers while maintaining clean, maintainable code. This is how it is done by professionals in the industry and how you should do it too. &lt;/p&gt;

&lt;p&gt;We'll start by establishing a solid foundation with a base analytics client.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Analytics Contract
&lt;/h2&gt;

&lt;p&gt;At the heart of our analytics architecture is the &lt;code&gt;AnalyticsClientBase&lt;/code&gt; abstract class. This class serves as a contract that all analytics implementations must follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnalyticsClientBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toggleAnalyticsCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;identifyUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resetUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other methods&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This abstract class defines a comprehensive set of tracking methods that cover common analytics scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User identification and session management&lt;/li&gt;
&lt;li&gt;Screen views and navigation tracking&lt;/li&gt;
&lt;li&gt;UI interaction events (button presses, dialogs, bottom sheets)&lt;/li&gt;
&lt;li&gt;App lifecycle events (foreground/background)&lt;/li&gt;
&lt;li&gt;Permission tracking&lt;/li&gt;
&lt;li&gt;Custom events with additional data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the full code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Use an Abstract Base Class?
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;AnalyticsClientBase&lt;/code&gt; serves several important purposes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Contract Enforcement&lt;/strong&gt;: Any new analytics implementation must implement all required tracking methods, ensuring consistency across different providers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety&lt;/strong&gt;: The abstract class provides compile-time type checking and clear method signatures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Inversion&lt;/strong&gt;: Our application code depends on the abstract interface rather than concrete implementations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Integration&lt;/strong&gt;: New analytics providers can be added by simply implementing the base class, without changing existing code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, if we want to track a button press, our UI code only needs to know about the abstract interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onButtonPressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnalyticsClientBase&lt;/span&gt; &lt;span class="n"&gt;aClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;aClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trackButtonPressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'submit_button'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'screen'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'checkout'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code remains unchanged regardless of whether we're using Firebase Analytics, PostHog, or any other analytics provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Methods Explained
&lt;/h3&gt;

&lt;p&gt;Let's look at some key methods in our analytics contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;identifyUser&lt;/code&gt;: Associates analytics events with a specific user, typically called after login&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resetUser&lt;/code&gt;: Clears user association, usually called during logout&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;trackScreenView&lt;/code&gt;: Monitors navigation patterns with additional context about the action&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;trackButtonPressed&lt;/code&gt;: Used to log button interactions by the user. We can pass in a button name and other things as data like screen, enable/disable etc. This way we add a lot of insightful context to the event.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;trackEvent&lt;/code&gt;: A flexible method for custom events with optional key-value data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Similarly, we have many more methods for some common tasks. You can adjust these as per your own requirements or add more if you want to track more things like &lt;code&gt;scroll_event&lt;/code&gt;, &lt;code&gt;swipe_event&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;Each method is designed to be future-proof and flexible enough to work with various analytics providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/arafaysaleem/implementing-logger-and-server-analytics-in-flutter-5389"&gt;Part 2: Implementing Logger and Server Analytics&lt;/a&gt;, we'll create our first two concrete implementations of this contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A debug logger client for development&lt;/li&gt;
&lt;li&gt;A server-side analytics client for custom backend tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll see how these implementations leverage our base class while providing their own unique functionality.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>tutorial</category>
      <category>mobile</category>
    </item>
    <item>
      <title>CustomFormField - Custom Widgets Series</title>
      <dc:creator>Abdur Rafay Saleem</dc:creator>
      <pubDate>Sun, 22 Dec 2024 22:52:56 +0000</pubDate>
      <link>https://dev.to/arafaysaleem/customformfield-custom-widgets-series-8ep</link>
      <guid>https://dev.to/arafaysaleem/customformfield-custom-widgets-series-8ep</guid>
      <description>&lt;p&gt;Welcome to another article for my &lt;strong&gt;Custom Widgets Series&lt;/strong&gt;. In this series I share some tips and code about creating reusable and advanced flutter widgets. These widgets are used by me and many professionals out there in production level flutter apps. Now they are available to you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Usage&lt;/li&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;li&gt;Credits&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Are you tired of dealing with the complexities of form fields in Flutter? Do you wish there was a simpler way to handle form validation, error styling, and value changes without writing tons of boilerplate code or messing with the Form API? I designed my own custom widget to make form handling in Flutter a breeze.&lt;/p&gt;

&lt;p&gt;I recently shared some screenshots of forms from my &lt;a href="https://play.google.com/store/apps/details?id=com.inceptrafay.family_expense_tracker.prod" rel="noopener noreferrer"&gt;Famony&lt;/a&gt; app on X(twitter).&lt;/p&gt;

&lt;p&gt;These forms contained validations for different field types like dropdowns, textfields, date picker, gender cards etc. You can see in the image below:&lt;/p&gt;

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

&lt;p&gt;Many people inboxed me asking how I was able to do that since not every widget has validations and it gets very messy to sync them all.&lt;/p&gt;

&lt;h2&gt;
  
  
  CustomFormField ✨
&lt;/h2&gt;

&lt;p&gt;Introducing the CustomFormField widget, wrapper around &lt;code&gt;FormField&lt;/code&gt; widget that takes in &lt;code&gt;validation&lt;/code&gt;, &lt;code&gt;onSaved&lt;/code&gt;, &lt;code&gt;onChanged&lt;/code&gt; callbacks etc.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;CustomFormField&lt;/code&gt;, you can easily manage form validation, error display, and value changes with minimal effort. No more struggling with the default FormField API to achieve basic functionality. It provides a clean and intuitive way to handle form fields, allowing you to focus on building great user experiences.&lt;/p&gt;

&lt;p&gt;It has built-in support for validation errors which you can style as you want. Just wrap any child to add validation without worrying about form reactive logic. You can see the it in the example below.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  CustomValueListener
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;CustomFormField&lt;/code&gt; needs a way to keep the form in sync and allow updating it from anywhere using a controller. Since the field can be of dynamic nature, the most suitable option for a controller was a &lt;code&gt;ValueNotifier&amp;lt;T&amp;gt;&lt;/code&gt;, where &lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt; is the type you pass to your &lt;code&gt;CustomFormField&amp;lt;T&amp;gt;&lt;/code&gt;. In order to listen to this custom controller we needed a custom listener widget as well.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;CustomValueListener&lt;/code&gt; widget is essential for efficiently managing state changes in the &lt;code&gt;CustomFormField&lt;/code&gt;. It listens to a &lt;code&gt;ValueNotifier&lt;/code&gt; and triggers updates whenever the value changes, ensuring that the form field's state is kept in sync. This separation of concerns simplifies the code, making it cleaner and more maintainable. Additionally, it handles the lifecycle of the listener, adding it during initialization and removing it upon disposal, which prevents memory leaks and ensures optimal performance. This approach allows for a more responsive and dynamic form handling experience in Flutter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Here is the crux of it all, the code for this amazing widget. I decided to place it at the end to avoid confusion, because it was important to see the usage and benefits before the implementation. I have included it as gist so I can update it in the future, if needed.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;If you like it, please keep following me as I have around 40 such widgets and I will post many more of these.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>tutorial</category>
      <category>mobile</category>
    </item>
    <item>
      <title>CustomText - Custom Widget Series</title>
      <dc:creator>Abdur Rafay Saleem</dc:creator>
      <pubDate>Sun, 22 Dec 2024 05:39:55 +0000</pubDate>
      <link>https://dev.to/arafaysaleem/customtext-custom-widget-series-42l4</link>
      <guid>https://dev.to/arafaysaleem/customtext-custom-widget-series-42l4</guid>
      <description>&lt;p&gt;Welcome to another article for my &lt;strong&gt;Custom Widgets Series&lt;/strong&gt;. In this series I share some tips and code about creating reusable and advanced flutter widgets. These widgets are used by me and many professionals out there in production level flutter apps. Now they are available to you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Usage&lt;/li&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;li&gt;Credits&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Are you guys tired of writing down the &lt;code&gt;Text&lt;/code&gt; widget in flutter with tedious amount of boilerplate for even the simplest of styling? Do you wish you didn't have to touch the &lt;code&gt;TextStyle&lt;/code&gt; for just changing one or two fields? Now there is a way to quickly create fixed style texts, with much simpler properties.&lt;/p&gt;

&lt;p&gt;Moreover, I am sure you have struggled with the &lt;code&gt;RichText&lt;/code&gt; API to just print simple multi styled text or make a certain link clickable without affecting the rest of the para. This widget introduces simple and intuitive ways to make that happen. You won't ever need to touch &lt;code&gt;RichText&lt;/code&gt; again.&lt;/p&gt;

&lt;h2&gt;
  
  
  CustomText ✨
&lt;/h2&gt;

&lt;p&gt;A wrapper around Text widget to directly pass in frequent properties like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;color&lt;/li&gt;
&lt;li&gt;font size&lt;/li&gt;
&lt;li&gt;font weight&lt;/li&gt;
&lt;li&gt;max lines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;etc. without creating seperate TextStyle. You can see the it in the example below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;CustomText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;'FindingJobsNearYou'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;letterSpacing:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onPrimary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Style Factories
&lt;/h3&gt;

&lt;p&gt;Moreover, it has factories for different styles like &lt;code&gt;.body()&lt;/code&gt;, &lt;code&gt;.subtitle()&lt;/code&gt;, and &lt;code&gt;.title()&lt;/code&gt;. They have certain font sizes, weights and colors predefined according to my choice so I can use them in a plug n play fashion.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;crossAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;CrossAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Title&lt;/span&gt;
    &lt;span class="n"&gt;CustomText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="n"&gt;Insets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gapH10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Message&lt;/span&gt;
    &lt;span class="n"&gt;CustomText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;maxLines:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="n"&gt;Insets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gapH10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Time&lt;/span&gt;
    &lt;span class="n"&gt;CustomText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onSurface&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;alpha:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Rich text
&lt;/h3&gt;

&lt;p&gt;Then comes in the heavy lifting factory &lt;code&gt;.rich()&lt;/code&gt; for the annoyingly complex RichText.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdorbghdrlemxa9yvvshy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdorbghdrlemxa9yvvshy.png" alt="An image of custom rich text widget in flutter" width="680" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See how simple the API is? All you have to do is use &lt;code&gt;.rich()&lt;/code&gt; factory. You no longer need to fight between &lt;strong&gt;Text&lt;/strong&gt; and &lt;strong&gt;TextSpan&lt;/strong&gt;. Just pass in instance of another &lt;strong&gt;CustomText&lt;/strong&gt; as &lt;code&gt;textWidget&lt;/code&gt;. Also, notice the &lt;strong&gt;Insets.gap&lt;/strong&gt; widget? It is a simple &lt;strong&gt;SizedBox&lt;/strong&gt;. You can pass any widget in between the text without worrying about &lt;strong&gt;WidgetSpan&lt;/strong&gt;. And just like that you can create complex text widgets.&lt;/p&gt;
&lt;h4&gt;
  
  
  Links and clickable text
&lt;/h4&gt;

&lt;p&gt;Very often you want to make a text clickable with some custom callback. You can wrap it with &lt;strong&gt;InkWell&lt;/strong&gt; or similar widgets but it is cumbersome. And, it is even more difficult to make only a part of the text clickable. &lt;/p&gt;

&lt;p&gt;Now just pass in &lt;code&gt;isLink: true&lt;/code&gt; and &lt;code&gt;onLinkTap()&lt;/code&gt; callback to the &lt;strong&gt;CustomText&lt;/strong&gt; or simply use the &lt;code&gt;.link()&lt;/code&gt; constructor. See the below example of creating a complex hyperlinked legal statement.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Legal Links&lt;/span&gt;
&lt;span class="n"&gt;CustomText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rich&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'By signing you agree to the '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;maxLines:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;lineHeight:&lt;/span&gt; &lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;letterSpacing:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;textAlign:&lt;/span&gt; &lt;span class="n"&gt;TextAlign&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;CustomText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'Terms'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;letterSpacing:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;onLinkTap:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;AppUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConstants&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;termsOfServiceUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;CustomText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;' and '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;letterSpacing:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;CustomText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'Privacy Policy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;letterSpacing:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;onLinkTap:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;AppUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConstants&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;privacyPolicyUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Here is the crux of it all, the code for this amazing widget. I decided to place it at the end to avoid confusion, because it was important to see the usage and benefits before the implementation. I have included it as gist so I can update it in the future, if needed.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;If you like it, please keep following me as I have around 40 such widgets and I will post many more of these.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
