<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Tomer - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Tomer - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 09 May 2026 14:05:25 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/tomerpacific/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Support Multiple Languages In Your Flutter Application ]]>
                </title>
                <description>
                    <![CDATA[ When building my own applications, I usually don’t stress about having multiple language support. All of my applications are pet projects of mine and I mostly use them to learn and advance my knowledge. Without any intention, some of the applications... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-support-multiple-languages-in-flutter/</link>
                <guid isPermaLink="false">673783bb1fb7eac514af465b</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Fri, 15 Nov 2024 17:24:11 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/YeO44yVTl20/upload/2ec70e1bfce727903fecba0c2f9b6b8b.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When building my own applications, I usually don’t stress about having multiple language support. All of my applications are pet projects of mine and I mostly use them to learn and advance my knowledge.</p>
<p>Without any intention, some of the applications that I have published to the Google Play Store are being used by a considerable amount of people (to my sheer astonishment).</p>
<p>After patting myself on the back, I started looking at the data of the users who are interacting (or just downloaded) with my application(s). One of the insights available in the Google Play console is the country of origin of users. There, I found out that some of my applications have a loyal audience in some non-English-speaking countries.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731601377555/a9cc451f-9e8a-4084-b58a-55af1428dd51.jpeg" alt="Screenshot showing popular countries application was downloaded from" class="image--center mx-auto" width="762" height="356" loading="lazy"></p>
<p>A people pleaser by heart, I figured the best course of action would be to add support to the spoken languages at the top 3 or 4 countries on that list. That is where I discovered the wonderful world of <a target="_blank" href="https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization">Internationalizing a Flutter application</a>.</p>
<p>And that leads us to the purpose of this article: helping you understand how to add multiple language support in your Flutter application.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-localization-in-flutter">How to Set Up Localization in Flutter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-localizations-with-dynamic-values">Localizations With Dynamic Values</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-with-localization">Testing With Localization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-how-to-set-up-localization-in-flutter">How to Set Up Localization in Flutter</h2>
<p>First and foremost, you need to include two packages in your <strong>pubspec.yaml</strong> file:</p>
<ol>
<li><p><a target="_blank" href="https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html">flutter_localizations</a></p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/intl">intl</a></p>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-attr">flutter_localizations:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-attr">intl:</span> <span class="hljs-string">any</span>
</code></pre>
<p>After doing this, head over to the bottom of your <strong>pubspec.yaml</strong> file and under the <code>flutter</code> section, make sure to have the <code>generate</code> attribute set to <code>true</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">flutter:</span>
  <span class="hljs-attr">generate:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>To support this, you will need to create another <strong>.yaml</strong> file called <strong>l10.yaml</strong> with these configurations:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">arb-dir:</span> <span class="hljs-string">lib/l10n</span>   <span class="hljs-string">///</span> <span class="hljs-string">This</span> <span class="hljs-string">is</span> <span class="hljs-string">where</span> <span class="hljs-string">our</span> <span class="hljs-string">translation</span> <span class="hljs-string">files</span> <span class="hljs-string">are</span> <span class="hljs-string">located</span> <span class="hljs-string">at</span>
<span class="hljs-attr">template-arb-file:</span> <span class="hljs-string">app_en.arb</span>       <span class="hljs-string">///</span> <span class="hljs-string">Sets</span> <span class="hljs-string">the</span> <span class="hljs-string">English</span> <span class="hljs-string">template</span>
<span class="hljs-attr">output-localization-file:</span> <span class="hljs-string">app_localizations.dart</span>  <span class="hljs-string">///</span> <span class="hljs-string">Output</span> <span class="hljs-string">file</span> <span class="hljs-string">where</span> <span class="hljs-string">the</span> <span class="hljs-string">generate</span> <span class="hljs-string">command</span> <span class="hljs-string">will</span> <span class="hljs-string">generate</span> <span class="hljs-string">localizations</span>
</code></pre>
<p>☝️ Head over <a target="_blank" href="https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization#configuring-the-l10n-yaml-file">here</a> to read about more configuration options in the <strong>l10.yaml</strong> file</p>
<p>To allow your application support multiple languages, add the following to your <code>MaterialApp</code> widget:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> MaterialApp(
  title: <span class="hljs-string">'My Application'</span>,
  localizationsDelegates: [                    <span class="hljs-comment">/// <span class="markdown">From here</span></span>
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [             
    Locale(<span class="hljs-string">'en'</span>), 
    Locale(<span class="hljs-string">'hi'</span>),
  ],                                           <span class="hljs-comment">/// <span class="markdown">To here</span></span>
  home: MainScreen(),
);
</code></pre>
<p>Having defined the languages we want to support, we need to create the files with the translations for these languages.</p>
<p>Create a folder called <strong>l10</strong> under your <strong>lib</strong> directory:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731601527005/01fd2950-9a09-486b-9f5b-a23595c7f607.jpeg" alt="Showing the folder structure of the application" class="image--center mx-auto" width="362" height="159" loading="lazy"></p>
<p>Inside the folder, you need to place files with an <strong>.arb</strong> extension that will hold key-value pairs of translations. So, for example, if your application needs to support English and Hindi, you will need to create two files:</p>
<ul>
<li><p><strong>app_en.arb</strong></p>
</li>
<li><p><strong>app_hi.arb</strong></p>
</li>
</ul>
<p>The contents of these files look like this:</p>
<pre><code class="lang-json">{
        <span class="hljs-attr">"appTitle"</span>: <span class="hljs-string">"Birthday Calendar"</span>,
        <span class="hljs-attr">"settings"</span>: <span class="hljs-string">"Settings"</span>,
        <span class="hljs-attr">"addBirthday"</span>: <span class="hljs-string">"Add Birthday"</span>,
        /...
}
</code></pre>
<pre><code class="lang-json">{
        <span class="hljs-attr">"appTitle"</span>: <span class="hljs-string">"जन्मदिन कैलेंडर"</span>,
        <span class="hljs-attr">"settings"</span>: <span class="hljs-string">"सेटिंग्स"</span>,
        <span class="hljs-attr">"addBirthday"</span>: <span class="hljs-string">"जन्मदिन जोड़ें"</span>,
        /...
}
</code></pre>
<p>Basically, you have a JSON object, with key value pairs, where the keys are the same across all JSON files, but the values are written in a different language.</p>
<p>The following command is used to generate the files associated with the contents of the <strong>.arb</strong> files:</p>
<pre><code class="lang-bash">flutter gen-l10n
</code></pre>
<p>In files where you intend to use translations, you need to add the following import:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_gen/gen_l10n/app_localizations.dart'</span>;
</code></pre>
<p>To access one of the keys from the <strong>.arb</strong> files, you need to use this code:</p>
<pre><code class="lang-dart">
AppLocalizations.of(context)!.appTitle <span class="hljs-comment">//Or another key name from the .arb file</span>
</code></pre>
<p>✋ Each time that you add more key-value pairs to your <strong>.arb</strong> files, you will need to run the command in the terminal to generate those translations. Otherwise, you won’t be able to access them through the code</p>
<h2 id="heading-localizations-with-dynamic-values">Localizations With Dynamic Values</h2>
<p>Seems straightforward up to this point, right? Well, what if you have places in your application that depend on data that is dynamic and not static? For example, in one of my applications, I have a string that includes an error and that error may change depending on the invocation of an API.</p>
<pre><code class="lang-dart">AlertDialog alertDialog = AlertDialog(
      title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">"Update Failed To Install ❌"</span>),
      content:
          Text(<span class="hljs-string">"Birthday Calendar has failed to update because: \n <span class="hljs-subst">$error</span>"</span>),
      actions: [alertDialogTryAgainButton, alertDialogCancelButton],
    );
</code></pre>
<p>In order to use localized strings here, we need to create a key-value pair in our <strong>.arb</strong> file that has a placeholder for the error:</p>
<pre><code class="lang-dart"><span class="hljs-string">"updateFailedToInstallDescription"</span>: <span class="hljs-string">"Birthday Calendar has failed to update because: {error}"</span>
</code></pre>
<p>And we can use it by doing this:</p>
<pre><code class="lang-dart">AlertDialog alertDialog = AlertDialog(
      title: Text(AppLocalizations.of(context)!.updateFailedToInstallTitle),
      content:
          Text(AppLocalizations.of(context)!.updateFailedToInstallDescription(error)),  <span class="hljs-comment">/// <span class="markdown"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">---</span> <span class="hljs-attr">HERE</span></span></span></span></span>
      actions: [alertDialogTryAgainButton, alertDialogCancelButton],
    );
</code></pre>
<h2 id="heading-testing-with-localization">Testing With Localization</h2>
<p>So you added localizations to your application, but now you realize that your unit tests need to be revamped in order to accommodate for this change. We’ll break this section down into two types of tests you may have:</p>
<ul>
<li><p>Unit tests</p>
</li>
<li><p>Integration tests</p>
</li>
</ul>
<p>For one of my applications, I had a utility class that was paired with a unit test class. So far, so good. When I added localization support to that application, one of the utility methods changed, since it now had to return a value based on the localization. To do that, I had to pass over the <code>AppLocalizations</code> object as an argument to the method. That argument relied on the BuildContext:</p>
<pre><code class="lang-dart"> <span class="hljs-keyword">static</span> <span class="hljs-built_in">String</span> convertAndTranslateMonthNumber(
      <span class="hljs-built_in">int</span> month, AppLocalizations appLocalizations) {
    <span class="hljs-keyword">switch</span> (month) {
      <span class="hljs-keyword">case</span> JANUARY_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.january;
      <span class="hljs-keyword">case</span> FEBRUARY_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.february;
      <span class="hljs-keyword">case</span> MARCH_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.march;
      <span class="hljs-keyword">case</span> APRIL_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.april;
      <span class="hljs-keyword">case</span> MAY_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.may;
      <span class="hljs-keyword">case</span> JUNE_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.june;
      <span class="hljs-keyword">case</span> JULY_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.july;
      <span class="hljs-keyword">case</span> AUGUST_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.august;
      <span class="hljs-keyword">case</span> SEPTEMBER_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.september;
      <span class="hljs-keyword">case</span> OCTOBER_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.october;
      <span class="hljs-keyword">case</span> NOVEMBER_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.november;
      <span class="hljs-keyword">case</span> DECEMBER_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.december;
      <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
    }
  }
</code></pre>
<p>This didn’t fare so well in my corresponding unit test class, since I had to create a <code>BuildContext</code>. Since doing that in a unit test class is problematic, there is a different way to get the <code>AppLocalizations</code> object without having to rely on a <code>BuildContext</code>.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> appLocalizations = lookupAppLocalizations(<span class="hljs-keyword">const</span> Locale(<span class="hljs-string">'en'</span>))
</code></pre>
<p>This way, we state the locale we want and then we can use it in any of our tests. My unit test looks like this after the revision:</p>
<pre><code class="lang-dart">test(<span class="hljs-string">"DateService convert month number 8 to August"</span>, () {
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> monthNumber = <span class="hljs-number">8</span>;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> monthName =
        BirthdayCalendarDateUtils.convertAndTranslateMonthNumber(
            monthNumber, appLocalizations);
    expect(monthName, <span class="hljs-string">"August"</span>);
  });
</code></pre>
<p>As for integration tests, you will need to wrap your widget inside of a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Localizations/Localizations.html">Localizations widget</a>. </p>
<pre><code class="lang-dart">testWidgets(<span class="hljs-string">"Your test description"</span>,
      (WidgetTester tester) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> tester.pumpWidget(
        Localizations(
          delegates: [
            <span class="hljs-comment">//localization delegates</span>
          ],
          locale: Locale(<span class="hljs-string">'en'</span>),
          child: Widget(),
        );
    );
    <span class="hljs-comment">//Your logic here</span>
  });
</code></pre>
<p>To see how all of this is implemented inside of an application, you can go <a target="_blank" href="https://github.com/TomerPacific/BirthdayCalendar">here</a>.</p>
<p>And if you want to download the application, you can head <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.birthday_calendar">here</a>.</p>
<p>If you would like to read other articles I have written, you can find them <a target="_blank" href="https://github.com/TomerPacific/MediumArticles">here</a>.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization">Official Flutter Documentation</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Tooltips in Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ When I wrote my last article about Jetpack Compose, I stated there that Jetpack Compose is missing some (in my opinion) basic components, and one of them is the tooltip. At the time, there was no built-in composable to display tooltips and there were... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-tooltips-in-jetpack-compose/</link>
                <guid isPermaLink="false">66fd516514798d90f2228542</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tooltip ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Wed, 02 Oct 2024 13:57:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813989960/b0a7ab29-d87c-4d87-9847-70b7e1c341b1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When I wrote my <a target="_blank" href="https://medium.com/better-programming/is-jetpack-compose-ready-for-you-eae6c93ad3f8">last article about Jetpack Compose</a>, I stated there that Jetpack Compose is missing some (in my opinion) basic components, and one of them is the tooltip.</p>
<p>At the time, there was no built-in composable to display tooltips and there were several alternative solutions circling online. The problem with those solutions was that once Jetpack Compose released newer versions, those solutions might break. So it wasn’t ideal and the community was left hoping that sometime in the future, support would be added for tooltips.</p>
<p>I’m glad to say that since <a target="_blank" href="https://developer.android.com/jetpack/androidx/releases/compose-material3#1.1.0">version 1.1.0 of Compose Material 3</a>, we now have built in tooltip support. 👏</p>
<p>While this in itself is great, more than a year has passed since that version was released. And with subsequent versions, the API related to tooltips changed drastically as well.</p>
<p>If you go over the changelog, you will see how the public and internal APIs have changed. So bear in mind, that when you read this article, things may have continued to change as everything related to Tooltips is still marked by the annotation <strong>ExperimentalMaterial3Api::class</strong>.</p>
<p>❗️ The version of material 3 used for this article is 1.2.1, which was released on March 6th, 2024</p>
<h2 id="heading-tooltip-types">Tooltip Types</h2>
<p>We now have support for two different types of tooltips:</p>
<ol>
<li><p>Plain tooltip</p>
</li>
<li><p>Rich media tooltip</p>
</li>
</ol>
<h3 id="heading-plain-tooltip">Plain Tooltip</h3>
<p>You can use the first kind to provide information about an icon button that wouldn’t be clear otherwise. For example, you can use a plain tooltip to indicate to a user what the icon button represents.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602449314/94cf84bf-dec0-462c-a8a0-6f878e0d5db3.gif" alt="Basic tooltip example" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>To add a tooltip to your application, you use the <strong>TooltipBox</strong> composable. This composable takes several arguments:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TooltipBox</span><span class="hljs-params">(
    positionProvider: <span class="hljs-type">PopupPositionProvider</span>,
    tooltip: @<span class="hljs-type">Composable</span> <span class="hljs-type">TooltipScope</span>.() -&gt; <span class="hljs-type">Unit</span>,
    state: <span class="hljs-type">TooltipState</span>,
    modifier: <span class="hljs-type">Modifier</span> = Modifier,
    focusable: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    enableUserInput: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    content: @<span class="hljs-type">Composable</span> () -&gt; <span class="hljs-type">Unit</span>,
)</span></span>
</code></pre>
<p>Some of these should be familiar to you if you have used Composables before. I’ll highlight the ones that have a specific use case here:</p>
<ul>
<li><p>positionProvider - Of <strong>PopupPositionProvider</strong> type, and is used to calculate the position of the tooltip.</p>
</li>
<li><p>tooltip - This is where you can design the UI of how the tooltip will look like.</p>
</li>
<li><p>state - This holds the state that is associated with a specific Tooltip instance. It exposes methods like showing/dismissing the tooltip and when instantiating an instance of one, you can declare if the tooltip should be persistent or not (meaning if it should keep displaying on the screen until a user performs a click action outside the tooltip).</p>
</li>
<li><p>content - This is the UI that the tooltip will display above/below.</p>
</li>
</ul>
<p>Here is an example of instantiating a <strong>BasicTooltipBox</strong> with all the relevant arguments filled in:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">BasicTooltip</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> tooltipPosition = TooltipDefaults.rememberPlainTooltipPositionProvider()
    <span class="hljs-keyword">val</span> tooltipState = rememberBasicTooltipState(isPersistent = <span class="hljs-literal">false</span>)

    BasicTooltipBox(positionProvider = tooltipPosition,
        tooltip =  { Text(<span class="hljs-string">"Hello World"</span>) } ,
        state = tooltipState) {
        IconButton(onClick = { }) {
            Icon(imageVector = Icons.Filled.Favorite, 
                 contentDescription = <span class="hljs-string">"Your icon's description"</span>)
        }
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602558759/e00e0bed-6a95-489e-af5c-a7d9dcc33fe6.gif" alt="A basic tooltip" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Jetpack Compose has a built in class called TooltipDefaults. You can use this class to help you instantiate arguments that make up a TooltipBox. For instance, you could use <strong>TooltipDefaults.rememberPlainTooltipPositionProvider</strong> to correctly position the tooltip in relation to the anchor element.</p>
<h3 id="heading-rich-tooltip">Rich Tooltip</h3>
<p>A rich media tooltip takes more space than a plain tooltip and can be used to provide more context about the functionality of an icon button. When the tooltip is shown, you can add buttons and links to it to provide further explanation or definitions.</p>
<p>It is instantiated in a similar way as a plain tooltip, inside of a TooltipBox, but you use the RichTooltip composable.</p>
<pre><code class="lang-kotlin">TooltipBox(positionProvider = tooltipPosition,
        tooltip = {
                  RichTooltip(
                      title = { Text(<span class="hljs-string">"RichTooltip"</span>) },
                      caretSize = caretSize,
                      action = {
                          TextButton(onClick = {
                              scope.launch {
                                  tooltipState.dismiss()
                                  tooltipState.onDispose()
                              }
                          }) {
                              Text(<span class="hljs-string">"Dismiss"</span>)
                          }
                      }
                  ) {
                        Text(<span class="hljs-string">"This is where a description would go."</span>)
                  }
        },
        state = tooltipState) {
        IconButton(onClick = {
            <span class="hljs-comment">/* Icon button's click event */</span>
        }) {
            Icon(imageVector = tooltipIcon,
                contentDescription = <span class="hljs-string">"Your icon's description"</span>,
                tint = iconColor)
        }
    }
</code></pre>
<p>A few things to notice about a Rich tooltip:</p>
<ol>
<li><p>A Rich tooltip has support for a caret.</p>
</li>
<li><p>You can add an action (that is, a button) to the tooltip to give users an option to find out more information.</p>
</li>
<li><p>You can add logic to dismiss the tooltip.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602624042/40160d88-4e8a-4487-835d-1b74a9dd7c72.png" alt="Rich tooltip without a caret" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602651265/f3e6f7fe-c4e1-4f98-972d-b20a273900b4.png" alt="Rich tooltip with a caret" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-edge-cases">Edge Cases</h3>
<p>When you choose to mark your <strong>tooltip state as persistent</strong>, it means that once the user interacts with the UI that shows your tooltip, it will stay visible until the user presses anywhere else on the screen.</p>
<p>If you looked at the example of a Rich tooltip from above, you might have noticed that we have added a button to dismiss the tooltip once it’s clicked.</p>
<p>There is a problem that happens once a user presses that button. Since the dismiss action is performed on the tooltip, if a user wants to perform another long press on the UI item that invokes this tooltip, the tooltip won’t be shown again. This means that the state of the tooltip is persistent on it being dismissed. So, how do we go about and resolve this?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602690256/a31b56bb-77c4-4444-bab6-7ffcca3f5207.gif" alt="Second long press does not trigger the tooltip" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In order to “reset” the state of the tooltip, we have to call the <strong>onDispose</strong> method that is exposed through the tooltip state. Once we do that, the tooltip state is reset and the tooltip will be shown again when the user performs a long press on the UI item.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">RichTooltip</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> tooltipPosition = TooltipDefaults.rememberRichTooltipPositionProvider()
    <span class="hljs-keyword">val</span> tooltipState = rememberTooltipState(isPersistent = <span class="hljs-literal">true</span>)
    <span class="hljs-keyword">val</span> scope = rememberCoroutineScope()

    TooltipBox(positionProvider = tooltipPosition,
        tooltip = {
                  RichTooltip(
                      title = { Text(<span class="hljs-string">"RichTooltip"</span>) },
                      caretSize = TooltipDefaults.caretSize,
                      action = {
                          TextButton(onClick = {
                              scope.launch {
                                  tooltipState.dismiss()
                                  tooltipState.onDispose()  <span class="hljs-comment">/// &lt;---- HERE</span>
                              }
                          }) {
                              Text(<span class="hljs-string">"Dismiss"</span>)
                          }
                      }
                  ) {

                  }
        },
        state = tooltipState) {
        IconButton(onClick = {  }) {
            Icon(imageVector = Icons.Filled.Call, contentDescription = <span class="hljs-string">"Your icon's description"</span>)
        }
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602730404/60f31668-ea66-4127-b6fc-41f3aca952ae.gif" alt="onDispose solves the issue" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Another scenario where the tooltip state does not reset is if instead of calling ourselves for the dismiss method per a user’s action, the user clicks outside of the tooltip, causing it to be dismissed. This calls the dismiss method behind the scenes and the tooltip state is set to dismissed. Long pressing on the UI element to see our tooltip again will result in nothing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602758707/60387e08-72e6-45d4-bd47-ffb2708e0efe.gif" alt="The tooltip does not show again" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Our logic that calls the tooltip’s onDispose method does not get triggered, so how can we reset the tooltip’s state?</p>
<p>Currently, I haven’t been able to figure this out. It might be related to the tooltip’s <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex">MutatorMutex</a>. Maybe with upcoming releases, there will be an API for this. I did notice that if other tooltips are present on the screen and they are pressed, this resets the previously clicked upon tooltip.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602790121/25a81994-a508-4c71-8424-c45370a7999d.gif" alt="25a81994-a508-4c71-8424-c45370a7999d" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>If you would like to see the code featured here, you can go to <a target="_blank" href="https://github.com/TomerPacific/MediumArticles/tree/master/TooltipExample">this GitHub repository</a></p>
<p>If you would like to see tooltips in an application, you can check it out <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.laundry">here</a>.</p>
<h4 id="heading-references">References</h4>
<ul>
<li><p><a target="_blank" href="https://m3.material.io/components/tooltips/overview">Material3 Tooltip Overview</a></p>
</li>
<li><p><a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material3/TooltipDefaults">Tooltip Defaults</a></p>
</li>
<li><p><a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt">Tooltip Source Code</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Migrate a Flutter Application from GetIt to Bloc ]]>
                </title>
                <description>
                    <![CDATA[ When I first built an application using Flutter, I quickly ran into situations where I needed to pass state from widget to widget. These widgets weren’t directly related and all I knew back then was that there were only Stateless widgets or Stateful ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/migrate-a-flutter-application-from-getit-to-bloc/</link>
                <guid isPermaLink="false">66ba502f43a51af2a76f756e</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Fri, 19 Jul 2024 23:05:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/ryan-quintal-US9Tc9pKNBU-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When I first built an <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.birthday_calendar&amp;hl=en">application</a> using Flutter, I quickly ran into situations where I needed to pass state from widget to widget. These widgets weren’t directly related and all I knew back then was that there were only Stateless widgets or Stateful ones.</p>
<p>I found it hard to understand how I could achieve letting a completely unrelated widget know about something that happens in another widget inside my application. </p>
<p>Take, for example, a feature I wanted to implement that would allow the user to choose the theme of the application (light/dark). Since I had a settings screen with this feature, I wondered how I could let the rest of the application know that the theme has changed and react to it.</p>
<p>Searching online for guidance, I noticed there was no shortage of solutions being offered. Each with it’s own degree of complexity. <strong>Bloc</strong> was a popular choice plenty of people online suggested, but in the same breath, it was said that the learning curve is quite steep. Wanting to deliver features to the application quicker, I chose to use <a target="_blank" href="https://pub.dev/packages/get_it">GetIt</a>.</p>
<p>Why did I choose GetIt? I think the package’s creator(s) pretty much sum it up best <a target="_blank" href="https://pub.dev/packages/get_it#why-getit">in their own words</a>:</p>
<blockquote>
<p><em>GetIt is:</em><br><em>- Extremely fast (O(1))</em><br><em>- Easy to learn/use</em><br><em>- Doesn’t clutter your UI tree with special Widgets to access your data like Provider or Redux does.</em></p>
</blockquote>
<p>It is mentioned that GetIt is not a state management solution, but rather a tool to help you access objects inside your application.</p>
<p>So I headed off in the direction of using GetIt with a combination of Provider and ChangeNotifier in my application. While it wasn’t pretty, it got the job done.</p>
<p>During the development of features for my application and making it more robust, I knew in the back of my head that I wasn’t using the correct tools to manage state properly in my application.</p>
<p>Recently, I decided that it was time to learn Bloc properly and to convert the code inside my application to use it. I knew that it wasn’t going to be an easy task, but after going through it, I can admit that after a few trail and error attempts, it got easier to handle. With each use case I encountered, my understanding grew.</p>
<p>In this article, I’ll present some actual use cases where I used GetIt in combination with Provider and ChangeNotifier and replaced them with Bloc. Hopefully you can use these examples to better understand how to use Bloc in your applications.</p>
<h2 id="heading-managing-the-darklight-theme">Managing the Dark/Light Theme</h2>
<p>I wanted my application to support different themes. To do that, I created a Settings screen where the user could control the theme color.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/1-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Settings Screen</em></p>
<p>Developing this was the first time I had to deal with changes in the application’s state that would be reflected in widgets that weren't directly related. So, besides creating a widget for the Settings screen,</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SettingsScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
              appBar: AppBar(
                title: <span class="hljs-keyword">new</span> Text(<span class="hljs-string">"Settings"</span>),
              ),
              body:
                  Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: [
                        Consumer&lt;SettingsScreenManager&gt;(
                            builder: (context, notifier, child) {
                              <span class="hljs-keyword">return</span>  SwitchListTile(
                                  title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Dark Mode'</span>),
                                  value: notifier.themeMode == ThemeMode.light ? <span class="hljs-keyword">false</span> : <span class="hljs-keyword">true</span>,
                                  secondary:
                                  <span class="hljs-keyword">new</span> Icon(
                                      Icons.dark_mode,
                                      color: notifier.themeMode == ThemeMode.light ? Color(<span class="hljs-number">0xFF642ef3</span>) : Color.fromARGB(<span class="hljs-number">200</span>, <span class="hljs-number">243</span>, <span class="hljs-number">231</span>, <span class="hljs-number">106</span>)
                                  ),
                                  onChanged:notifier.handleThemeModeSettingChange
                              );
                            }
                        ),
                        <span class="hljs-comment">//.....</span>
                      ],
                    ),
                  )
      );
  }
</code></pre>
<p>I also created a manager class for it called SettingsScreenManager, where I had this method:</p>
<pre><code class="lang-dart"> <span class="hljs-keyword">void</span> handleThemeModeSettingChange(<span class="hljs-built_in">bool</span> isDarkModeEnabled) {
    _themeMode = _themeMode == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
    _storageService.saveThemeModeSetting(isDarkModeEnabled);
    notifyListeners();
  }
</code></pre>
<p>The connection between the screen and its manager happens when the widget is created, as that is where I create the manager class. Then, throughout the widget itself, I call methods on the manager class. To make the widget redraw itself, I used the Consumer widget.</p>
<p>This is not the best approach, and to rectify the situation I created a Bloc to handle the theme mode:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:birthday_calendar/service/storage_service/storage_service.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_bloc/flutter_bloc.dart'</span>;

<span class="hljs-keyword">enum</span> ThemeEvent { toggleDark, toggleLight }

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ThemeBloc</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Bloc</span>&lt;<span class="hljs-title">ThemeEvent</span>, <span class="hljs-title">ThemeMode</span>&gt; </span>{
  ThemeBloc(StorageService storageService, <span class="hljs-built_in">bool</span> isDarkMode) : <span class="hljs-keyword">super</span>(isDarkMode ? ThemeMode.dark : ThemeMode.light) {
    <span class="hljs-keyword">on</span>&lt;ThemeEvent&gt;((event, emit) {
      ThemeMode themeMode = event == ThemeEvent.toggleDark ? ThemeMode.dark : ThemeMode.light;
      emit(themeMode);
      storageService.saveThemeModeSetting(themeMode == ThemeMode.dark ? <span class="hljs-keyword">true</span> : <span class="hljs-keyword">false</span>);
    });
  }
}
</code></pre>
<p>Let’s break down the components of this Bloc:</p>
<ol>
<li>I have declared an enum called <strong>ThemeEvent</strong> to signify the user’s choice of light/dark theme</li>
<li>Since the state of the Bloc is directly the <strong>ThemeMode</strong> object, there was no need to create a specific State object</li>
<li>Whenever the theme changes, I emit the chosen theme mode</li>
</ol>
<p>And I initialized this bloc inside my <strong>main.dart</strong> file in order for it to be accessible to any widget in the widget hierarchy. Also, I wanted any change that occurred due to this Bloc to be enacted on the entire application.</p>
<pre><code class="lang-dart"> <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MultiBlocProvider(
      providers: [
        BlocProvider(create: (context) =&gt; ThemeBloc(storageService, isDarkMode)),
        <span class="hljs-comment">//...</span>
      ],
      child: BlocBuilder&lt;ThemeBloc, ThemeMode&gt;(
        builder: (context, state) {
          <span class="hljs-keyword">return</span> MaterialApp(
              title: applicationName,
              theme: ThemeData.light(),
              themeMode: state,
              darkTheme: ThemeData.dark(),
              home: MainPage(
                  key: Key(<span class="hljs-string">"BirthdayCalendar"</span>),
                  notificationService: notificationService,
                  contactsService: contactsService,
                  storageService: storageService,
                  title: applicationName,
                  currentMonth: BirthdayCalendarDateUtils.getCurrentMonthNumber()));
        },
      ),
    );
  }
</code></pre>
<h2 id="heading-requesting-permission">Requesting Permission</h2>
<p>There is a feature in my application that allows users to import their contacts. In order to do so, there is a requirement to first ask a runtime permission. </p>
<p>Initially, I handled this using the same approach as in the previous section, utilizing the SettingsScreenManager class, a Consumer, and a Provider.</p>
<pre><code class="lang-dart">Consumer&lt;SettingsScreenManager&gt;(
   builder: (context, notifier, child) {
    <span class="hljs-keyword">return</span> ListTile(
      title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">"Import Contacts"</span>),
      leading: Icon(Icons.contacts,
          color: !notifier.isContactsPermissionPermanentlyDenied ? Colors.blue : Colors.grey
      ),
      onTap: () {
        Provider.of&lt;SettingsScreenManager&gt;(context, listen: <span class="hljs-keyword">false</span>).handleImportingContacts(context);
      },
      enabled: !notifier.isContactsPermissionPermanentlyDenied
  );
}),
</code></pre>
<p>Replacing this was a step up from creating the ThemeBloc since I needed to handle the different permission statuses and also to remember if the permission was permanently denied.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">enum</span> ContactsPermissionStatusEvent {
  PermissionUnknown,
  PermissionDenied,
  PermissionGranted,
  PermissionPermanentlyDenied
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ContactsPermissionStatusBloc</span>
    <span class="hljs-keyword">extends</span> <span class="hljs-title">Bloc</span>&lt;<span class="hljs-title">ContactsPermissionStatusEvent</span>, <span class="hljs-title">PermissionStatus</span>&gt; </span>{
  ContactsPermissionStatusBloc(ContactsService contactsService)
      : <span class="hljs-keyword">super</span>(PermissionStatus.denied) {
    <span class="hljs-keyword">on</span>&lt;ContactsPermissionStatusEvent&gt;((event, emit) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">if</span> (event == ContactsPermissionStatusEvent.PermissionUnknown) {
        <span class="hljs-built_in">bool</span> permissionStatus =
            <span class="hljs-keyword">await</span> contactsService.isContactsPermissionsPermanentlyDenied();
        <span class="hljs-keyword">if</span> (permissionStatus) {
          emit(PermissionStatus.permanentlyDenied);
          <span class="hljs-keyword">return</span>;
        }
      }
      emit(_convertEventNameToPermissionStatus(event));
    });
  }

  PermissionStatus _convertEventNameToPermissionStatus(
      ContactsPermissionStatusEvent event) {
    <span class="hljs-keyword">switch</span> (event) {
      <span class="hljs-keyword">case</span> ContactsPermissionStatusEvent.PermissionDenied:
        <span class="hljs-keyword">return</span> PermissionStatus.denied;
      <span class="hljs-keyword">case</span> ContactsPermissionStatusEvent.PermissionGranted:
        <span class="hljs-keyword">return</span> PermissionStatus.granted;
      <span class="hljs-keyword">case</span> ContactsPermissionStatusEvent.PermissionPermanentlyDenied:
        <span class="hljs-keyword">return</span> PermissionStatus.permanentlyDenied;
      <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">return</span> PermissionStatus.denied;
    }
  }
}
</code></pre>
<p>This Bloc has the following:</p>
<ul>
<li>A <strong>ContactsPermissionStatusEvent</strong> enum that correlates with the different permissions status the OS has</li>
<li>The state for this Bloc can be easily represented with the <strong>PermissionStatus</strong> class</li>
<li>I have a private helper method called <strong>_convertEventNameToPermissionStatus</strong> to help in converting the event name to it’s corresponding permission status</li>
</ul>
<p>You might be asking yourself why I added an event called <strong>PermissionUnknown</strong>. I did this so I could get the permission status in advance of the user navigating to the SettingsScreen. In the case where the user previously chose to permanently deny the permission, I wanted to gray out the option to import contacts for them. </p>
<p>To achieve this, I created the Bloc in the main.dart file:</p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MultiBlocProvider(
      providers: [
        BlocProvider(create: (context) =&gt; ThemeBloc(storageService, isDarkMode)),
        BlocProvider(
            create: (context) =&gt; ContactsPermissionStatusBloc(contactsService)),
        BlocProvider(create: (context) =&gt; VersionBloc())
      ],
      child: BlocBuilder&lt;ThemeBloc, ThemeMode&gt;(
        builder: (context, state) {
          <span class="hljs-keyword">return</span> MaterialApp(
</code></pre>
<p>and I sent the event inside the initState method of the widget that is the parent of the SettingsScreen.</p>
<pre><code class="lang-dart"> <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-comment">//....</span>
    BlocProvider.of&lt;ContactsPermissionStatusBloc&gt;(context)
        .add(ContactsPermissionStatusEvent.PermissionUnknown);
    <span class="hljs-keyword">super</span>.initState();
  }
</code></pre>
<p>And instead of the huge chunk of code I had earlier, I now have this:</p>
<pre><code class="lang-dart">BlocBuilder&lt;ContactsPermissionStatusBloc, PermissionStatus&gt;(
              builder: (context, state) {
            <span class="hljs-keyword">return</span> ListTile(
                title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">"Import Contacts"</span>),
                leading: Icon(Icons.contacts, color: Colors.blue),
                onTap: () {
                  _handleImportingContacts(context);
                },
                enabled: state.isPermanentlyDenied ? <span class="hljs-keyword">false</span> : <span class="hljs-keyword">true</span>);
          }),
</code></pre>
<h2 id="heading-interacting-with-a-list">Interacting with a List</h2>
<p>Part of my application allows users to add/remove birthdays on specific calendar dates. As with previous features, here too I created a manager class to handle the state for if a user added/removed a birthday. </p>
<p>Part of the logic involved the presentation of an alert dialog with fields to add a birthday. This logic proved to be the most robust when trying to migrate to Bloc, as I had to think about all of the user flows.</p>
<p>This is what that widget looked like:</p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> ChangeNotifierProvider(
      create: (context) =&gt; BirthdaysForCalendarDayManager(<span class="hljs-keyword">this</span>.birthdays, <span class="hljs-keyword">this</span>.dateOfDay),
          builder: (context, provider) {
              <span class="hljs-keyword">return</span> Scaffold(
              appBar: AppBar(
              title: FittedBox(
                  fit: BoxFit.fitWidth,
                  child: Text(
                      <span class="hljs-string">"Birthdays for <span class="hljs-subst">${_dateService.convertMonthToWord(<span class="hljs-keyword">this</span>.dateOfDay.month)}</span> <span class="hljs-subst">${<span class="hljs-keyword">this</span>.dateOfDay.day}</span>"</span>)
              )
          ),
            body: Center(
                child: Column(
                  children: [
                      Consumer&lt;BirthdaysForCalendarDayManager&gt;(
                          builder: (context, data, child) =&gt;
                          Expanded(child:
                            ListView.builder(
                                  itemCount: data.birthdays.length,
                                  itemBuilder: (BuildContext context, <span class="hljs-built_in">int</span> index) {
                                  <span class="hljs-keyword">return</span> BirthdayWidget(
                                    key: Key(data.birthdays[index].name),
                                      birthdayOfPerson: data.birthdays[index],
                                      onDeletePressedCallback: () {
                                        Provider.of&lt;BirthdaysForCalendarDayManager&gt;(context, listen: <span class="hljs-keyword">false</span>).removeBirthdayFromList(data.birthdays[index]);
                                    },
                                    indexOfBirthday: index);
                                  },
                                 ),
                           ),
                          )
                      ],
                   )
                ),
                floatingActionButton: FloatingActionButton(
                onPressed: () {
                  Provider.of&lt;BirthdaysForCalendarDayManager&gt;(context, listen: <span class="hljs-keyword">false</span>).handleAddBirthdayBtnPressed(context, dateOfDay);
                  },
                child: Icon(Icons.add)),
              );
          },
    );
  }
</code></pre>
<p>And the manager class:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BirthdaysForCalendarDayManager</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ChangeNotifier</span> </span>{

  NotificationService _notificationService = getIt&lt;NotificationService&gt;();
  StorageService _storageService = getIt&lt;StorageService&gt;();
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;UserBirthday&gt; _currentBirthdays = [];
  <span class="hljs-built_in">DateTime</span> date = <span class="hljs-built_in">DateTime</span>.now();

  UnmodifiableListView&lt;UserBirthday&gt; <span class="hljs-keyword">get</span> birthdays =&gt; UnmodifiableListView(_currentBirthdays);

  BirthdaysForCalendarDayManager(<span class="hljs-built_in">List</span>&lt;UserBirthday&gt; birthdays, <span class="hljs-built_in">DateTime</span> dateTime) {
    <span class="hljs-comment">//....</span>
  }

  <span class="hljs-keyword">void</span> _handleUserInput(UserBirthday userBirthday) {
    <span class="hljs-comment">//....</span>
  }

  <span class="hljs-keyword">void</span> _addBirthdayToList(UserBirthday userBirthday) {
    <span class="hljs-comment">//....</span>
    notifyListeners();
  }

  <span class="hljs-keyword">void</span> removeBirthdayFromList(UserBirthday birthdayToRemove) <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">//....</span>
    notifyListeners();
  }

  <span class="hljs-keyword">void</span> handleAddBirthdayBtnPressed(BuildContext context, <span class="hljs-built_in">DateTime</span> dateOfDay) <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">//....</span>
  }
</code></pre>
<p>So how can we go about migrating all this logic to Bloc? Well, first let’s think of the different events we will need:</p>
<ol>
<li>Adding an item to the list</li>
<li>Removing an item to the list</li>
<li>Presenting the dialog that allows users to add an item to the list (this is used to be able to show the dialog)</li>
</ol>
<p>So our inner enum for events can look like:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">enum</span> BirthdayEvent { AddBirthday, RemoveBirthday, ShowAddBirthdayDialog }
</code></pre>
<p>But what will our BirthdaysEvent include?</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BirthdaysEvent</span> </span>{
  <span class="hljs-keyword">final</span> BirthdayEvent eventName;  <span class="hljs-comment">// 1</span>
  <span class="hljs-keyword">final</span> UserBirthday? birthday;   <span class="hljs-comment">// 2</span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool?</span> shouldShowAddBirthdayDialog; <span class="hljs-comment">// 3</span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;UserBirthday&gt; birthdays; <span class="hljs-comment">// 4</span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">DateTime?</span> date;  <span class="hljs-comment">//5</span>

  BirthdaysEvent(
      {<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.eventName,
      <span class="hljs-keyword">this</span>.birthday,
      <span class="hljs-keyword">this</span>.shouldShowAddBirthdayDialog,
      <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.birthdays,
      <span class="hljs-keyword">this</span>.date});
}
</code></pre>
<ol>
<li>The event name</li>
<li>The birthday we will either add or remove</li>
<li>A flag to indicate if we should present the dialog</li>
<li>The whole list of birthdays for the specific date</li>
<li>The date the user wants to add/remove birthdays to/from</li>
</ol>
<p>You may have noticed that not all fields are required to create a <strong>BirthdaysEvent</strong>. This is because not all of these fields are necessary for the different types of events. For example, when the user wants to add another birthday, the second argument (titled birthday) is irrelevant since we want to create a birthday.</p>
<p>Next, we need to think about what should be included in our state. Looking at the code above, it is clear that we need:</p>
<ul>
<li>To keep the list of birthdays, as we are either removing from or adding to it</li>
<li>A flag to indicate if we should show the add birthday dialog</li>
<li>The current date to add/remove birthdays to</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BirthdaysState</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">DateTime?</span> date;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;UserBirthday&gt;? birthdays;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool</span> showAddBirthdayDialog;

  BirthdaysState(
      {<span class="hljs-keyword">this</span>.date, <span class="hljs-keyword">this</span>.birthdays, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.showAddBirthdayDialog});
}
</code></pre>
<p>So we got our events in place and our state as well. Now it's time to implement the logic in our bloc that handles each of these events and create a new state:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BirthdaysBloc</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Bloc</span>&lt;<span class="hljs-title">BirthdaysEvent</span>, <span class="hljs-title">BirthdaysState</span>&gt; </span>{
  BirthdaysBloc(NotificationService notificationService,
      StorageService storageService, <span class="hljs-built_in">List</span>&lt;UserBirthday&gt; birthdaysForDate)
      : <span class="hljs-keyword">super</span>(BirthdaysState(
            date: <span class="hljs-built_in">DateTime</span>.now(),
            birthdays: birthdaysForDate,
            showAddBirthdayDialog: <span class="hljs-keyword">false</span>)) {
    <span class="hljs-keyword">on</span>&lt;BirthdaysEvent&gt;((event, emit) {
      <span class="hljs-keyword">switch</span> (event.eventName) {
        <span class="hljs-keyword">case</span> BirthdayEvent.AddBirthday:
          _handleAddEvent(event, emit, storageService, notificationService);
          <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> BirthdayEvent.RemoveBirthday:
          _handleRemoveEvent(event, emit, storageService, notificationService);
          <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> BirthdayEvent.ShowAddBirthdayDialog:
          emit(<span class="hljs-keyword">new</span> BirthdaysState(showAddBirthdayDialog: <span class="hljs-keyword">true</span>));
          <span class="hljs-keyword">break</span>;
      }
    });
  }
</code></pre>
<p>If we look at one event, <strong>ShowAddBirthdayDialog</strong>, you can see that we are just emitting a new BirthdayState where the <strong>showAddBirthdayDialog</strong> is set to true. But where is this handled? I had to heavily refactor the widget from above in order for it to respond for changes in the state.</p>
<pre><code class="lang-dart">  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
   <span class="hljs-keyword">return</span> BlocProvider(                            <span class="hljs-comment">// 1</span>
        create: (context) =&gt;
            BirthdaysBloc(notificationService, storageService, birthdays),
        child: BlocBuilder&lt;BirthdaysBloc, BirthdaysState&gt;(   <span class="hljs-comment">// 2</span>
            builder: (context, state) {
          <span class="hljs-keyword">return</span> Scaffold(
            appBar: AppBar(
                title: FittedBox(
                    fit: BoxFit.fitWidth,
                    child: Text(
                        <span class="hljs-string">"Birthdays for <span class="hljs-subst">${BirthdayCalendarDateUtils.convertMonthToWord(<span class="hljs-keyword">this</span>.dateOfDay.month)}</span> <span class="hljs-subst">${<span class="hljs-keyword">this</span>.dateOfDay.day}</span>"</span>))),
            body: Center(
                child: Column(
              children: [
                (state.birthdays == <span class="hljs-keyword">null</span> || state.birthdays!.length == <span class="hljs-number">0</span>)
                    ? Spacer()
                    : Expanded(
                        child: ListView.builder(
                          itemCount: state.birthdays != <span class="hljs-keyword">null</span>
                              ? state.birthdays!.length
                              : <span class="hljs-number">0</span>,
                          itemBuilder: (BuildContext context, <span class="hljs-built_in">int</span> index) {
                            <span class="hljs-keyword">return</span> BirthdayWidget(
                                key: Key(state.birthdays![index].name),
                                birthdayOfPerson: state.birthdays![index],
                                onDeletePressedCallback: () {  <span class="hljs-comment">// 3</span>
                                  BlocProvider.of&lt;BirthdaysBloc&gt;(context).add(
                                      <span class="hljs-keyword">new</span> BirthdaysEvent(
                                          eventName:
                                              BirthdayEvent.RemoveBirthday,
                                          birthday: state.birthdays![index],
                                          birthdays: birthdays));
                                },
                                indexOfBirthday: index,
                                storageService: storageService,
                                notificationService: notificationService);
                          },
                        ),
                      ),
                BlocListener&lt;BirthdaysBloc, BirthdaysState&gt;(  <span class="hljs-comment">// 4</span>
                  listener: (context, state) {
                    <span class="hljs-keyword">if</span> (state.showAddBirthdayDialog) {
                      showDialog(
                          context: context,
                          builder: (_) =&gt; BlocProvider.value(  <span class="hljs-comment">// 5</span>
                              value: BlocProvider.of&lt;BirthdaysBloc&gt;(context),
                              child: AddBirthdayForm(
                                  dateOfDay: dateOfDay,
                                  storageService: storageService)));
                    }
                  },
                  child: Spacer(),
                )
              ],
            )),
            floatingActionButton: FloatingActionButton(
                onPressed: () { <span class="hljs-comment">// 6</span>
                  BlocProvider.of&lt;BirthdaysBloc&gt;(context).add(BirthdaysEvent(
                      eventName: BirthdayEvent.ShowAddBirthdayDialog,
                      shouldShowAddBirthdayDialog: <span class="hljs-keyword">true</span>,
                      birthdays: birthdays));
                },
                child: Icon(Icons.add)),
          );
        }));
  }
</code></pre>
<p>There is a lot to unpack here, so let’s take it one step at a time.</p>
<ol>
<li>The BirthdaysBloc is created inside this widget since it is not needed anywhere else up the widget tree</li>
<li>We are using a BlocBuilder so the widget will re-draw itself when the state changes</li>
<li>When a birthday is chosen to be deleted, we create a RemoveBirthday event and pass along all the necessary information</li>
<li>We are using a BlocListener to handle the changes in the state in order to show the AlertDialog for adding a new birthday</li>
<li>Since our BirthdaysBloc is not found on the global level, it is necessary to pass it in to the <strong>AddBirthdayForm</strong> widget using BlocProvider</li>
<li>When the user presses the floating action button to signify an intent to add a birthday, we create a ShowAddBirthdayDialog event</li>
</ol>
<p>Notice that, after all these changes, the manager class was no longer needed and therefore, the code itself is more straightforward and easier to maintain.</p>
<p>You are more than welcome to check out the entirety of the code shown above in the GitHub repository here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/BirthdayCalendar">https://github.com/TomerPacific/BirthdayCalendar</a></div>
<p>And if you like, you can check out the application itself, <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.birthday_calendar">here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Migrate from Play Core Library ]]>
                </title>
                <description>
                    <![CDATA[ You may have recently received an email from Google Play Store stating the following: Update your Play Core Maven dependency to an Android 14 compatible version! Your current Play Core library is incompatible with targetSdkVersion 34 (Android 14), w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/migrate-from-play-core-library/</link>
                <guid isPermaLink="false">66ba5031256e9dbeab31aa84</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Wed, 26 Jun 2024 17:53:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/06/ben-hershey-fnRKVPx5_xY-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>You may have recently received an email from Google Play Store stating the following:</p>
<blockquote>
<p><em>Update your Play Core Maven dependency to an Android 14 compatible version! Your current Play Core library is incompatible with targetSdkVersion 34 (Android 14), which introduces a backwards-incompatible change to broadcast receivers to improve user security. As a reminder, from August 31, Google Play requires all new app releases to target Android 14. Update to the latest Play Core library version dependency to avoid app crashes:</em> <a target="_blank" href="https://developer.android.com/guide/playcore#playcore-migration"><em>https://developer.android.com/guide/playcore#playcore-migration</em></a>  </p>
<p><em>You may not be able to release future versions of your app with this SDK version to production or open testing.</em></p>
</blockquote>
<p>Looks frightening, doesn’t it?</p>
<p>Don’t be so worried. It is actually easier than it looks.</p>
<h2 id="heading-what-the-change-is-actually-about">What the Change is Actually About</h2>
<p>Basically, Google stopped releasing new versions of the play core library back in early 2022.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>The last version of play core library released</em></p>
<p>And from April 2022, they have broken down the original play core library into four separate libraries:</p>
<ul>
<li>Play Assets Delivery Library</li>
<li>Play Feature Delivery Library</li>
<li>Play In-App Reviews Library</li>
<li>Play In-App Updates Library</li>
</ul>
<p>Each library has its own functionality and responsibility.</p>
<p>Since the older core play library only supports up to a certain API level, you need to migrate your application to use the newer libraries that have support for the most recent API levels.</p>
<p>In essence, you need to figure out which functionality of the original core play library you are using and then download the correct part. For example, if you had logic to notify users when a newer version of your application was available, you need to take the Play In-App-Updates library.</p>
<p>We will be presenting two uses cases here:</p>
<ul>
<li>Native Android application</li>
<li>Flutter application</li>
</ul>
<h2 id="heading-use-case-native-android-app">Use Case – Native Android App</h2>
<p>If you have a native Android application, whether it is written in Kotlin or Java, you need to do the following:</p>
<ol>
<li>Open your application level build.gradle file</li>
<li>Most probably you will see under the dependencies block, this line:</li>
</ol>
<pre><code class="lang-groovy">implementation 'com.google.android.play:core-ktx:1.8.1'
</code></pre>
<ol start="3">
<li><p>You will need to remove it and replace it according to what you used in the previous core library</p>
</li>
<li><p>If you need to take the Play In-App-Updates library, then you need to add these to the dependencies block:</p>
</li>
</ol>
<pre><code class="lang-groovy">implementation 'com.google.android.play:app-update:2.1.0'
//Add the dependency below if you are using Kotlin in your application
implementation 'com.google.android.play:app-update-ktx:2.1.0'
</code></pre>
<ol start="5">
<li>Rebuild your application and see that everything works as it should.</li>
</ol>
<p>✋ You might also need to change import statements from <strong>import com.google.android.play.core.tasks.*;</strong> to <strong>import com.google.android.gms.tasks.*;</strong>.</p>
<h2 id="heading-use-case-flutter-application">Use Case – Flutter Application</h2>
<p>Since Flutter is a framework that caters to both Android and iOS, this scenario is a bit different from the one above. If you receive the warning to upgrade the core play library in your Flutter application, you need to have a look at the libraries you are using in your pubspec.yaml file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">in_app_update:</span> <span class="hljs-string">^3.0.0</span>
</code></pre>
<p>As you can see above, the application depends on the <strong>in_app_update</strong> library, which has to do with notifying users when a newer version of the application is available. When we head over to in_app_update’s pub.dev <a target="_blank" href="https://pub.dev/packages/in_app_update/changelog">changelog page</a>, we can see that:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/1-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>version 4.1.0 added the required support</em></p>
<p>So we need to update our pubspec.yaml file to use that version (at the very least).</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">in_app_update:</span> <span class="hljs-string">^4.1.0</span>
</code></pre>
<p>Run Pub get and you should be good to go.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Make Your Flutter Package Privacy Manifest Compatible ]]>
                </title>
                <description>
                    <![CDATA[ Beginning May 1st, Apple will enforce all new applications or updated versions of applications that will be uploaded to the Apple Store, to include a Privacy Manifest file.  If you are unfamiliar with what a Privacy Manifest is, I suggest reading my ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-make-your-flutter-package-privacy-manifest-compatible/</link>
                <guid isPermaLink="false">66ba501c158e6c6a8cb8c7a0</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Mon, 20 May 2024 08:10:33 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/05/tierra-mallorca-rgJ1J8SDEAY-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Beginning May 1st, Apple will enforce all new applications or updated versions of applications that will be uploaded to the Apple Store, to include a Privacy Manifest file. </p>
<p>If you are unfamiliar with what a Privacy Manifest is, I suggest reading my <a target="_blank" href="https://www.freecodecamp.org/news/what-the-ios-privacy-manifest-means-for-developers/">other article</a>.</p>
<p>There has been a lot of confusion regarding what developers need to do if they are using Flutter. </p>
<p>This is because Flutter has been marked by Apple as a <a target="_blank" href="https://developer.apple.com/news/?id=r1henawx#:~:text=Third%2Dparty%20SDK%20privacy%20manifest%20and%20signatures.&amp;text=Starting%20in%20spring%202024%2C%20if,used%20as%20a%20binary%20dependency.">“commonly used SDK”</a> (including several other Flutter packages). This has been somewhat addressed by the Flutter community. If you are interested, you can read the following GitHub issues:</p>
<ul>
<li><a target="_blank" href="https://github.com/flutter/flutter/issues/143232?source=post_page-----52b2da5eabf3--------------------------------">Support Privacy Manifest and Required APIs in iOS and macOS</a></li>
<li><a target="_blank" href="https://github.com/flutter/flutter/issues/131940?source=post_page-----52b2da5eabf3--------------------------------">Determine how to handle privacy manifests in packages</a></li>
</ul>
<p>As of May 2024, things are still a bit vague. It would be preferable to have a solution that reassures us of being able to update our package and not cause applications that use it to be rejected.</p>
<p>We want to continue being good citizens in the Mobile ecosystem.</p>
<h2 id="heading-how-to-upgrade-dependencies">How to Upgrade Dependencies</h2>
<p>Regardless of what logic you have inside your package, in order to support Apple’s changes, you have to enforce your package to use <a target="_blank" href="https://medium.com/flutter/whats-new-in-flutter-3-19-58b1aae242d2">Flutter version 3.19</a> at the very least. </p>
<p>You can do this by going to your <code>pubspec.yaml</code> file and changing the version there:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">environment:</span>
  <span class="hljs-attr">sdk:</span> <span class="hljs-string">"&gt;=3.0.0 &lt;4.0.0"</span>
  <span class="hljs-attr">flutter:</span> <span class="hljs-string">"^2.0.0"</span>   <span class="hljs-string">///</span> <span class="hljs-string">&lt;---</span> <span class="hljs-string">Change</span> <span class="hljs-string">this</span> <span class="hljs-string">to</span> <span class="hljs-number">3.19</span>
</code></pre>
<p>Then, you need to go over any dependencies you may have in your <code>pubspec.yaml</code> file and find out if you have packages that are listed under the “commonly used sdks”.</p>
<p>If so, you will need to find out if that package has released a new version with a privacy manifest file. For example, if our package’s <code>pubspec.yaml</code> looks like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-attr">intl:</span> <span class="hljs-string">^0.17.0</span>
  <span class="hljs-attr">shared_preferences:</span> <span class="hljs-string">^2.0.5</span>
  <span class="hljs-attr">url_launcher:</span> <span class="hljs-string">^6.0.17</span>
  <span class="hljs-attr">package_info_plus:</span> <span class="hljs-string">^3.1.2</span>
</code></pre>
<p>our package depends on <code>url_launcher</code>, which as it happens, is also listed under the “commonly used sdks”. </p>
<p>When we go over to <code>url_launcher</code>’s page on <a target="_blank" href="https://pub.dev/packages/url_launcher">pub.dev</a>, we will be able to see that they released a new version, 6.2.6, that includes a privacy manifest.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/1.jpg" alt="Image" width="600" height="400" loading="lazy">
_Screenshot of url<em>launcher package and its version that supports privacy manifest</em></p>
<p>So we will upgrade to that version.</p>
<p>Once we have gone over all our our dependencies, we can go ahead and deal with the code in our own package.</p>
<h2 id="heading-how-to-add-the-privacy-manifest-file">How to Add the Privacy Manifest File</h2>
<p>The privacy manifest file needs to reside inside of the <code>Resources</code> folder in your project structure (under <code>darwin</code>).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/1-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Directory structure of iOS folder inside a Flutter package</em></p>
<p>Make sure to follow Apple’s guidelines on what data to include there. This will depend on your package’s use case. If you are unsure, you can either refer to <a target="_blank" href="https://developer.apple.com/documentation/bundleresources/privacy_manifest_files">Apple’s documentation</a>, or check out my article that I linked to at the beginning.</p>
<p>Then, inside of your package’s <code>podspec</code> file, you need to add the privacy manifest as a resource there:</p>
<pre><code class="lang-podspec"> s.resource_bundles = {<span class="hljs-string">'package_name_privacy'</span> =&gt; [<span class="hljs-string">'Resources/PrivacyInfo.xcprivacy'</span>]}
</code></pre>
<h2 id="heading-almost-done">Almost Done</h2>
<p>As stated, things are not yet clear on whether the solutions proposed will stand the test of time. </p>
<p>One issue that is currently being investigated is the fact that Flutter packages are under the hood, static frameworks. This can cause issues for application developers. </p>
<p>When using your package, the privacy manifest file can be obscured by top level resources of the application itself. Meaning, the application’s own privacy manifest file might overshadow your package’s privacy manifest file. This in turn, will cause the application to get rejected from the Apple store since it will not perceive that your package does indeed have a privacy manifest file.</p>
<p>According to <a target="_blank" href="https://github.com/flutter/flutter/issues/145269?source=post_page-----52b2da5eabf3--------------------------------#issuecomment-2070221423">this GitHub issue comment</a>, this is a known issue to Apple and they are working on fixing this.</p>
<p>In <a target="_blank" href="https://developer.apple.com/news/?id=pvszzano">Apple’s April 26th announcement</a>, they state that:</p>
<blockquote>
<p>Apps won’t be accepted if they fail to meet the manifest and signature requirements. </p>
</blockquote>
<p>Apps also won’t be accepted if all of the following apply:</p>
<ul>
<li>They’re missing a reason for a listed API.</li>
<li>The code is part of a dynamic framework embedded via the Embed Frameworks build phase.</li>
<li>The framework is a newly added third-party SDK that’s on the list of commonly used third-party SDKs.</li>
</ul>
<p>It seems that things have been limited to dynamic frameworks for the time being, as Apple adds:</p>
<blockquote>
<p>_I_n the future, these required reason requirements will expand to include the entire app binary.__</p>
</blockquote>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Hopefully by now you have the knowledge and the tools to make sure your Flutter package is compatible with Apple's Privacy Manifest changes. </p>
<p>But be sure to keep your eyes and ears peeled for any announcement Apple makes because  more changes to your Flutter package could be needed.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Work on a Multi-Library Project in Android – Locally and Remotely ]]>
                </title>
                <description>
                    <![CDATA[ In this article, we're going to talk about multi-library projects in Android. It's not something ordinary, but not something out of the ordinary either.  You may have come across multi-library projects in your line of work, or you may be looking into... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/working-on-a-multiple-library-project-in-android/</link>
                <guid isPermaLink="false">66ba50548e44e0cdf1281256</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Sat, 27 Apr 2024 22:37:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/04/sandy-millar-5PCeHBkMCmk-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, we're going to talk about multi-library projects in Android. It's not something ordinary, but not something out of the ordinary either. </p>
<p>You may have come across multi-library projects in your line of work, or you may be looking into converting your library into sub-modules for better structure and organization. No matter the case, you should be well aware of what lies in front of you before diving in.</p>
<p>Writing your own library in Android is neat. You get a chance to write some code that can help other developers (or even yourself). </p>
<p>Since libraries can’t be a standalone project by themselves, they are usually always paired in a project with an application. This allows developing the library to be a simple process where you add a feature/fix a bug and then you can test it directly with the application you have in the project. Thus, simulating (in a local way) how a developer will integrate your library.</p>
<p>But, what if your library relies on another library you are developing?</p>
<p>If you are not aware of it, you should know that a library (read aar) cannot contain another local library within it. It can rely on libraries remotely (via dependencies), but not on something local. </p>
<p>This is not supported in Android, and while some solutions popped up during the years (<a target="_blank" href="https://github.com/kezong/fat-aar-android">FatAar</a>), these didn’t always solve the problem and are not up to date. There is even a <a target="_blank" href="https://issuetracker.google.com/issues/62121508?pli=1">Google Issue Tracker</a> requesting this feature that has been open for quite some time and is receiving plenty of attention from the community. But let’s identify which walls we can break and which we cannot.</p>
<p>Imagine your project hierarchy looks like this:</p>
<pre><code>-- App
|
 -- OuterLib
   |
    --- InnerLib
</code></pre><p>So, since InnerLib can’t be part of your original project, where can it reside? And also how would you be able to work locally while developing features inside InnerLib?</p>
<p>We are going to answer these questions in this article.</p>
<h2 id="heading-git-submodule">Git Submodule</h2>
<p>For most technical problems, there isn’t always just one solution. Usually, there are more, but each solution has its drawbacks. It's all a question of which drawbacks you are more comfortable living with at the end of the day.</p>
<p>To answer our first question, where can InnerLib reside, we have several options:</p>
<ol>
<li>Make InnerLib a submodule of our original project</li>
<li>Make InnerLib a remote dependency of its own</li>
</ol>
<p>If you are not aware of submodules in Git, <a target="_blank" href="https://git-scm.com/book/en/v2/Git-Tools-Submodules">Git’s documentation</a> is a good place to familiarize yourself with them. Quoting from it (the first paragraph):</p>
<blockquote>
<p>It often happens that while working on one project, you need to use another project from within it. 👉 Perhaps it’s a library that a third party developed or that you’re developing separately and using in multiple parent projects. 👈 A common issue arises in these scenarios: you want to be able to treat the two projects as separate yet still be able to use one from within the other.</p>
</blockquote>
<p>This paragraph shows us that this is exactly our use case. Using a submodule has its benefits. All your code is in one place, is easy to manage, and is easy to develop locally. </p>
<p>But submodules have some weaknesses. One is the fact that you must always be aware of which branch your submodule is pointing to. Imagine a scenario where you are on a release branch in your main repository and your sub-module is on a feature branch. If you don’t notice, you release a version of your code with something that is not ready for production. Whoops.</p>
<p>Now think about this within a team of developers. One careless mistake can be costly.</p>
<p>If the first option sounds problematic for you, then hosting your library in another repository is your second choice. Setting up the repository is pretty simple, but how do you work locally now?</p>
<h2 id="heading-working-locally">Working Locally</h2>
<p>Now that we've gotten our project set up properly, we will probably have a line similar to this in our OuterLib build.gradle file:</p>
<pre><code>dependencies {
  implementation <span class="hljs-string">'url_to_remote_inner_lib_repository'</span>
}
</code></pre><p>How can we make the development cycle efficient and easy to work with? If we develop some feature in InnerLib, how do we test things out in OuterLib? Or in our application?</p>
<p>One solution that might come up is to import our InnerLib locally to our OuterLib project, while having InnerLib .gitignored in our OuterLib project. You can do so easily by right clicking on the name of the project in the left hand side menu in Android Studio and going to New → Module.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>How to import a module (Step 1)</em></p>
<p>Then in the window that opens up, you can choose the Import option at the bottom left:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/1-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>How to import a module (Step 2)</em></p>
<p>That sounds easy and simple so far, but what’s the catch?</p>
<p>Each time you modify a file that belongs to InnerLib, the changes won’t be reflected inside InnerLib since it is ignored. So, each change you want to make has to happen inside of InnerLib and then you have to import it again inside OuterLib to see the changes.</p>
<p>This doesn’t seem right. There must be a better way of doing this.</p>
<p>With just a few lines in our <strong>settings.gradle</strong> file, we can make sure our files stay in sync when we make changes in InnerLib. </p>
<p>When we imported InnerLib into our project, Android Studio made a copy of InnerLib and cached it. That is why we needed to re-import the library for every change we made inside of it. We can tell Android Studio where to reference the files from using the <strong>projectDir</strong> attribute. </p>
<p>Our settings.gradle might look something like this:</p>
<pre><code>include <span class="hljs-string">':outerLib'</span>, <span class="hljs-string">':innerLib'</span>, <span class="hljs-string">':app'</span>
</code></pre><p>To reference our InnerLib locally, we would have to change settings.gradle into this:</p>
<pre><code>include <span class="hljs-string">':outerLib'</span>, <span class="hljs-string">':innerLib'</span>, <span class="hljs-string">':app'</span>
project(<span class="hljs-string">'innerLib'</span>).projectDir = <span class="hljs-keyword">new</span> File(<span class="hljs-string">'PATH_TO_INNER_LIB'</span>)
</code></pre><p>Using this approach, our InnerLib files will be linked to our working directory, so every change we make will be reflected immediately. </p>
<p>But, we would like flexibility when working locally on OuterLib with a remote version of InnerLib. What we wrote above inside the settings.gradle file will only allow us to work locally and surely we don’t want to commit that as it is.</p>
<h2 id="heading-maven-local">Maven Local</h2>
<p>If the approach above doesn’t sit quite right with you, there is a different one you can take. Just like you would publish your library publicly with maven, you can do the same thing locally with maven local. Maven local is a set of repositories that sit locally on your machine.</p>
<p>Below are the paths for mavenLocal depending on the operating system of your machine:</p>
<ul>
<li>Mac → /Users/YOUR_USERNAME/.m2</li>
<li>Linux → /home/YOUR_USERNAME/.m2</li>
<li>Windows → C:\Users\YOUR_USERNAME.m2</li>
</ul>
<p>In essence you can publish your library locally and then link to it in your project. Doing it this way, we can link our project to InnerLib. </p>
<p>In order to allow this configuration in our project, we need to do the following things:</p>
<ol>
<li>Add <strong><em>mavenLocal()</em></strong> as a repository inside our repositories clause. This is to allow our project the ability to search for repositories locally</li>
</ol>
<pre><code>buildscript {
    repositories {
        mavenLocal()
    }
}

...

allprojects { 
    repositories { 
        mavenLocal() 
    }
}
</code></pre><ol start="2">
<li><p>Change our implementation line inside our dependencies clause to reference our InnerLib as if it we are referencing it remotely</p>
</li>
<li><p>To publish InnerLib locally, we will create a file called publishingLocally.gradle that will contain the following:</p>
</li>
</ol>
<pre><code>apply plugin: <span class="hljs-string">'maven-publish'</span> 

project.afterEvaluate {
    publishing { 
      publications {
            library(MavenPublication) { 
                    setGroupId groupId          <span class="hljs-comment">//your library package</span>
                    setArtifactId artifactId              
                    version versionName         <span class="hljs-comment">//I.E. 1.0</span>

                    artifact bundleDebugAar

                    pom.withXml { 
                        def dependenciesNode = asNode().appendNode(<span class="hljs-string">'dependencies'</span>)
                        def dependencyNode = dependenciesNode.appendNode(<span class="hljs-string">'dependency'</span>)
                        dependencyNode.appendNode(<span class="hljs-string">'groupId'</span>, <span class="hljs-string">'your_group_id'</span>)
                        dependencyNode.appendNode(<span class="hljs-string">'artifactId'</span>, <span class="hljs-string">'your_artificat_id'</span>)
                        dependencyNode.appendNode(<span class="hljs-string">'version'</span>, <span class="hljs-string">'your_version'</span>)
                    } 
                }
            }
        }
}
</code></pre><ol start="4">
<li>Inside your application level build.gradle file, add the line:</li>
</ol>
<pre><code>apply <span class="hljs-keyword">from</span>: <span class="hljs-string">'/.publishingLocally.gradle</span>
</code></pre><p>If this option seems a bit too good to be true, <strong>it is</strong>. While on one hand, we can develop things locally seamlessly just as if we were working with a remote library. On the other, if we make any change inside InnerLib while working locally, it is required to publish it locally again. While this isn’t a costly task, it does create a need to perform tedious tasks over and over.</p>
<h2 id="heading-a-solution-for-working-locally-and-remotely">A Solution for Working Locally and Remotely</h2>
<p>We want to avoid the constant need to re-publish our InnerLib package whenever we make a change locally. We need to figure out a way to make our project be aware of those changes. </p>
<p>In the Working Locally section, we found out how to do that, but we had an issue with committing the settings.gradle file. To solve this problem so we can work both locally and remotely with our InnerLib, we will use a parameter we will define in our <strong><em>gradle.properties</em></strong> file.</p>
<p>The gradle.properties file is a place where you can store project level settings that configure your development environment. This helps make sure that all the developers on a team have a consistent development environment. </p>
<p>Some settings you might be familiar with that are found inside this file are AndroidX support (android.useAndroidX=true) or the JVM arguments (org.gradle.jvmargs=-Xmx1536m). </p>
<p>To help us solve our situation, we can add a parameter here to indicate whether we want to work locally or not. Something along the lines of:</p>
<pre><code>workingLocally = <span class="hljs-literal">false</span>
</code></pre><p>This parameter will grant us the ability to distinguish between which settings we are working with, either locally or with production code. First, let’s alter what we have in our settings.gradle file by wrapping it in a condition that checks if our parameter is true:</p>
<pre><code>include <span class="hljs-string">':outerLib'</span>, <span class="hljs-string">':innerLib'</span>, <span class="hljs-string">':app'</span>
<span class="hljs-keyword">if</span> (workingLocally.booleanValue()) {
  project(<span class="hljs-string">'innerLib'</span>).projectDir = <span class="hljs-keyword">new</span> File(<span class="hljs-string">'PATH_TO_INNER_LIB'</span>)
}
</code></pre><p>This way, we indicate to the project to get the files for our InnerLib locally from our machine. </p>
<p>Another place where we need to change our logic is in our build.gradle file. Here, instead of getting the code to our library remotely in our dependencies block, we can indicate whether we are depending on it locally or not.</p>
<pre><code>dependencies {
   <span class="hljs-keyword">if</span> (workingLocally.booleanValue()) {
      implementation <span class="hljs-string">'innerLib'</span>
   } <span class="hljs-keyword">else</span> {
     implementation <span class="hljs-string">'url_to_remote_repository'</span>
  }
}
</code></pre><blockquote>
<p><em>⚠️ Word of warning: You should never commit the gradle.properties file when working locally.</em></p>
</blockquote>
<p>The journey was long and may have seemed quite exhausting. But now we have a full-proof setup for working locally and remotely on a multiple library project.</p>
<p>If you encounter any issues or would like to give your take on this, feel free to leave a comment.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Test Proto DataStore ]]>
                </title>
                <description>
                    <![CDATA[ In a previous article, I described how you can use Proto DataStore in your application. I wrote that article as part of my experience in using Proto DataStore in one of my applications. Following that, I wanted to see what it would be like to write t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/testing-proto-datastore/</link>
                <guid isPermaLink="false">66ba503f43a51af2a76f7570</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Mon, 08 Apr 2024 20:54:23 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/04/alexander-schimmeck-QX0SWFpB2ho-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a target="_blank" href="https://medium.com/proandroiddev/android-proto-datastore-should-you-use-it-36ae997d00f2">In a previous article</a>, I described how you can use Proto DataStore in your application. I wrote that article as part of my experience in using Proto DataStore in <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.todo">one of my applications</a>.</p>
<p>Following that, I wanted to see what it would be like to write tests for the Proto DataStore in that application using the knowledge I gained. </p>
<p>Searching online for guidance didn’t provide much relief, so I figured I would share my knowledge for those that might be looking for it. In the worst case, it would be for my progeny.</p>
<p>In my search, I did find <a target="_blank" href="https://medium.com/androiddevelopers/datastore-and-testing-edf7ae8df3d8">this article</a>, but that focuses mostly on testing the Preferences DataStore and not the Proto DataStore. It does state that:</p>
<blockquote>
<p>“However, keep in mind you can use this material for setting up <a target="_blank" href="https://developer.android.com/codelabs/android-proto-datastore#0"><strong>Proto DataStore</strong></a> testing, as it would be very similar to Preferences.”</p>
</blockquote>
<p>But having followed it, I found out that besides the dependencies, there aren’t many similarities here and you need to introduce separate logic to test your own Proto DataStore.</p>
<h2 id="heading-setting-things-up">Setting Things Up</h2>
<p>In your application’s build.gradle file, add the following dependencies:</p>
<pre><code class="lang-groovy">dependencies {
  ///.....
  androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
  debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
</code></pre>
<p><strong><code>$compose_version</code></strong> is the variable you defined in your project level build.gradle file.</p>
<p>Then, go to your <strong><code>androidTest</code></strong> directory and create a new file. Usually, you would have a repository class that interacts with your Proto DataStore, so you can name this file as YourRepositoryClassNameTest. We will use the name <strong><code>MyRepositoryTest</code></strong>.</p>
<p>Before we delve into testing the Proto DataStore itself, we need to instantiate it. If you go online to find any documentation on this, it is kind of sparse. </p>
<p>Instantiating a Proto DataStore is used with the global Context like so (when not used in a testing scenario):</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> Context.myDataStore: DataStore&lt;MyItem&gt; <span class="hljs-keyword">by</span> dataStore(
                 fileName = DATA_STORE_FILE_NAME,
                 serializer = MyItemSerializer
 )
</code></pre>
<p>Well, you can't do this inside of a test class, because, while you can copy-paste the above code, <strong>you won’t be able to</strong> <strong>access</strong> the DataStore object. You can get the application context like this:</p>
<pre><code class="lang-kotlin">ApplicationProvider.getApplicationContext()
</code></pre>
<p>but our <code>myDataStore</code> object won’t be available through it.</p>
<p>So, what can we do?</p>
<p>In the article linked above, there is an example of how we can create a Preference DataStore using the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/datastore/preferences/core/PreferenceDataStoreFactory#create(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler,kotlin.collections.List,kotlinx.coroutines.CoroutineScope,kotlin.Function0)">PreferenceDataStoreFactory.create</a> method.</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">create</span><span class="hljs-params">(    
            corruptionHandler: <span class="hljs-type">ReplaceFileCorruptionHandler</span>&lt;<span class="hljs-type">Preferences</span>&gt;? = <span class="hljs-literal">null</span>,    
            migrations: <span class="hljs-type">List</span>&lt;<span class="hljs-type">DataMigration</span>&lt;<span class="hljs-type">Preferences</span>&gt;&gt; = listOf()</span></span>,
            scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),    
            produceFile: () -&gt; File): DataStore&lt;Preferences&gt;
</code></pre>
<p>But since we are not using a Preference DataStore, that won’t work for us. What will work is using the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/datastore/core/DataStoreFactory#create(androidx.datastore.core.Serializer,androidx.datastore.core.handlers.ReplaceFileCorruptionHandler,kotlin.collections.List,kotlinx.coroutines.CoroutineScope,kotlin.Function0)">DataStoreFactory.create</a> method like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T : Any?&gt;</span> <span class="hljs-title">create</span><span class="hljs-params">(    
    serializer: <span class="hljs-type">Serializer</span>&lt;<span class="hljs-type">T</span>&gt;,   
    corruptionHandler: <span class="hljs-type">ReplaceFileCorruptionHandler</span>&lt;<span class="hljs-type">T</span>&gt;? = <span class="hljs-literal">null</span>,    
    migrations: <span class="hljs-type">List</span>&lt;<span class="hljs-type">DataMigration</span>&lt;<span class="hljs-type">T</span>&gt;&gt; = listOf()</span></span>,    
    scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),     produceFile: () -&gt; File): DataStore&lt;T&gt;
</code></pre>
<p>There are several arguments for this method (and some have default values), but we don’t need to pass all of them in. We will be passing:</p>
<ul>
<li>Our serializer class</li>
<li>A lambda method for creating the file for our Proto DataStore</li>
</ul>
<pre><code class="lang-kotlin">dataStore = DataStoreFactory.create(   
        produceFile = {                    
            testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME)                    },            
        serializer = MyItemSerializer 
      )
</code></pre>
<p>We get the <code>testContext</code> by:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> testContext: Context = ApplicationProvider.getApplicationContext()
</code></pre>
<h2 id="heading-how-to-test-the-datastore">How to Test the DataStore</h2>
<p>Having created our Proto DataStore successfully, we can move on to writing some tests for it. Keep in mind that you have a repository class that receives the instance of the Proto DataStore as a dependency, so after creating the Proto DataStore, we need to create an instance of our repository class.</p>
<pre><code class="lang-kotlin"> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> repository = MyRepository(datastore)
</code></pre>
<p>First, let’s create a test to check our initial Proto DataStore state. The Proto DataStore itself exposes a flow which we can use.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalCoroutinesApi::class)</span>
    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">repository_testFetchInitialState</span><span class="hljs-params">()</span></span> {
        runTest {
            testScope.launch {
                <span class="hljs-keyword">val</span> dataStoreObject = repository.myFlow.first()
                <span class="hljs-comment">// Insert here whatever we want to assert from our</span>
                <span class="hljs-comment">// Proto DataStore. I.E. a flag whose initial value is false</span>
                assert(dataStoreObject.myFlag == <span class="hljs-literal">false</span>)  
            }
        }
    }
</code></pre>
<p>☝️ You may have noticed this earlier, but we are using a <strong>OptIn annotation</strong> here. This is because (currently) the APIs which we are using are experimental and must be marked so when we use them.</p>
<p>Since we are accessing the flow of our <code>DataStore</code>, we need to wrap it in our <code>testScope</code>. <code>TestScope</code> is created by doing:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalCoroutinesApi::class)</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> dispatcher = TestCoroutineDispatcher()
<span class="hljs-meta">@OptIn(ExperimentalCoroutinesApi::class)</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> testScope = TestCoroutineScope(dispatcher)
</code></pre>
<p>Run it and enjoy your first Proto DataStore test.</p>
<p>That was fun for about two seconds.</p>
<p>Let’s do something more meaningful.</p>
<p>Imagine our Proto DataStore has a list of objects inside of it and we want to test the state of it when we add an item to it.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalCoroutinesApi::class)</span>
    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">repository_testAdditionOfItem</span><span class="hljs-params">()</span></span> {
        runTest {
            testScope.launch {
              <span class="hljs-comment">//1</span>
               <span class="hljs-keyword">val</span> item: MyItem = MyItem.newBuilder().setItemId(UUID.randomUUID().toString())
                    .setItemDescription(TEST_ITEM_DESCRIPTION).build()
                <span class="hljs-comment">//2</span>
                repository.updateItem(item)

                <span class="hljs-comment">//3</span>
                <span class="hljs-keyword">val</span> items = repository.myFlow.first().itemsList
                assert(items.size == <span class="hljs-number">1</span>)

                <span class="hljs-comment">//4</span>
                assert(items[<span class="hljs-number">0</span>].itemDescription.equals(TEST_ITEM_DESCRIPTION))
            }
        }
    }
</code></pre>
<ol>
<li>We create a test item using the exposed API from the protobuff</li>
<li>We add this item to the Proto DataStore using a method we exposed on the MyRepository class</li>
<li>We grab the list of items from the flow the Proto DataStore exposes</li>
<li>We make sure that the item found in the Proto DataStore matches the item we created earlier</li>
</ol>
<h2 id="heading-your-datastore-has-a-leak-in-it">Your DataStore Has a Leak in it</h2>
<p>If you try to run the tests above in one go, you will soon receive an error during runtime:</p>
<blockquote>
<p>There are multiple DataStores active for the same file: /data/user/0/com.example.app/files/datastore/dataStore_filename.pb. You should either maintain your DataStore as a singleton or confirm that there is no two DataStore’s active on the same file (by confirming that the scope is cancelled).</p>
</blockquote>
<p>Well, that is problematic. We only created one DataStore instance in our test class.</p>
<p>What is going on here?</p>
<p>Because we are not using <a target="_blank" href="https://developer.android.com/topic/libraries/architecture/datastore#preferences-create">the property delegate</a> to create our DataStore (meaning Context.datastore), it isn’t ensured that our DataStore object is a singleton whenever we access it.</p>
<p>To circumvent this scenario, I have found out that one approach is to delete and recreate the DataStore for each test case. To delete the DataStore, we can do this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@After</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">cleanup</span><span class="hljs-params">()</span></span> {
  File(testContext.filesDir, <span class="hljs-string">"datastore"</span>).deleteRecursively()
}
</code></pre>
<p>and before every test, we recreate it:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Before</span>
 <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span></span> {
    dataStore = DataStoreFactory.create(
        produceFile = {
            testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME)
        },
        serializer = MyItemSerializer
    )
 }
</code></pre>
<p>To see a full example, you can go <a target="_blank" href="https://github.com/TomerPacific/Todo/blob/master/app/src/androidTest/java/com/tomerpacific/todo/TodoItesmRepositoryTest.kt">here</a>.</p>
<p>In this article, I wanted to show the outline of a how a Proto DataStore can be tested. </p>
<p>While I went over two test cases, depending on your DataStore and the types you configured there, there could be more test cases and scenarios to write. The building blocks are there, you just have to adapt it to your needs.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What the iOS Privacy Manifest Means for Developers ]]>
                </title>
                <description>
                    <![CDATA[ At WWDC 2023, Apple introduced us to a new bundle resource that is going to be added to every application and library: the privacy manifest. A lot has been written since then about this subject but without that much clarity. When first announced, App... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-the-ios-privacy-manifest-means-for-developers/</link>
                <guid isPermaLink="false">66ba504cf8a814ef73b78bd2</guid>
                
                    <category>
                        <![CDATA[ privacy ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Fri, 15 Mar 2024 16:56:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/pawel-czerwinski-jj4LC7iKA6Q-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a target="_blank" href="https://developer.apple.com/videos/play/wwdc2023/10060">At WWDC 2023</a>, Apple introduced us to a new bundle resource that is going to be added to every application and library: the privacy manifest. A lot has been written since then about this subject but without that much clarity.</p>
<p>When first announced, Apple stated that in the Spring of 2024 (read – spring is already here), having a privacy manifest is expected and will be part of <a target="_blank" href="https://developer.apple.com/distribute/app-review/">the application review process</a>. Apple also asks application developers, as well as SDK developers, to adopt the privacy manifest.</p>
<p>Fast forward to December 7th, 2023, <a target="_blank" href="https://developer.apple.com/news/?id=r1henawx#:~:text=Third%2Dparty%20SDK%20privacy%20manifest%20and%20signatures.&amp;text=Starting%20in%20spring%202024%2C%20if,used%20as%20a%20binary%20dependency.">Apple announced</a> a list of “commonly used third-party SDKs” that, if included by your application, you have to have a privacy manifest for. No real explanation has been given as to why the third-party SDKs listed are the ones that have been selected, but there has been much speculation about it.</p>
<p>And here we stand after February 29th, 2024 (on a leap day!), when <a target="_blank" href="https://developer.apple.com/news/?id=3d8a9yyh">Apple announced</a> a timeline for enforcing the required reason API section of the privacy manifest.</p>
<p>All of this has led to quite a bit of confusion from developers who are scrambling to understand if their application or SDK falls into a privacy manifest category.</p>
<p>Developers are unsure if they should add a privacy manifest to their SDK, even if it is not listed. The <a target="_blank" href="https://developer.apple.com/documentation/bundleresources/privacy_manifest_files">documentation</a> itself, while being adept at giving an outline of everything, lacks the necessary distinctions and details developers are looking for.</p>
<p>Part of me wants to say that Apple is keeping things vague since the near future has coming changes that privacy manifests will bring. Another part of me says that Apple has always been this vague, and it is just their modus operandi.</p>
<p>In any case, you are reading this article because you want to understand how all of this affects you. So, let’s break things down.</p>
<blockquote>
<p><em>⚠️ Disclaimer: This article won’t deal with explaining what the privacy manifest is or how to add it to your application/library, as that has been covered by Apple's documentation fairly well.</em></p>
</blockquote>
<h2 id="heading-the-four-horsemen">The Four Horsemen</h2>
<p>Privacy manifest is divided into four subjects:</p>
<ul>
<li>NSPrivacyTracking.</li>
<li>NSPrivacyTrackingDomains.</li>
<li>NSPrivacyCollectedDataTypes (nutrition labels).</li>
<li>NSPrivacyAccessedAPITypes (required reason APIs).</li>
</ul>
<p>The first two are tied together and can pose the most substantial changes to your application/library, so we’ll ease into this list by starting with number three.</p>
<h3 id="heading-what-are-nsprivacycollecteddatatypes">What are NSPrivacyCollectedDataTypes?</h3>
<p>This section holds various categories of data collection that if your application or SDK does something with, you have to declare them. </p>
<p>Each type of data collected must be supplied with the reason for collecting it. </p>
<p>The categories range from contact information about the user (such as email/phone number), to Location and Purchases. </p>
<p>Inside your privacy manifest file, you will have an array of NSPrivacyCollectedDataTypes, where each item will hold:</p>
<ul>
<li>The type of data collected.</li>
<li>Whether or not this data is linked to the user.</li>
<li>Whether or not this data is used to track the user.</li>
<li>The reason(s) for collecting this data.</li>
</ul>
<p>Let’s do one as an example. Imagine your application collects the precise location of a user in order to track the user’s movement to see if the user is nearby any specific stores. </p>
<p>If the user is nearby such a store, you present a relevant ad to them. Factoring all that you will need to create an entry where:</p>
<ul>
<li>The data type will be NSPrivacyCollectedDataTypePreciseLocation.</li>
<li>Mark true as we are linking the data to the user.</li>
<li>Mark true as we are tracking the user with this data.</li>
<li>Since we are going to display ads to the user, we will choose. NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising, NSPrivacyCollectedDataTypePurposeProductPersonalization, and NSPrivacyCollectedDataTypePurposeAppFunctionality since all of those fit for the data we collect.</li>
</ul>
<h3 id="heading-what-are-nsprivacyaccessedapitypes">What are NSPrivacyAccessedAPITypes?</h3>
<p>As mentioned, this section gets a bit more obscure and a bit more demanding.</p>
<p>Here, Apple lists very specific APIs from different categories that if you happen to use, you need to declare them. </p>
<p>For every API listed, there are specific reasons you need to fall into in order to declare your use of it. Some reasons state clearly that even if you use the API, you cannot send the data received by this API to a server (off-device). </p>
<p>If you find that your application or SDK uses one of the listed APIs, then you need to list it with an appropriate reason(s). For example, if we use the example from the previous section, our application reads and writes data to user defaults that has to do with the user’s location. So, we will need to:</p>
<ul>
<li>List NSPrivacyAccessedAPICategoryUserDefaults as the NSPrivacyAccessedAPIType.</li>
<li>Use CA92.1 inside the NSPrivacyAccessedAPITypeReasons.</li>
</ul>
<p>If you think you don’t see the reason you are using an API for, <a target="_blank" href="https://idmsa.apple.com/IDMSWebAuth/signin.html?path=%2Fcontact%2Frequest%2Fprivacy-manifest-reason%2F&amp;appIdKey=891bd3417a7776362562d2197f89480a8547b108fd934911bcbea0110d07f757&amp;rv=0">you can let Apple know about it</a>.</p>
<blockquote>
<p>🎯 None of the APIs listed can be used for tracking the user.</p>
</blockquote>
<p>At last, we come to the two most problematic categories.</p>
<h3 id="heading-what-are-the-nsprivacytracking-and-nsprivacytrackingdomains">What are the NSPrivacyTracking and NSPrivacyTrackingDomains?</h3>
<p>What is tracking? Do you know? Does anyone know? It really doesn’t matter, because <a target="_blank" href="https://developer.apple.com/app-store/app-privacy-details/#user-tracking">Apple has a definition for it</a>:</p>
<blockquote>
<p><em>“Tracking” refers to linking data collected from your app about a particular end-user or device, such as a user ID, device ID, or profile, with Third-Party Data for targeted advertising or advertising measurement purposes, or sharing data collected from your app about a particular end-user or device with a data broker.</em></p>
</blockquote>
<p>So, if your application or SDK doesn’t fall into that definition, you need to mark false as the value for NSPrivacyTracking and you can exhale. </p>
<p>Because if you have to mark true as the NSPrivacyTracking, then you must supply all the domains your application or SDK uses for the purpose of tracking as part of NSPrivacyTrackingDomains.</p>
<p>By now, you must be asking yourself, why I am making a big fuss about this. Well, it has to do with the fact that Apple will block all requests to any domain listed under NSPrivacyTrackingDomains if the user doesn’t allow the application to track him/her.</p>
<p>Read the paragraph above again.</p>
<p>Get it? You will now need to re-route network requests differently based on whether the user has given consent to be tracked or not. </p>
<p>On the client side (application/library), this might be a small change to handle. But on the server/infrastructure side, this might require some heavy lifting as new domains (or sub domains), need to be created. </p>
<p>Data that has been aggregating a certain way, now needs to be aggregated from another source. You obviously also need to make sure that no tracking related data is sent to your newly created domains. You wouldn’t want to end up in a scenario where your application/library stops working entirely.</p>
<p>To assist you in understanding which domains fall into the tracking category, you can use <a target="_blank" href="https://developer.apple.com/documentation/xcode/detecting-when-your-app-contacts-domains-that-may-be-profiling-users">Instruments</a>. Be aware that if your domains do not fall into that category now, it doesn’t mean that they won’t fall into it later.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As with any new regulation or policy, many questions are still left unanswered:</p>
<ul>
<li>If my application has a webview, where some network requests are made, do I have to include those as domains for NSPrivacyTrackingDomains?</li>
<li>Are sub domains good enough or do developers need to create completely different domains?</li>
<li>If my library is not listed as part of the commonly used SDKs, is there a chance that it might be in the future? What is the criteria used for listing such SDKs?</li>
<li>Do I have to include a signature to my SDK even if it is not listed under the commonly used SDKs?</li>
</ul>
<p>Also, when looking at the current state of things in the developer community, the response is quite the same. At the time of writing this article, numerous SDKs that are listed in Apple’s list, still haven’t released a version with a privacy manifest.</p>
<p>As we will get closer to the date of when it will be mandatory to have a privacy manifest, hopefully more details will emerge and better clarity. Until then, brace yourselves.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Flutter Tutorial – How to Migrate to V2 Embedding ]]>
                </title>
                <description>
                    <![CDATA[ If you hopped on the Flutter bandwagon in it’s early days, most likely you have a project or two that were created prior to version 1.12 of Flutter. If that is the case, you may have seen this message whenever you run Pub get in one of your projects:... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/flutter-migrating-to-v2-embedding/</link>
                <guid isPermaLink="false">66ba4ff68e44e0cdf1281250</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Mon, 29 Jan 2024 18:35:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/nick-fewings-J54DjpXYJuE-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you hopped on the Flutter bandwagon in it’s early days, most likely you have a project or two that were created prior to version 1.12 of Flutter. If that is the case, you may have seen this message whenever you run Pub get in one of your projects:</p>
<blockquote>
<p>This app is using a deprecated version of the Android embedding.<br>To avoid unexpected runtime failures, or future build failures, try to migrate this app to the V2 embedding.  </p>
<p>Take a look at the docs for migrating an app: <a target="_blank" href="https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects">https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects</a></p>
</blockquote>
<p>Now, the document itself has the steps you need to follow to make this warning disappear, but it does not always clarify what to change and where. </p>
<p>This article will give you a step by step walkthrough on migrating your Flutter application to V2 Embedding so you can make that warning go away for good.</p>
<h2 id="heading-automatic-migration-the-easy-way-out">Automatic Migration – The Easy Way Out</h2>
<p>It needs to be said that you can forego this process of migration if your application can be easily recreated. So what does that mean? </p>
<p>Well, if the code in your application is not complex, then you can just save the files in your lib folder and create a new project using <strong><code>flutter create</code></strong>. That way, you will have a project that is already migrated to V2 Embedding and will just need to copy paste the code you have in your lib folder.</p>
<p>But, if your project is more complex – let’s say it's a package that has platform specific code – you will probably be better off by migrating it manually.</p>
<h2 id="heading-manual-migration-follow-these-steps">Manual Migration – Follow These Steps</h2>
<ol>
<li>Open the <strong>MainActivity</strong>.kt (or .java) file in your application</li>
<li>You need to remove any content this file has and just leave it bare with a class declaration (unless you have specific logic there).</li>
<li>Remove all of the imports and make sure to have one import that is this:</li>
</ol>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> io.flutter.embedding.android.FlutterActivity;
</code></pre>
<p>The end result should be as follows:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> io.flutter.embedding.android.FlutterActivity;
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">FlutterActivity</span> </span>{   
    <span class="hljs-comment">// Nothing should be here</span>
}
</code></pre>
<ol start="4">
<li>Open the AndroidManifest.xml file and change the name attribute under the application tag to <strong><code>${applicationName}</code></strong> – so it looks like this: </li>
</ol>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">application</span>      
    <span class="hljs-attr">android:name</span>=<span class="hljs-string">"${applicationName}"</span>&gt;</span> 
     .... 
<span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span>
</code></pre>
<ol start="5">
<li>You need to add the following meta data inside your application tags:</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta-data</span>           
     <span class="hljs-attr">android:name</span>=<span class="hljs-string">"flutterEmbedding"</span>       
     <span class="hljs-attr">android:value</span>=<span class="hljs-string">"2"</span> /&gt;</span>
</code></pre>
<ol start="6">
<li>If you want a specific Splash screen behavior, you will need to remove the Splash screen meta tag:</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta-data</span>                     <span class="hljs-attr">android:name</span>=<span class="hljs-string">"io.flutter.app.android.SplashScreenUntilFirstFrame"</span>                <span class="hljs-attr">android:value</span>=<span class="hljs-string">"true"</span> /&gt;</span>
</code></pre>
<ol start="7">
<li>Then head to your styles.xml file and configure the LaunchTheme there with the drawable of your liking:</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">resources</span>&gt;</span>    
     <span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"LaunchTheme"</span> <span class="hljs-attr">parent</span>=<span class="hljs-string">"@android:style/Theme.Black.NoTitleBar"</span>&gt;</span><span class="xml">        <span class="hljs-tag">&lt;<span class="hljs-name">item</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"android:windowBackground"</span>&gt;</span>@drawable/launch_background
        <span class="hljs-tag">&lt;/<span class="hljs-name">item</span>&gt;</span>   
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">resources</span>&gt;</span>
</code></pre>
<p>Your AndroidManifest.xml will look something like this after all the changes above:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">manifest</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>    <span class="hljs-attr">package</span>=<span class="hljs-string">"PACKAGE_NAME"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">application</span>        
    <span class="hljs-attr">android:name</span>=<span class="hljs-string">"${applicationName}"</span>    
    <span class="hljs-attr">android:label</span>=<span class="hljs-string">"APPLICATION_LABEL"</span>      
    <span class="hljs-attr">android:icon</span>=<span class="hljs-string">"@mipmap/ic_launcher"</span>&gt;</span>     
        <span class="hljs-tag">&lt;<span class="hljs-name">activity</span>           
            <span class="hljs-attr">android:name</span>=<span class="hljs-string">".MainActivity"</span>
            <span class="hljs-attr">android:exported</span>=<span class="hljs-string">"true"</span>   
            <span class="hljs-attr">android:launchMode</span>=<span class="hljs-string">"singleTop"</span>     <span class="hljs-attr">android:theme</span>=<span class="hljs-string">"@style/LaunchTheme"</span>                <span class="hljs-attr">android:configChanges</span>=<span class="hljs-string">"orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"</span>            <span class="hljs-attr">android:hardwareAccelerated</span>=<span class="hljs-string">"true"</span>            <span class="hljs-attr">android:windowSoftInputMode</span>=<span class="hljs-string">"adjustResize"</span>&gt;</span>     
                <span class="hljs-tag">&lt;<span class="hljs-name">intent-filter</span>&gt;</span>    
                    <span class="hljs-tag">&lt;<span class="hljs-name">action</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.intent.action.MAIN"</span>/&gt;</span>                                <span class="hljs-tag">&lt;<span class="hljs-name">category</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.intent.category.LAUNCHER"</span>/&gt;</span>  
                    <span class="hljs-tag">&lt;/<span class="hljs-name">intent-filter</span>&gt;</span>     
       <span class="hljs-tag">&lt;/<span class="hljs-name">activity</span>&gt;</span>     
       <span class="hljs-tag">&lt;<span class="hljs-name">meta-data</span>       
               <span class="hljs-attr">android:name</span>=<span class="hljs-string">"flutterEmbedding"</span>         
            <span class="hljs-attr">android:value</span>=<span class="hljs-string">"2"</span> /&gt;</span> 
 <span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">manifest</span>&gt;</span>
</code></pre>
<h2 id="heading-androidx-support">AndroidX Support</h2>
<p>Your project might also be needed to be migrated to use AndroidX libraries instead of the older support libraries. You will be alerted for this when you build and run your application:</p>
<blockquote>
<p>Your app isn’t using AndroidX. To avoid potential build failures, you can quickly migrate your app by following the steps on <a target="_blank" href="https://goo.gl/CP92wY">https://goo.gl/CP92wY</a>.</p>
</blockquote>
<p>Fixing this is rather simple, as Android Studio has built in support for migrating to AndroidX.</p>
<p>Start by opening the Android folder of your Flutter application as a standalone project</p>
<p>Click on Refactor → Migrate to AndroidX:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/migration.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Dropdown menu to Migrate to AndroidX</em></p>
<p>You will then be prompted to save a copy of your project and past that, the migration process will take place.</p>
<h3 id="heading-errors-you-might-see">Errors You Might See</h3>
<p>During this migration process, you might encounter several errors when building your application. The most prominent ones are:</p>
<ul>
<li><em>Unable to get mutable Windows environment variable map</em></li>
<li><em>cvc-complex-type.2.4.a: Invalid content was found starting with element ‘base-extension’. One of ‘{layoutlib}’ is expected</em></li>
<li><em>Warning: This version only understands SDK XML versions up to 2 but an SDK XML file of version 3 was encountered. This can happen if you use versions of Android Studio and the command-line tools that were released at different times</em></li>
</ul>
<p>The first two errors are related to each other and both stem from the same root cause. It is because your project was set up with an old Gradle version and it is needed to upgrade it. </p>
<p>To do so, follow these steps:</p>
<ol>
<li>Open the Android folder in your Flutter application as a standalone project</li>
<li>Click on File → Project Structure:</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/migration-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Dropdown menu to select Project Structure</em></p>
<ol start="3">
<li>Change the Gradle version to something more recent and that matches the current Android Studio version you are using</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/migration-2.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>AGP and Gradle settings screen</em></p>
<p>You could also use the AGP Upgrade Assistant to do this as well by going to Tools →AGP Upgrade Assistant:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/migration-3.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Dropdown menu to upgrade AGP using the AGP Upgrade Assistant</em></p>
<p>The third issue, which is a warning, might be caused by having an old version of the Android SDK Tools. To learn how to do that, you can go <a target="_blank" href="https://developer.android.com/studio/intro/update#sdk-manager">here</a>.</p>
<p>Your project should be now fully migrated, compiling and running smoothly. </p>
<p>If you want to read other articles I have written, you can check them out here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles">https://github.com/TomerPacific/MediumArticles</a></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Android Proto DataStore – Should You Use It? ]]>
                </title>
                <description>
                    <![CDATA[ A few years back, Google announced the DataStore, which is a replacement for the tried and true SharedPreferences.  If you use or have used SharedPreferences in your applications, you might be thinking of making the switch. But as with everything, th... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/android-proto-datastore-should-you-use-it/</link>
                <guid isPermaLink="false">66ba4fdccabb4cb0aaa837f4</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ protocol-buffers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Tue, 02 Jan 2024 19:52:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/12/niklas-ohlrogge-j-0olYcaihg-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A few years back, Google announced the DataStore, which is a replacement for the tried and true SharedPreferences. </p>
<p>If you use or have used SharedPreferences in your applications, you might be thinking of making the switch. But as with everything, the main question here is: what is going to be the cost in development?</p>
<p><a target="_blank" href="https://developer.android.com/codelabs/android-proto-datastore#3">There are benefits</a> for using DataStore, but only the <strong>Proto DataStore</strong> allows you to save objects while providing type safety. </p>
<p>If you look at the <a target="_blank" href="https://developer.android.com/topic/libraries/architecture/datastore#proto-datastore">documentation</a> for Proto DataStore, you will find that it is a bit outdated and missing some crucial steps when working with it. So that is why, in this article, we are going to go over how to integrate Proto DataStore into your application and show that it’s not that big of a hassle to use it.</p>
<h2 id="heading-what-is-datastore">What is DataStore?</h2>
<p>Jetpack DataStore has two variants:</p>
<ul>
<li>Preferences DataStore</li>
<li>Proto DataStore</li>
</ul>
<p>We won’t be discussing the first, due to it’s similarity to SharedPreferences and also the fact that is has been covered widely. So now let’s understand what the Proto in Proto DataStore means.</p>
<p>Proto is the name Google chose to represent <a target="_blank" href="https://protobuf.dev/">Protocol Buffers</a>. These are (Google’s) mechanism that help you serialize structured data. They are not coding language-specific and in general, you define the type of data that you wish to work with and then code is generated that helps you read and write your data.</p>
<p>✋ We will be using the Proto 3 version in this article.</p>
<p>How does that definition look like?</p>
<pre><code class="lang-proto">message MyItem {
    string itemName = 1;
    int32 itemId = 2;
}
</code></pre>
<p>First, you define an object with the message keyword. Inside it, you list the fields associated with that object. The numbers at the end of each field are used to identify the field itself and <strong>cannot be changed once being set and the object is in use</strong>.</p>
<p>But what if we wanted to have multiple objects in our .proto file? Assuming the objects are related to one another, you can do this simply by adding more message objects:</p>
<pre><code class="lang-proto">message MyItem {
    string itemName = 1;
    int32 itemId = 2;
}

message MyListOfItems {
   repeated MyItem items = 1;
}
</code></pre>
<p>Notice that above we have added another message object that relies on the MyItem object defined above. If you want to define a list of objects, you need to use the <strong>repeated</strong> keyword.</p>
<h2 id="heading-how-to-set-up-proto-datastore">How to Set Up Proto DataStore</h2>
<p>To get started, you'll need to add the following dependencies to your application level build.gradle:</p>
<pre><code class="lang-groovy"> implementation "androidx.datastore:datastore-preferences:1.0.0"
 implementation  "com.google.protobuf:protobuf-javalite:3.18.0"
</code></pre>
<p>Then, you will need to create a proto directory inside your project. This directory needs to be a sibling of the Java folder in your project structure. </p>
<p>Inside of the proto directory, you will be creating a .proto file. This file is responsible for generating the data types you wish to store in Proto DataStore.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/1.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Inside the proto directory, create a file with the .proto extension. Our .proto file will hold objects representing a Todo list (what else?). So we will call our file <strong>todo.proto</strong> and it will look like this:</p>
<pre><code class="lang-proto">syntax = "proto3";

option java_package = "com.yourPackageName.todo";
option java_multiple_files = true;

message TodoItem {
  string itemId = 1;
  string itemDescription = 2;
}

message TodoItems {
  repeated TodoItem items = 1;
}
</code></pre>
<p>Notice how we defined two message objects:</p>
<ol>
<li>TodoItem – that defines a todo item</li>
<li>TodoItems – that defines a list of TodoItem objects</li>
</ol>
<p>Next, build the project so that classes will be generated for TodoItem and TodoItems.</p>
<p>After our data objects have been defined, we need to create a class to serialize them. This class will tell the DataStore how to read/write our objects.</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// 1</span>
<span class="hljs-keyword">object</span> TodoItemSerializer: Serializer&lt;TodoItems&gt; {
   <span class="hljs-comment">// 2</span>
    <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> defaultValue: TodoItems = TodoItems.getDefaultInstance()
    <span class="hljs-comment">// 3</span>
    <span class="hljs-keyword">override</span> <span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">readFrom</span><span class="hljs-params">(input: <span class="hljs-type">InputStream</span>)</span></span>: TodoItems {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">return</span> TodoItems.parseFrom(input)
        } <span class="hljs-keyword">catch</span> (exception: InvalidProtocolBufferException) {
            <span class="hljs-keyword">throw</span> CorruptionException(<span class="hljs-string">"Cannot read proto."</span>, exception)
        }
    }
    <span class="hljs-comment">// 3</span>
    <span class="hljs-keyword">override</span> <span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">writeTo</span><span class="hljs-params">(
        t: <span class="hljs-type">TodoItems</span>,
        output: <span class="hljs-type">OutputStream</span>
    )</span></span> = t.writeTo(output)
}
</code></pre>
<p>Let’s review what we have in this class:</p>
<ol>
<li>When we declare the class, we need to implement the <strong>Serializer</strong> interface with our object as the type (T)</li>
<li>We define a default value for the serializer in case the file is not created</li>
<li>We override the readFrom/writeTo methods and we make sure to have our object as the data type there</li>
</ol>
<p>We have our .proto file with our data types and our serializer, so the next step is to instantiate the DataStore. We do this by using the property delegate created by dataStore, which requires giving a filename where our data will be saved and our serializer class (which we defined above).</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> DATA_STORE_FILE_NAME = <span class="hljs-string">"todo.pb"</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> Context.todoItemDatastore: DataStore&lt;TodoItems&gt; <span class="hljs-keyword">by</span> dataStore(
    fileName = DATA_STORE_FILE_NAME,
    serializer = TodoItemSerializer,
)
</code></pre>
<p>This piece of code needs to reside at the top of a class of your choosing above the definition of the class itself. That is:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> DATA_STORE_FILE_NAME = <span class="hljs-string">"todo.pb"</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> Context.todoItemDatastore: DataStore&lt;TodoItems&gt; <span class="hljs-keyword">by</span> dataStore(
    fileName = DATA_STORE_FILE_NAME,
    serializer = TodoItemSerializer,
)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">YourClassName</span> </span>{

}
</code></pre>
<p>To access this object in the rest of our application, we will need to use a context. An example is to use the application context in your viewmodel class:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyViewModel</span></span>(application: Application): AndroidViewModel(application) {

   <span class="hljs-keyword">val</span> todoDataStore = application.todoItemDataStore
   <span class="hljs-comment">//...</span>
}
</code></pre>
<h2 id="heading-how-to-use-kotlin-flow">How to Use Kotlin Flow</h2>
<p>Now that we have gone through setting up everything we need for our DataStore, we'll discuss how we are actually going to interact with it. We'll want to read and write data to/from it. But the way we can do so is different from what you may be familiar with from SharedPreferences.</p>
<p>The DataStore we defined above has a data field that exposes a Flow for the properties we defined in our DataStore.</p>
<p>🚰 If you are not familiar with flows, <a target="_blank" href="https://developer.android.com/kotlin/flow">this</a> is a good place to start.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> todoItemFlow: Flow&lt;TodoItems&gt; = todoItemDataStore.<span class="hljs-keyword">data</span>
        .<span class="hljs-keyword">catch</span> { exception -&gt;
            <span class="hljs-keyword">if</span> (exception <span class="hljs-keyword">is</span> IOException) {
                emit(TodoItems.getDefaultInstance())
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">throw</span> exception
            }
        }
</code></pre>
<p>The code above shows how you can define a Flow that collects data from the Proto DataStore. A catch block was added in case an exception occurs. You can place this logic in the class where you defined your DataStore and use it like so in your viewmodel:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> todoItemsFlow: LiveData&lt;TodoItems&gt; = todoItemsRepository.todoItemFlow.asLiveData()
</code></pre>
<p>Notice how we converted our Flow to LiveData. We did this for two reasons:</p>
<ol>
<li>Flows can stay active regardless of the activity/fragment that uses them</li>
<li>LiveData is something familiar to many developers, and I wanted to make this example as approachable as possible</li>
</ol>
<p>To be able to do this, you need to add the following dependency to your build.gradle file:</p>
<pre><code class="lang-groovy">implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
</code></pre>
<p>In your activity/fragment class, you can observe this live data like so:</p>
<pre><code class="lang-kotlin">myViewModel.todoItemFlow.observe(LocalLifecycleOwner.current) { todoItems -&gt;
                <span class="hljs-comment">// Logic to access data from DataStore</span>
            }
</code></pre>
<h2 id="heading-why-and-when-to-use-datastore">Why and When to Use DataStore</h2>
<p>After everything we reviewed, it’s time to talk about the elephant in the room. Should you go ahead and use DataStore (either Preferences or Proto) in your existing or next project?</p>
<p>In my opinion, the answer should be <strong>Yes</strong>. Besides the fact that Google is moving away from SharedPreferences, DataStore offers plenty of benefits to help you focus on your application and not the persistence of your data. </p>
<p>It’s safe to interact with the DataStore from the UI thread (as it moves work to I/O automatically), and it forces you to use Flow (if you haven’t still) and enjoy all the benefits within. There is also an option to migrate easily from SharedPreferences to Preferences DataStore.</p>
<p>If you are contemplating using Room instead of Proto DataStore, well that depends on your use case. If the amount of data you are going to save (or persist) is rather small and won’t require partial updating, the Proto DataStore is the way to go. If you have a larger data set or one that may be complex, you should opt for using Room instead.</p>
<p>If you want to see how all this code looks like in an application, you can see it here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/Todo">https://github.com/TomerPacific/Todo</a></div>
<p>If you want to read other articles I have written, you can see them here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles">https://github.com/TomerPacific/MediumArticles</a></div>
<p>Thanks for reading!</p>
<p>References:</p>
<ul>
<li><a target="_blank" href="https://protobuf.dev/programming-guides/proto3/">Protocol Buffers Documentation (proto 3)</a></li>
<li><a target="_blank" href="https://developer.android.com/codelabs/android-proto-datastore#0">Working With Proto DataStore Codelab</a></li>
<li><a target="_blank" href="https://developer.android.com/topic/libraries/architecture/datastore">DataStore Documentation</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Request Location Permissions in Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ Getting a user’s location can be a bit of hassle. Over the years, the permissions required to do this and the logic associated with it have changed quite drastically.  If your application depends on getting the user’s location, you want to ensure tha... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/requesting-location-permissions-in-jetpack-compose/</link>
                <guid isPermaLink="false">66ba5034158e6c6a8cb8c7a3</guid>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ user experience ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Thu, 05 Oct 2023 19:28:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/10/andrew-stutesman-l68Z6eF2peA-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Getting a user’s location can be a bit of hassle. Over the years, the permissions required to do this and the logic associated with it have changed quite drastically. </p>
<p>If your application depends on getting the user’s location, you want to ensure that the user has a good experience when the application requests this info. So it's crucial to handle all the edge cases and allow the user to select the option the they're most comfortable with.</p>
<p>With Jetpack Compose, the logic associated with getting the user’s location has changed a bit, and it’s important to know the in’s and out’s of how it can be done.</p>
<p>As with most things, there is a library you can use to handle getting permissions. It’s from Accompanist (read Google) and you can find it <a target="_blank" href="https://google.github.io/accompanist/permissions/#:~:text=A%20library%20which%20provides%20Android%20runtime%20permissions%20support%20for%20Jetpack%20Compose.&amp;text=The%20permission%20APIs%20are%20currently,marked%20with%20the%20%40ExperimentalPermissionsApi%20annotation.">here</a>. <strong>But you are here to learn how to do things yourself,</strong> right? So read on. 🕵️‍♀️</p>
<p>Before we get into the code and the logic, it is important to understand that requesting a permission from a user is a path that can have many decision points. As such, it is best described by having states that represent the current status of the permission (approved/rejected/denied) and the current status of the operating system. </p>
<p>The diagram below illustrates this flow:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/location.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Later on in this article, you will see how we will represent these states in variables in our code.</p>
<h2 id="heading-save-our-souls-sos">Save Our Souls (S.O.S)</h2>
<p>Before we get to the logic of asking for a permission, we’ll take care of the boilerplate around it. As always, add the necessary permissions to your AndroidManifest.xml file:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.ACCESS_COARSE_LOCATION"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.ACCESS_FINE_LOCATION"</span> /&gt;</span>
</code></pre>
<p>Then, we’ll create a composable screen where we ask for the necessary location permissions. The first step in this screen is to check whether the user has previously granted the required permissions. You can do this with something that is not new to Jetpack Compose – using the checkSelfPermission method:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> locationPermissionsAlreadyGranted = ContextCompat.checkSelfPermission(
            <span class="hljs-keyword">this</span>,
            Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
</code></pre>
<p>If the permission is not granted, we have to request it. We will use the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/activity/compose/package-summary#rememberlauncherforactivityresult">rememberLauncherForActivityResult</a> object to do this. This allows us in Jetpack Compose to get a result from an Activity.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> locationPermissions = arrayOf(
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.ACCESS_COARSE_LOCATION)

<span class="hljs-keyword">val</span> locationPermissionLauncher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
                onResult = { permissions -&gt;
                    <span class="hljs-keyword">val</span> permissionsGranted = permissions.values.reduce { acc, isPermissionGranted -&gt;
                        acc &amp;&amp; isPermissionGranted
                    }

                    <span class="hljs-keyword">if</span> (!permissionsGranted) {
                       <span class="hljs-comment">//Logic when the permissions were not granted by the user</span>
                    }
                })
</code></pre>
<p>The arguments that we need to pass to <code>rememberLauncherForActivityResult</code> are:</p>
<ol>
<li>An <a target="_blank" href="https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract">ActivityResultContract</a> – specifies the input of the activity and the output</li>
<li>onResult – a callback when the result is received</li>
</ol>
<p>In the code snippet above, we are using a multiple permissions contract, since we are requesting several location permissions. There is also a contract for requesting just one permission, <strong>ActivityResultContracts.RequestPermission().</strong></p>
<p>This piece of code doesn’t not run immediately as we need to request the permissions. To do that we use the launch method to start the activity:</p>
<pre><code class="lang-kotlin">locationPermissionLauncher.launch(locationPermissions)
</code></pre>
<p>In the case the user gave all or several of the required permissions, we can continue the logic of the application. </p>
<p>But, if the user did not approve any of the permissions, we need to find a way to make the user understand why these permissions are necessary.</p>
<h2 id="heading-explaining-the-rationale">Explaining the Rationale</h2>
<p>In case the user declined the permission, but did not select the “Deny And Don’t Ask Me Again” option, we have a way of giving the user a brief explanation on why they should grant the required permission(s). </p>
<p>To figure out if we should present this rationale, we use the <a target="_blank" href="https://developer.android.com/reference/androidx/core/app/ActivityCompat#shouldShowRequestPermissionRationale(android.app.Activity,java.lang.String)">shouldShowRequestPermissionRationale</a> from the Activity class:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> shouldShowPermissionRationale: <span class="hljs-built_in">Boolean</span> = shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)
</code></pre>
<p>Once we know that we can display this explanation, there are two ways to go about it:</p>
<ol>
<li>We can present it to the user with an AlertDialog</li>
<li>We can use the Snackbar</li>
</ol>
<p>Presenting an alert dialog is pretty straightforward. All we have to do is make sure we describe clearly to the user why it is required to approve this permission:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ShowLocationPermissionRationale</span><span class="hljs-params">()</span></span> {
        AlertDialog(
            onDismissRequest = {
               <span class="hljs-comment">//Logic when dismiss happens</span>
            },
        title = {
            Text(<span class="hljs-string">"Permission Required"</span>)
                },
        text = {
            Text(<span class="hljs-string">"You need to approve this permission in order to..."</span>)
        },
        confirmButton = {
            TextButton(onClick = {
              <span class="hljs-comment">//Logic when user confirms to accept permissions</span>
            }) {
                Text(<span class="hljs-string">"Confirm"</span>)
            }
        },
        dismissButton = {
            TextButton(onClick = {
              <span class="hljs-comment">//Logic when user denies to accept permissions</span>
            }) {
                Text(<span class="hljs-string">"Deny"</span>)
            }
        })
    }
</code></pre>
<p>If we want to present the Snackbar, we need to be aware that we have to use a Scaffold container since that is the only container that supports showing a Snackbar. If we don’t use one, a Snackbar won’t appear. </p>
<p>Below is a snippet that shows you how to do this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> scope = rememberCoroutineScope()
<span class="hljs-keyword">val</span> snackbarHostState = remember { SnackbarHostState() }

Scaffold(snackbarHost = {
        SnackbarHost(hostState = snackbarHostState)
    }) { contentPadding -&gt;
        <span class="hljs-keyword">if</span> (shouldShowPermissionRationale) {
            LaunchedEffect(key1 = shouldShowPermissionRationale, block = {
                scope.launch {
                    <span class="hljs-keyword">val</span> userAction = snackbarHostState.showSnackbar(
                        message =<span class="hljs-string">"Please authorize location permissions"</span>,
                        actionLabel = <span class="hljs-string">"Approve"</span>,
                        duration = SnackbarDuration.Indefinite,
                        withDismissAction = <span class="hljs-literal">true</span>
                    )
                    <span class="hljs-keyword">when</span> (userAction) {
                        SnackbarResult.ActionPerformed -&gt; {
                            <span class="hljs-comment">//User approved to grant the permission</span>
                            <span class="hljs-comment">//Ask for permissions again</span>
                        }
                        SnackbarResult.Dismissed -&gt; {
                            <span class="hljs-comment">//User dismissed snackbar</span>
                        }
                    }
                }
            })
        }
}
</code></pre>
<p>We allowed the Snackbar itself to be dismissible using the withDismissAction attribute and listened in to the action performed by the user.</p>
<h2 id="heading-lifecycle-observer">Lifecycle Observer</h2>
<p>One thing we have glossed over is the fact that we need to make sure our permission request adheres to the composable lifecycle. This means that once a user chooses their preferences regarding the permission request, we need the UI to adapt accordingly. </p>
<p>If you try and put the code above inside the activity’s onCreate method, you will be surprised with the outcome, since the application will crash with the following exception:</p>
<blockquote>
<p><em>java.lang.IllegalStateException: Launcher has not been initialized</em></p>
</blockquote>
<p>This is because Composable functions are supposed to be side effect free. What is a side effect? According to <a target="_blank" href="https://developer.android.com/jetpack/compose/side-effects">Google’s documentation</a> it is:</p>
<blockquote>
<p>… a change to the state of the app that happens outside the scope of a composable function</p>
</blockquote>
<p>So, in our use case, launching an activity for the permissions is the side effect happening here. </p>
<p>To circumvent this scenario, we need to use one of the side effect options. Since we don’t want to ask the user for permissions without remembering what their choices were previously, we can’t use the general <strong>SideEffect</strong>. And <strong>LaunchedEffect</strong> is used for calling suspend methods inside of a Composable, which is not our use case here. </p>
<p>So we are left with <strong>DisposableEffect</strong>. Reading the <a target="_blank" href="https://developer.android.com/jetpack/compose/side-effects#disposableeffect">documentation</a>, we can see that <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#DisposableEffect(kotlin.Any,kotlin.Function1)">DisposableEffect</a> can be combined with Lifecycle events, which is what we are after.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> lifecycleOwner = LocalLifecycleOwner.current
            DisposableEffect(key1 = lifecycleOwner, effect = {
                <span class="hljs-keyword">val</span> observer = LifecycleEventObserver { _, event -&gt;
                    <span class="hljs-keyword">if</span> (event == Lifecycle.Event.ON_START &amp;&amp; !locationPermissionsAlreadyGranted) {
                        locationPermissionLauncher.launch(locationPermissions)
                       }
                    }
                    lifecycleOwner.lifecycle.addObserver(observer)
                    onDispose {
                        lifecycleOwner.lifecycle.removeObserver(observer)
                    }
                }
            )
</code></pre>
<p>In the code snippet above, we are adding a lifecycle observer that runs only in the case of the onStart lifecycle event. We also combine it with the boolean we have declared at the start of this section, locationPermissionsAlreadyGranted. This is so we won’t show the dialog for asking permissions if they are already granted. </p>
<p>As with all lifecycle observers, we need to remove our observer once the composition ends. We have that logic inside DisposableEffect’s onDispose clause.</p>
<h2 id="heading-location-not-found">Location Not Found</h2>
<p>The last case we need to deal with is when the user chooses the “Deny And Don’t Ask Me Again” option. When this happens, we cannot ask the user to grant the required permissions. </p>
<p>The only way the user can revert their choice is to go to the settings screen of our application and change the permissions there. So we need to direct the user to go there. </p>
<p>To open the settings screen of our application, we need to use an intent with the action of <strong>ACTION_APPLICATION_DETAILS_SETTINGS.</strong></p>
<pre><code class="lang-kotlin">Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts(<span class="hljs-string">"package"</span>, packageName, <span class="hljs-literal">null</span>)).also {
            startActivity(it)
        }
</code></pre>
<p>Taking the logic above, we can add it to our code when we know that the user has chosen to deny the permissions and not be asked again. This happens inside our request for permissions when the user has not granted the permissions and the option to show the rationale is false.</p>
<h2 id="heading-location-confirmed">Location Confirmed</h2>
<p>If we take everything we discussed in this article and put it inside one file, we will get the following code:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">ComponentActivity</span></span>() {

    <span class="hljs-meta">@OptIn(ExperimentalMaterial3Api::class)</span>
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)

        setContent {

            <span class="hljs-keyword">var</span> locationPermissionsGranted <span class="hljs-keyword">by</span> remember { mutableStateOf(areLocationPermissionsAlreadyGranted()) }
            <span class="hljs-keyword">var</span> shouldShowPermissionRationale <span class="hljs-keyword">by</span> remember {
                mutableStateOf(
                    shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)
                )
            }

            <span class="hljs-keyword">var</span> shouldDirectUserToApplicationSettings <span class="hljs-keyword">by</span> remember {
                mutableStateOf(<span class="hljs-literal">false</span>)
            }

            <span class="hljs-keyword">var</span> currentPermissionsStatus <span class="hljs-keyword">by</span> remember {
                mutableStateOf(decideCurrentPermissionStatus(locationPermissionsGranted, shouldShowPermissionRationale))
            }

            <span class="hljs-keyword">val</span> locationPermissions = arrayOf(
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION
            )

            <span class="hljs-keyword">val</span> locationPermissionLauncher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
                onResult = { permissions -&gt;
                    locationPermissionsGranted = permissions.values.reduce { acc, isPermissionGranted -&gt;
                        acc &amp;&amp; isPermissionGranted
                    }

                    <span class="hljs-keyword">if</span> (!locationPermissionsGranted) {
                        shouldShowPermissionRationale =
                            shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)
                    }
                    shouldDirectUserToApplicationSettings = !shouldShowPermissionRationale &amp;&amp; !locationPermissionsGranted
                    currentPermissionsStatus = decideCurrentPermissionStatus(locationPermissionsGranted, shouldShowPermissionRationale)
                })

            <span class="hljs-keyword">val</span> lifecycleOwner = LocalLifecycleOwner.current
            DisposableEffect(key1 = lifecycleOwner, effect = {
                <span class="hljs-keyword">val</span> observer = LifecycleEventObserver { _, event -&gt;
                    <span class="hljs-keyword">if</span> (event == Lifecycle.Event.ON_START &amp;&amp;
                        !locationPermissionsGranted &amp;&amp;
                        !shouldShowPermissionRationale) {
                        locationPermissionLauncher.launch(locationPermissions)
                    }
                }
                lifecycleOwner.lifecycle.addObserver(observer)
                onDispose {
                    lifecycleOwner.lifecycle.removeObserver(observer)
                    }
                }
            )

            <span class="hljs-keyword">val</span> scope = rememberCoroutineScope()
            <span class="hljs-keyword">val</span> snackbarHostState = remember { SnackbarHostState() }

            LocationPermissionsTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Scaffold(snackbarHost = {
                        SnackbarHost(hostState = snackbarHostState)
                    }) { contentPadding -&gt;
                        Column(modifier = Modifier.fillMaxSize(),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally){
                            Text(modifier = Modifier
                                .padding(contentPadding)
                                .fillMaxWidth(),
                                text = <span class="hljs-string">"Location Permissions"</span>,
                                textAlign = TextAlign.Center)
                            Spacer(modifier = Modifier.padding(<span class="hljs-number">20</span>.dp))
                            Text(modifier = Modifier
                                .padding(contentPadding)
                                .fillMaxWidth(),
                                text = <span class="hljs-string">"Current Permission Status: <span class="hljs-variable">$currentPermissionsStatus</span>"</span>,
                                textAlign = TextAlign.Center,
                                fontWeight = FontWeight.Bold
                            )
                        }
                        <span class="hljs-keyword">if</span> (shouldShowPermissionRationale) {
                            LaunchedEffect(<span class="hljs-built_in">Unit</span>) {
                                scope.launch {
                                    <span class="hljs-keyword">val</span> userAction = snackbarHostState.showSnackbar(
                                        message =<span class="hljs-string">"Please authorize location permissions"</span>,
                                        actionLabel = <span class="hljs-string">"Approve"</span>,
                                        duration = SnackbarDuration.Indefinite,
                                        withDismissAction = <span class="hljs-literal">true</span>
                                    )
                                    <span class="hljs-keyword">when</span> (userAction) {
                                        SnackbarResult.ActionPerformed -&gt; {
                                            shouldShowPermissionRationale = <span class="hljs-literal">false</span>
                                            locationPermissionLauncher.launch(locationPermissions)
                                        }
                                        SnackbarResult.Dismissed -&gt; {
                                            shouldShowPermissionRationale = <span class="hljs-literal">false</span>
                                        }
                                    }
                                }
                            }
                        }
                        <span class="hljs-keyword">if</span> (shouldDirectUserToApplicationSettings) {
                            openApplicationSettings()
                        }
                    }
                }
            }
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">areLocationPermissionsAlreadyGranted</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Boolean</span> {
        <span class="hljs-keyword">return</span> ContextCompat.checkSelfPermission(
            <span class="hljs-keyword">this</span>,
            Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">openApplicationSettings</span><span class="hljs-params">()</span></span> {
        Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts(<span class="hljs-string">"package"</span>, packageName, <span class="hljs-literal">null</span>)).also {
            startActivity(it)
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">decideCurrentPermissionStatus</span><span class="hljs-params">(locationPermissionsGranted: <span class="hljs-type">Boolean</span>,
                                              shouldShowPermissionRationale: <span class="hljs-type">Boolean</span>)</span></span>: String {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (locationPermissionsGranted) <span class="hljs-string">"Granted"</span>
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (shouldShowPermissionRationale) <span class="hljs-string">"Rejected"</span>
        <span class="hljs-keyword">else</span> <span class="hljs-string">"Denied"</span>
    }
}
</code></pre>
<p>And this is how it looks like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/location2.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>I have put all the logic in one file just for the purpose of this article. It is by no means the most esthetic and correct approach to handling the logic with requesting permissions. </p>
<p>You could easily refactor out the logic variables associated with holding the different state of the request for permissions to a view model class that is attached to this screen.</p>
<p>You can see all of the code described in this article by going to this project:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles/tree/master/LocationPermissions">https://github.com/TomerPacific/MediumArticles/tree/master/LocationPermissions</a></div>
<p>And if you would like to read more of my articles, you can go view them below:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles">https://github.com/TomerPacific/MediumArticles</a></div>
<p>I have also used this logic in an application that you can try out <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.scheduler">here</a>.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Firebase Remote Config? ]]>
                </title>
                <description>
                    <![CDATA[ Remote configurations are useful because they allow you to alter the behavior in your application without having to release a new version of the app. One prominent example is using remote configurations to decide if a feature should be turned on or o... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/firebase-remote-config/</link>
                <guid isPermaLink="false">66ba4ff0158e6c6a8cb8c79a</guid>
                
                    <category>
                        <![CDATA[ Cloud Services ]]>
                    </category>
                
                    <category>
                        <![CDATA[ configuration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Tue, 03 Oct 2023 15:44:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/rima-kruciene-gpKe3hmIawg-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Remote configurations are useful because they allow you to alter the behavior in your application without having to release a new version of the app. One prominent example is using remote configurations to decide if a feature should be turned on or off. That way, you can gradually roll it out to production or test it to see how users react.</p>
<p>If you want your application to have this functionality, you'd usually have to build your server and its logic. But we now live in an age of technological innovation, and tools have been created to help you minimize your development time.</p>
<p>This tool is called Firebase Remote Config — a cloud service that enables you change different functionalities of your app without releasing updates or asking users to update the app.</p>
<h2 id="heading-overview">Overview</h2>
<p>You can access the Remote Config feature in your project’s Firebase console. It is usually under the Release &amp; Monitor section on the left sidebar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-6.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>There are two ways in which you can define your remote configurations:</p>
<ol>
<li>Using Firebase.</li>
<li>Using a template file that is in JSON format.</li>
</ol>
<p>We will focus on the first option, as the second option is a less intuitive approach.</p>
<p>Firebase Remote Config lets you define one or more keys during configuration. Keys can be of the following type:</p>
<ul>
<li>String</li>
<li>Number</li>
<li>Boolean</li>
<li>JSON</li>
</ul>
<p>These keys are used as the configurations for your application. For example, if you have a feature in your application that you would like to control through remote configurations, you  could define a Boolean key titled enableFeatureX.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-7.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Each key you set has a few other settings that may be useful to you. For example, you can define a default value for a key (for example, false can be the default value of a Boolean key) or make it use a value that you have defined in your application. </p>
<p>Another cool thing you can do, by clicking on the Add new button in the image above, is to set the value of a key based on certain factors. You'll see these options when you click on the button:</p>
<ul>
<li>Conditional value.</li>
<li>Experiment.</li>
<li>Personalization.</li>
</ul>
<p>Once you are done adding a key, make sure to publish your changes so they will be deployed.</p>
<h2 id="heading-the-conditional-value-option">The Conditional Value Option</h2>
<p>You can configure how a value will be set to specific users based on various conditions.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-8.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here, you can decide on what you want to test and how. You will discover several options when you click on the “Applies if” dropdown. </p>
<p>To illustrate the use of this feature, let’s say that you want to target iOS users in the US. You can do that using the “Applies if” dropdown and choosing Platform and then iOS. </p>
<p>After that, you can press the "and" button to add a condition for Country/Region and choose United States.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-9.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Make sure to also name your condition, otherwise, the Create condition button won’t be enabled.</p>
<p>Notice how the last field in defining a new condition window tells you how many users will be affected by this condition? That's a pretty cool feature.</p>
<h2 id="heading-the-experiment-option">The Experiment Option</h2>
<p>This option lets you change the behavior of a value in your remote configurations before taking effect on all your users.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-10.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can follow these steps to configure the experiment option:</p>
<ul>
<li>In the first step, you have to fill in the name and description of your experiment.</li>
<li>Then, you have choose which application to target and how many users will be affected (percentage-wise) in the second step.</li>
<li>The third step is to set up the metrics to measure this experiment. There are two types — the primary metrics and additional metrics.</li>
<li>Lastly, you can decide on the number of A/B test groups for this experiment.</li>
</ul>
<h2 id="heading-the-personalization-option">The Personalization Option</h2>
<p>Last but not least is the option to tailor a specific value of your remote configurations to a user based on their own behavior.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-11.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can define values the algorithm may supply to the user based on their behavior. These will be chosen by an objective you define (Step 2). This objective can range from the engagement time of the user to the amount of clicks they perform. In Step 3, you define a condition that will target users so that they will become personalized. Lastly, in Step 4, you add the name and description of this personalization.</p>
<p>Each option has much more to offer than what I have described here, so if you want to learn more, you can use one of the reference links at the bottom. Now that we understand what Remote Config is, let’s see how we can add it to our application.</p>
<h2 id="heading-how-to-setup-firebase-remote-config">How to Setup Firebase Remote Config</h2>
<p>Before you can do anything related to applying remote configurations, you need to make sure you've added Firebase to your Android project. This has been documented <a target="_blank" href="https://firebase.google.com/docs/android/setup?authuser=0">here</a>. </p>
<p>After you have done that, follow these steps:</p>
<h3 id="heading-step-1-add-the-firebase-remote-configuration-library-to-your-project-inside-your-application-buildgradle-file">Step #1 - Add the Firebase remote configuration library to your project inside your application build.gradle file</h3>
<pre><code> implementation <span class="hljs-string">'com.google.firebase:firebase-config-ktx'</span>
</code></pre><p>There is an option to also import the Firebase Analytics module, but it is not required for remote configurations. It is used in other areas of remote configurations, such as defining a condition based on a specific event happening.</p>
<h3 id="heading-step-2-use-the-remoteconfig-object">Step #2 - Use the <code>RemoteConfig</code> Object</h3>
<p>After syncing your project, you can access the <code>RemoteConfig</code> object with this command:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig
</code></pre>
<h3 id="heading-step-3-define-fetch-interval">Step #3 - Define fetch interval</h3>
<p>You can define how often your remote configurations will be fetched and updated. When you are still developing your application, setting this number to be relatively low is more ideal.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> remoteConfigSettings = remoteConfigSettings {                             minimumFetchIntervalInSeconds = <span class="hljs-number">2000</span>
}
</code></pre>
<p>If you set the <code>**minimumFetchIntervalInSeconds**</code> to be too low, Firebase will throw a <code>FirebaseRemoteConfigFetchThrottledException</code>, so use a low number only when you are testing things.</p>
<h3 id="heading-step-4-set-the-configuration-for-the-remote-configuration">Step #4 - Set the configuration for the remote configuration</h3>
<p>You can set the remote configuration using the code below:</p>
<pre><code class="lang-kotlin">remoteConfig.setConfigSettingsAsync(remoteConfigSettings)
</code></pre>
<h3 id="heading-step-5-set-default-values">Step #5 - Set default values</h3>
<p>You can have application default values for your remote configurations. These can be created as an XML file inside the XML directory inside the res folder. Here’s what the code looks like:</p>
<pre><code class="lang-kotlin">:remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
</code></pre>
<p>This XML file must have an underlying element of a map to wrap all your default values. For example, let’s imagine we have defined a key in remote configurations called <code>my_key</code>, whose value is <code>1</code>. The XML for the default values will look like this:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">defaultsMap</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">entry</span>&gt;</span>      
      <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>my_key<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>     
      <span class="hljs-tag">&lt;<span class="hljs-name">value</span>&gt;</span>1<span class="hljs-tag">&lt;/<span class="hljs-name">value</span>&gt;</span>   
    <span class="hljs-tag">&lt;/<span class="hljs-name">entry</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">defaultsMap</span>&gt;</span>
</code></pre>
<p>Remote configurations need to be fetched and activated. The fetch action fetches and stores your Remote Configurations inside the Remote Config object. The activation part is to make these values available to your application. That’s why there are two API methods:</p>
<ul>
<li><code>fetch</code> (and later use activate)</li>
</ul>
<pre><code class="lang-kotlin">remoteConfig.fetch().addOnCompleteListener { task -&gt;               <span class="hljs-keyword">if</span>                <span class="hljs-keyword">if</span> (task.isSuccessful) {     
              <span class="hljs-comment">//Remote Configurations fetch successfully          </span>
           }         
        }.addOnFailureListener { error -&gt;             
                <span class="hljs-comment">//Remote Configurations fetch failure            </span>
       }
-------------------------
remoteConfig.activate().addOnCompleteListener { task -&gt;  
<span class="hljs-keyword">if</span> (task.isSuccessful) {
        <span class="hljs-comment">//Remote Configurations activation success   </span>
        }  
   }.addOnFailureListener { error -&gt; 
               <span class="hljs-comment">//Remote Configurations activation failure</span>
  }
</code></pre>
<ul>
<li><code>fetchAndActivate</code></li>
</ul>
<pre><code class="lang-kotlin">remoteConfig.fetchAndActivate().addOnCompleteListener { task -&gt;                                <span class="hljs-keyword">if</span> (task.isSuccessful) {     
                <span class="hljs-comment">//Remote Configurations fetched and activated successfully                }        </span>
       }.addOnFailureListener { error -&gt;           
       <span class="hljs-comment">//Remote Configurations fetched and activated failure    </span>
     }
</code></pre>
<h3 id="heading-step-6-access-configurations">Step #6 - Access configurations</h3>
<p>Now that our remote configurations have been fetched and activated, we can access and use them in our application. We can do so by accessing the <code>remoteConfig</code> object and using one of the getter methods per the type of the value we set:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> myRemoteConfigValue: String = remoteConfig.getString(<span class="hljs-string">"my_key"</span>)
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Since your application will rely on remote configurations for its operation (or parts of it), it is crucial to decide how the application will behave if it does not arrive or takes too long to receive a response. </p>
<p>In essence, there are two ways to handle loading the remote configurations:</p>
<ol>
<li>Your application boots up and waits for the remote configurations to be activated.</li>
<li>Your application boots up and does not wait for the remote configuration to be activated. Opting instead to use the remote configurations on the second application run.</li>
</ol>
<p>It's important to understand that there is no option that is preferable over the other. It all depends on what your use case is and how you would like the user's experience to be when using your application. The first option guarantees that once your application is loaded, all the remote configurations that you have defined will be set and the user's experience will be smooth after the initial load time. If you have critical features that rely on the remote configurations, you will have to go with this option.</p>
<p>On the other hand, if your remote configurations concern a specific feature of your application that doesn't necessarily need to happen on the first initial launch, you might consider going for second option. That way, your application does not need to wait for the remote configurations to be received from Firebase and the logic inside your application can happen later.</p>
<p>There are good and bad implications for each of these methods, and it’s up to you to decide which is better suited for your application. If you choose the first option, you may add a loading screen that times out after a certain period. If you choose option two, it is recommended to create a default mechanism for features in your application and how they should work when the configuration has not yet been received.</p>
<p>There is more than we have discussed in this article, and I encourage you to investigate deeper things. I recently used Firebase Remote Configurations in an application I created that helps users schedule appointments.</p>
<p>You can check it out o<a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.scheduler">n the Google Play store</a>.</p>
<p>And you can see the source code here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/scheduler">https://github.com/TomerPacific/scheduler</a></div>
<p>If you want to read other articles I have written, you can find them below:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles">https://github.com/TomerPacific/MediumArticles</a></div>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/remote-config/get-started?platform=android">Getting Started With Firebase For Android</a></li>
<li><a target="_blank" href="https://firebase.google.com/docs/remote-config/use-cases">Remote Config Use Cases</a></li>
<li><a target="_blank" href="https://firebase.google.com/docs/remote-config/loading">Remote Config Loading Strategies</a></li>
<li><a target="_blank" href="https://firebase.google.com/docs/remote-config/personalization">Remote Config Personalization</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create Tabs in Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ We’ve all seen it. We’ve all done it. Ain’t nothing like good ol’ tabs to organize content in a complex application. So how do we go about creating a tab layout in Jetpack Compose?  In this tutorial, we’ll go over all of the basics, but also show som... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/tabs-in-jetpack-compose/</link>
                <guid isPermaLink="false">66ba503cf8a814ef73b78bcc</guid>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Tue, 28 Feb 2023 19:09:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/02/chiara-f-MI8He1NWPWg-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We’ve all seen it.</p>
<p>We’ve all done it.</p>
<p>Ain’t nothing like good ol’ tabs to organize content in a complex application. So how do we go about creating a tab layout in Jetpack Compose? </p>
<p>In this tutorial, we’ll go over all of the basics, but also show some things that are more advanced.</p>
<h2 id="heading-how-to-create-simple-tabs">How to Create Simple Tabs</h2>
<p>To create a tab layout, you need to start with a <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#TabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function1,kotlin.Function0,kotlin.Function0)"><strong>TabRow</strong></a>. This will be a container element that will hold your tabs.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-meta">@UiComposable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabRow</span><span class="hljs-params">(
    selectedTabIndex: <span class="hljs-type">Int</span>,
    modifier: <span class="hljs-type">Modifier</span> = Modifier,
    backgroundColor: <span class="hljs-type">Color</span> = MaterialTheme.colors.primarySurface,
    contentColor: <span class="hljs-type">Color</span> = contentColorFor(backgroundColor)</span></span>,
    indicator: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> (tabPositions: List&lt;TabPosition&gt;) -&gt; <span class="hljs-built_in">Unit</span> = <span class="hljs-meta">@Composable</span> { tabPositions -&gt;
            TabRowDefaults.Indicator(
                Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
            )
        },
    divider: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> () -&gt; <span class="hljs-built_in">Unit</span> = <span class="hljs-meta">@Composable</span> {
            TabRowDefaults.Divider()
        },
    tabs: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> () -&gt; <span class="hljs-built_in">Unit</span>
): <span class="hljs-built_in">Unit</span>
</code></pre>
<ul>
<li><strong>selectedTabIndex</strong> indicates the index of the tab that is currently selected</li>
<li><strong>indicator</strong> represents the UI that indicates which tab is currently selected</li>
<li><strong>divider</strong> is a composable that is drawn at the bottom of the TabRow under the indicator</li>
<li>If you don’t have a need to custom style your tabs, you can use <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/TabRowDefaults"><strong>TabRowDefaults</strong></a> as it contains the default values and implementation used for TabRow (you can see it being used inside divider)</li>
</ul>
<p>Let’s see the usage of TabRow with an example. We will create a simple layout that will have three tabs:</p>
<ol>
<li>Home</li>
<li>About</li>
<li>Settings</li>
</ol>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabScreen</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> tabIndex <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-number">0</span>) }

    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>)

    Column(modifier = Modifier.fillMaxWidth()) {
        TabRow(selectedTabIndex = tabIndex) {
            tabs.forEachIndexed { index, title -&gt;
                Tab(text = { Text(title) },
                    selected = tabIndex == index,
                    onClick = { tabIndex = index }
                )
            }
        }
        <span class="hljs-keyword">when</span> (tabIndex) {
            <span class="hljs-number">0</span> -&gt; HomeScreen()
            <span class="hljs-number">1</span> -&gt; AboutScreen()
            <span class="hljs-number">2</span> -&gt; SettingsScreen()
        }
    }
}
</code></pre>
<p>A couple things to pay attention to:</p>
<ul>
<li>The TabRow composable holds inside of itself a <strong>Tab</strong> composable</li>
<li>After the TabRow composable, we have a when clause to handle what happens when each tab is clicked (in our specific case we are opening different screens)</li>
<li>We are using a variable called tabIndex to keep track of which Tab is selected</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/1.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Pretty bland, right?</p>
<p>Let’s spice things up with icons by using the icon attribute of the Tab composable.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabScreen</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> tabIndex <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-number">0</span>) }

    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>)

    Column(modifier = Modifier.fillMaxWidth()) {
        TabRow(selectedTabIndex = tabIndex) {
            tabs.forEachIndexed { index, title -&gt;
                Tab(text = { Text(title) },
                    selected = tabIndex == index,
                    onClick = { tabIndex = index },
                    icon = {
                        <span class="hljs-keyword">when</span> (index) {
                            <span class="hljs-number">0</span> -&gt; Icon(imageVector = Icons.Default.Home, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">1</span> -&gt; Icon(imageVector = Icons.Default.Info, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">2</span> -&gt; Icon(imageVector = Icons.Default.Settings, contentDescription = <span class="hljs-literal">null</span>)
                        }
                    }
                )
            }
        }
        <span class="hljs-keyword">when</span> (tabIndex) {
            <span class="hljs-number">0</span> -&gt; HomeScreen()
            <span class="hljs-number">1</span> -&gt; AboutScreen()
            <span class="hljs-number">2</span> -&gt; SettingsScreen()
        }
    }
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/1-1.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Looking better, but a question does arise: What if we have more tabs than the screen can show?</p>
<p>Luckily, the answer is simple.</p>
<p>There is an option to make our TabRow scrollable. Instead of using the TabRow element, you can use the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#ScrollableTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,kotlin.Function1,kotlin.Function0,kotlin.Function0)"><strong>ScrollableTabRow</strong></a> composable.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-meta">@UiComposable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ScrollableTabRow</span><span class="hljs-params">(
    selectedTabIndex: <span class="hljs-type">Int</span>,
    modifier: <span class="hljs-type">Modifier</span> = Modifier,
    backgroundColor: <span class="hljs-type">Color</span> = MaterialTheme.colors.primarySurface,
    contentColor: <span class="hljs-type">Color</span> = contentColorFor(backgroundColor)</span></span>,
    edgePadding: Dp = TabRowDefaults.ScrollableTabRowPadding,
    indicator: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> (tabPositions: List&lt;TabPosition&gt;) -&gt; <span class="hljs-built_in">Unit</span> = <span class="hljs-meta">@Composable</span> { tabPositions -&gt;
            TabRowDefaults.Indicator(
                Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
            )
        },
    divider: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> () -&gt; <span class="hljs-built_in">Unit</span> = <span class="hljs-meta">@Composable</span> {
            TabRowDefaults.Divider()
        },
    tabs: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> () -&gt; <span class="hljs-built_in">Unit</span>
): <span class="hljs-built_in">Unit</span>
</code></pre>
<p>So if we convert our example from above, we will get this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabScreen</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> tabIndex <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-number">0</span>) }

    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>, <span class="hljs-string">"More"</span>, <span class="hljs-string">"Something"</span>, <span class="hljs-string">"Everything"</span>)

    Column(modifier = Modifier.fillMaxWidth()) {
        ScrollableTabRow(selectedTabIndex = tabIndex) {
            tabs.forEachIndexed { index, title -&gt;
                Tab(text = { Text(title) },
                    selected = tabIndex == index,
                    onClick = { tabIndex = index },
                    icon = {
                        <span class="hljs-keyword">when</span> (index) {
                            <span class="hljs-number">0</span> -&gt; Icon(imageVector = Icons.Default.Home, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">1</span> -&gt; Icon(imageVector = Icons.Default.Info, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">2</span> -&gt; Icon(imageVector = Icons.Default.Settings, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">3</span> -&gt; Icon(imageVector = Icons.Default.Lock, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">4</span> -&gt; Icon(imageVector = Icons.Default.HeartBroken, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">5</span> -&gt; Icon(imageVector = Icons.Default.Star, contentDescription = <span class="hljs-literal">null</span>)
                        }
                    }
                )
            }
        }
        <span class="hljs-keyword">when</span> (tabIndex) {
            <span class="hljs-number">0</span> -&gt; HomeScreen()
            <span class="hljs-number">1</span> -&gt; AboutScreen()
            <span class="hljs-number">2</span> -&gt; SettingsScreen()
            <span class="hljs-number">3</span> -&gt; MoreScreen()
            <span class="hljs-number">4</span> -&gt; SomethingScreen()
            <span class="hljs-number">5</span> -&gt; EverythingScreen()
        }
    }
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/2.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-create-tabs-with-swiping-enabled">How to Create Tabs with Swiping Enabled</h2>
<p>Scrollable tabs are nice, but swiping between tabs is even better. Most users will feel that it's more intuitive to swipe between the tabs rather than clicking on each one. If you look at the documentation, you will notice that there are a few options to go with:</p>
<ol>
<li>The <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#(androidx.compose.ui.Modifier).swipeable(androidx.compose.material.SwipeableState,kotlin.collections.Map,androidx.compose.foundation.gestures.Orientation,kotlin.Boolean,kotlin.Boolean,androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Function2,androidx.compose.material.ResistanceConfig,androidx.compose.ui.unit.Dp)">swipeable</a> modifier</li>
<li>The <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/gestures/package-summary#(androidx.compose.ui.input.pointer.PointerInputScope).detectDragGestures(kotlin.Function1,kotlin.Function0,kotlin.Function0,kotlin.Function2)">detectDragGestures</a> modifier</li>
<li>The <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/gestures/package-summary#(androidx.compose.ui.Modifier).draggable(androidx.compose.foundation.gestures.DraggableState,androidx.compose.foundation.gestures.Orientation,kotlin.Boolean,androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Boolean,kotlin.coroutines.SuspendFunction2,kotlin.coroutines.SuspendFunction2,kotlin.Boolean)">draggable</a> modifier</li>
</ol>
<p>Not all of these will help us in achieving our goal, each one for its own reasons. If you don’t want to go through the “hassle” of doing things yourself, there is a library from Accompanist called <a target="_blank" href="https://google.github.io/accompanist/pager/#usage">pager</a> that you can use. It allows you to add the ability to either horizontally or vertically create a row/column that reacts to swipes.</p>
<p>Steps to implement it have been covered already and you can use the resources below to learn how to do it:</p>
<ul>
<li><a target="_blank" href="https://johncodeos.com/how-to-create-tabs-with-jetpack-compose/">https://johncodeos.com/how-to-create-tabs-with-jetpack-compose/</a></li>
<li><a target="_blank" href="https://www.rockandnull.com/jetpack-compose-swipe-pager/">https://www.rockandnull.com/jetpack-compose-swipe-pager/</a></li>
</ul>
<p>If you are like me and you like to do things for yourself and are up for getting your hands dirty, read on.</p>
<h2 id="heading-option-1-the-swipeable-modifier">Option 1: the <code>Swipeable</code> Modifier</h2>
<p>The first thing to know about the swipeable modifier is that it is annotated with the @<a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/ExperimentalMaterialApi"><strong>ExperimentalMaterialApi</strong></a>. This means that this API can change between versions of Jetpack Compose and that it isn’t stable. </p>
<p>Apart from that, we need to go over the mechanism that the swipeable modifier uses. It has 3 building blocks:</p>
<ol>
<li>A swipeable state – Denoting the current state and holding data about any on going swipe or swipe related animation.</li>
<li>Anchors – A map of values (Float based) restricting the swipe action from the minimum value to the maximum value. It maps anchor points to swipeable states.</li>
<li>Thresholds – A value denoting the difference between two known anchors.</li>
</ol>
<pre><code class="lang-kotlin"><span class="hljs-meta">@ExperimentalMaterialApi</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T : Any?&gt;</span> Modifier.<span class="hljs-title">swipeable</span><span class="hljs-params">(
    state: <span class="hljs-type">SwipeableState</span>&lt;<span class="hljs-type">T</span>&gt;,
    anchors: <span class="hljs-type">Map</span>&lt;<span class="hljs-type">Float</span>, T&gt;,
    orientation: <span class="hljs-type">Orientation</span>,
    enabled: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    reverseDirection: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">false</span>,
    interactionSource: <span class="hljs-type">MutableInteractionSource</span>? = <span class="hljs-literal">null</span>,
    thresholds: (<span class="hljs-type">from</span>, <span class="hljs-type">to</span>) -&gt; <span class="hljs-type">ThresholdConfig</span> = { _, _ -&gt; FixedThreshold(<span class="hljs-number">56.</span>dp)</span></span> },
    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
    velocityThreshold: Dp = VelocityThreshold
): Modifier
</code></pre>
<p>Regardless of this API being experimental, it just isn’t meant to be used for the swiping gesture we are seeking. </p>
<p>You can. use this modifier for a switch button that the user can drag between on/off positions (as an example). But what would be our anchors in our example? How do we define the thresholds? The swipe a user performs cannot be constrained between two points. Therefore, we’ll let this one go and move on to detectDragGestures.</p>
<h2 id="heading-option-2-the-detectdraggestures-modifier">Option 2: the <code>detectDragGestures</code> Modifier</h2>
<p>As the name implies, this modifier detects the drag gesture, which can be quite similar to swiping.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> PointerInputScope.<span class="hljs-title">detectDragGestures</span><span class="hljs-params">(
    onDragStart: (<span class="hljs-type">Offset</span>) -&gt; <span class="hljs-type">Unit</span> = { },
    onDragEnd: () -&gt; <span class="hljs-type">Unit</span> = { },
    onDragCancel: () -&gt; <span class="hljs-type">Unit</span> = { },
    onDrag: (<span class="hljs-type">change</span>: <span class="hljs-type">PointerInputChange</span>, <span class="hljs-type">dragAmount</span>: <span class="hljs-type">Offset</span>) -&gt; <span class="hljs-type">Unit</span>
)</span></span>: <span class="hljs-built_in">Unit</span>
</code></pre>
<p>As you can see, the <strong>onDrag</strong> callback has two arguments:</p>
<ol>
<li><code>change</code> – of <code>PointerInputChange</code> type, denoting the change in pointer when dragging</li>
<li><code>dragAmount</code> – of <code>Offset</code> type, denoting the amount dragged in x,y values</li>
</ol>
<p>This callback is called when:</p>
<blockquote>
<p><em>“… waits for pointer down and touch stop in any direction and then calls <code>onDrag</code> for each drag event.”</em></p>
</blockquote>
<p>The upside to use this modifier instead of the draggable one is that it provides you with information about the change in both x and y coordinates.</p>
<p>The downside of it is that it isn’t going to offer a smooth and elegant solution for swiping. This is because of the amount of times the onDrag callback is triggered. </p>
<p>When a user performs a swipe gesture, the onDrag callback is triggered multiple times. This makes it harder to discern when the “drag” gesture has ended completely. </p>
<p>When experimenting with this, I saw the onDrag callback being triggered three times for each swipe gesture. This won’t be a good fit for our use case so let’s check out the draggable modifier.</p>
<h2 id="heading-option-3-the-draggable-modifier">Option 3: the <code>Draggable</code> Modifier</h2>
<p>Think of this modifier as the stripped down version of the one before. This one measures changes in the UI when the user performs a drag gesture in only one orientation (vertical/horizontal). Since we only care about horizontal swipes, this can be a good option.</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> Modifier.<span class="hljs-title">draggable</span><span class="hljs-params">(
    state: <span class="hljs-type">DraggableState</span>,
    orientation: <span class="hljs-type">Orientation</span>,
    enabled: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    interactionSource: <span class="hljs-type">MutableInteractionSource</span>? = <span class="hljs-literal">null</span>,
    startDragImmediately: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">false</span>,
    onDragStarted: <span class="hljs-type">suspend</span> <span class="hljs-type">CoroutineScope</span>.(<span class="hljs-type">startedPosition</span>: <span class="hljs-type">Offset</span>) -&gt; <span class="hljs-type">Unit</span> = {},
    onDragStopped: <span class="hljs-type">suspend</span> <span class="hljs-type">CoroutineScope</span>.(<span class="hljs-type">velocity</span>: <span class="hljs-type">Float</span>) -&gt; <span class="hljs-type">Unit</span> = {},
    reverseDirection: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">false</span>
)</span></span>: Modifier
</code></pre>
<p>Here as well there is no similarity to the two other modifiers and we will point out the things to pay attention to:</p>
<ul>
<li><code>state</code> – Similar to the state in the swipeable modifier, only here we are talking about a drag motion.</li>
<li><code>onDragStarted</code> – A callback triggered when the drag motion has begun.</li>
<li><code>onDragStopped</code> – A callback triggered when the drag motion has ended.</li>
</ul>
<p>Unlike <strong><code>detectDragGestures</code></strong>, here <code>onDragStopped</code> is called once for every swipe gesture, making this modifier the best candidate for the job.</p>
<p>It’s implementation as a swipe gesture detector in our example is quite robust, so let’s start with some prerequisites:</p>
<ol>
<li>We will be saving the index of the tab currently being viewed in a view model class</li>
<li>This index will be of <code>MutableLiveData</code> so that our composables will be able to recompose when the value is changed</li>
<li>Each of our screens will add the <code>draggable</code> modifier to its layout</li>
<li>We will need to add the runtime-livedata library as we are going to use the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/livedata/package-summary#(androidx.lifecycle.LiveData).observeAsState(kotlin.Any)"><code>observeAsState</code></a> method.</li>
</ol>
<p>We will start with #4.</p>
<p>Go to your application’s build.gradle file and add the following dependency:</p>
<pre><code>implementation <span class="hljs-string">"androidx.compose.runtime:runtime-livedata:$compose_version"</span>
</code></pre><p>where <strong><code>$compose_version</code></strong> is the version of Jetpack Compose you are using.</p>
<p>We have also minimized our previous example to hold three screens instead of six, as the solution works for either case and there is no need to create extra boiler plate.</p>
<p>Below is the view model:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModel</span></span>(application: Application) : AndroidViewModel(application) {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> _tabIndex: MutableLiveData&lt;<span class="hljs-built_in">Int</span>&gt; = MutableLiveData(<span class="hljs-number">0</span>)
    <span class="hljs-keyword">val</span> tabIndex: LiveData&lt;<span class="hljs-built_in">Int</span>&gt; = _tabIndex
    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>)

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">updateTabIndexBasedOnSwipe</span><span class="hljs-params">(isSwipeToTheLeft: <span class="hljs-type">Boolean</span>)</span></span> {
        _tabIndex.value = <span class="hljs-keyword">when</span> (isSwipeToTheLeft) {
            <span class="hljs-literal">true</span> -&gt; Math.floorMod(_tabIndex.value!!.plus(<span class="hljs-number">1</span>), tabs.size)
            <span class="hljs-literal">false</span> -&gt; Math.floorMod(_tabIndex.value!!.minus(<span class="hljs-number">1</span>), tabs.size)
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">updateTabIndex</span><span class="hljs-params">(i: <span class="hljs-type">Int</span>)</span></span> {
        _tabIndex.value = i
    }

}
</code></pre>
<ul>
<li><strong><code>tabIndex</code></strong> is in charge of holding the currently selected index.</li>
<li><strong><code>index</code></strong> is the exposed tabIndex.</li>
<li><strong><code>tabs</code></strong> is the list of tab names.</li>
<li>The method <strong><code>updateTabIndexBasedOnSwipe</code></strong> is triggered when a swipe happens and performs the calculation of where to move the tabIndex to.</li>
</ul>
<p>Each screen is made up of the same layout:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">AboutScreen</span><span class="hljs-params">(viewModel: <span class="hljs-type">MainViewModel</span>)</span></span> {

    <span class="hljs-keyword">var</span> isSwipeToTheLeft <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-literal">false</span>) }
    <span class="hljs-keyword">val</span> dragState = rememberDraggableState(onDelta = { delta -&gt;
        isSwipeToTheLeft = delta &gt; <span class="hljs-number">0</span>
    })

    Column(modifier = Modifier.fillMaxSize().draggable(
        state = dragState,
        orientation = Orientation.Horizontal,
        onDragStarted = {  },
        onDragStopped = {
            viewModel.updateTabIndexBasedOnSwipe(isSwipeToTheLeft = isSwipeToTheLeft)
        }),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center) {
        Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
            Text(
                text = <span class="hljs-string">"About"</span>,
                textAlign = TextAlign.Center,
                fontSize = <span class="hljs-number">20</span>.sp,
                fontWeight = FontWeight.Bold
            )
        }
    }
}
</code></pre>
<ul>
<li><strong><code>isSwipeToTheLeft</code></strong> is a Boolean indicating the direction of the swipe.</li>
<li><strong><code>dragState</code></strong> holds the state of the drag being performed and updates isSwipeToTheLeft according to the delta.</li>
<li>When the callback <strong><code>onDragStopped</code></strong> is called, we are calling the exposed viewModel method updateTabIndexBasedOnSwipe.</li>
</ul>
<p>And finally, our <code>TabLayout</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabLayout</span><span class="hljs-params">(viewModel: <span class="hljs-type">MainViewModel</span>)</span></span> {
    <span class="hljs-keyword">val</span> tabIndex = viewModel.tabIndex.observeAsState()
    Column(modifier = Modifier.fillMaxWidth()) {
        TabRow(selectedTabIndex = tabIndex.value!!) {
            viewModel.tabs.forEachIndexed { index, title -&gt;
                Tab(text = { Text(title) },
                    selected = tabIndex.value!! == index,
                    onClick = { viewModel.updateTabIndex(index) },
                    icon = {
                        <span class="hljs-keyword">when</span> (index) {
                            <span class="hljs-number">0</span> -&gt; Icon(imageVector = Icons.Default.Home, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">1</span> -&gt; Icon(imageVector = Icons.Default.Info, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">2</span> -&gt; Icon(imageVector = Icons.Default.Settings, contentDescription = <span class="hljs-literal">null</span>)
                        }
                    }
                )
            }
        }

        <span class="hljs-keyword">when</span> (tabIndex.value) {
            <span class="hljs-number">0</span> -&gt; HomeScreen(viewModel = viewModel)
            <span class="hljs-number">1</span> -&gt; AboutScreen(viewModel = viewModel)
            <span class="hljs-number">2</span> -&gt; SettingsScreen(viewModel = viewModel)
        }
    }
}
</code></pre>
<ul>
<li>Notice that when a tab is selected, we are updating the currently selected tab in the <code>viewModel</code> with <strong><code>updateTabIndex</code></strong>.</li>
</ul>
<p>Putting it all together yields:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/2-1.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>A few words regarding what we have accomplished. You might have noticed that there is some boilerplate we are adding for each of our screens that results in repetition. Each screen is saving the state of the drag. </p>
<p>To improve on that, we can move the <code>draggableState</code> to the view model, like so:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModel</span></span>(application: Application) : AndroidViewModel(application) {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> _tabIndex: MutableLiveData&lt;<span class="hljs-built_in">Int</span>&gt; = MutableLiveData(<span class="hljs-number">0</span>)
    <span class="hljs-keyword">val</span> tabIndex: LiveData&lt;<span class="hljs-built_in">Int</span>&gt; = _tabIndex
    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>)

    <span class="hljs-keyword">var</span> isSwipeToTheLeft: <span class="hljs-built_in">Boolean</span> = <span class="hljs-literal">false</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> draggableState = DraggableState { delta -&gt;
        isSwipeToTheLeft= delta &gt; <span class="hljs-number">0</span>
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> _dragState = MutableLiveData&lt;DraggableState&gt;(draggableState)
    <span class="hljs-keyword">val</span> dragState: LiveData&lt;DraggableState&gt; = _dragState

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">updateTabIndexBasedOnSwipe</span><span class="hljs-params">()</span></span> {
        _tabIndex.value = <span class="hljs-keyword">when</span> (isSwipeToTheLeft) {
            <span class="hljs-literal">true</span> -&gt; Math.floorMod(_tabIndex.value!!.plus(<span class="hljs-number">1</span>), tabs.size)
            <span class="hljs-literal">false</span> -&gt; Math.floorMod(_tabIndex.value!!.minus(<span class="hljs-number">1</span>), tabs.size)
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">updateTabIndex</span><span class="hljs-params">(i: <span class="hljs-type">Int</span>)</span></span> {
        _tabIndex.value = i
    }

}
</code></pre>
<p>And that reduces the boilerplate a bit, since each screen now looks like:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">AboutScreen</span><span class="hljs-params">(viewModel: <span class="hljs-type">MainViewModel</span>)</span></span> {

    Column(modifier = Modifier.fillMaxSize().draggable(
        state = viewModel.dragState.value!!,
        orientation = Orientation.Horizontal,
        onDragStarted = {  },
        onDragStopped = {
            viewModel.updateTabIndexBasedOnSwipe()
        }),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center) {
        Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
            Text(
                text = <span class="hljs-string">"About"</span>,
                textAlign = TextAlign.Center,
                fontSize = <span class="hljs-number">20</span>.sp,
                fontWeight = FontWeight.Bold
            )
        }
    }
}
</code></pre>
<p>I hope this article gave you the necessary tools to create your own tabs UI in Jetpack Compose. </p>
<p>The example shown above can be found <a target="_blank" href="https://github.com/TomerPacific/MediumArticles/tree/master/JetpackComposeTabs">here</a>.</p>
<p>And if you would like to read other articles I have written, you can check them out <a target="_blank" href="https://github.com/TomerPacific/MediumArticles">here</a>.</p>
<p>References:</p>
<ul>
<li><a target="_blank" href="https://m3.material.io/components/tabs/overview">Material Design page about Tabs</a></li>
<li><a target="_blank" href="https://developer.android.com/jetpack/compose/touch-input/gestures">Gestures In Jetpack Compose</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Serialize Your Data in Kotlin and Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ Serialization is the process of transforming data that's in one format into another format that can be stored.  If you have ever worked with a database or fetching data from a server, this should all be familiar to you. If not, you have come to the r... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/serializing-your-data-in-kotlin/</link>
                <guid isPermaLink="false">66ba503a158e6c6a8cb8c7a5</guid>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kotlin ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Wed, 01 Feb 2023 21:19:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/fineas-anton-cnoMG2034k8-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Serialization is the process of transforming data that's in one format into another format that can be stored. </p>
<p>If you have ever worked with a database or fetching data from a server, this should all be familiar to you. If not, you have come to the right place. </p>
<p>In this tutorial, we will go over:</p>
<ul>
<li>How to setup serialization in a Jetpack Compose project</li>
<li>How to serialize a data class</li>
<li>How to de-serialize a data class</li>
</ul>
<p>You might be asking yourself, what’s so special about serialization in Jetpack Compose? In essence, there isn’t a lot of difference than with a regular Kotlin Android project. The only difference is in the setup.</p>
<h2 id="heading-how-to-set-everything-up">How to Set Everything Up</h2>
<p>Each version of Jetpack Compose corresponds with a version of Kotiln that it is compatible with. Each version of the kotlin-serialization library is also compatible with a specific version of Kotlin. So you need to make sure that each of the three parts in this tripod are compatible with one another.</p>
<p>How can you that?</p>
<p>Your first resource you'll want to consult is the <a target="_blank" href="https://developer.android.com/jetpack/androidx/releases/compose-kotlin">Compose to Kotlin Compatibility Map</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/1_5brVwILW54aNaFFimDF87Q.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here you can see which version of Jetpack Compose corresponds to which Kotlin version.</p>
<p>The second resource you will need is the <a target="_blank" href="https://github.com/Kotlin/kotlinx.serialization/releases">releases page</a> for the kotlin-serialization library. There you will find which library version is compatible with which Kotlin version.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/1_y6Ba1fROOcSSXXm-Nll4Ew.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Confused? 😕</p>
<p>Let’s illustrate this with an example:</p>
<ul>
<li>Your Jetpack Compose version is <strong>1.1.0</strong>.</li>
<li>Looking over the compatibility map, you see it is compatible with Kotlin version <strong>1.6.10</strong>.</li>
<li>Heading to the releases page of kotlin-serialization library, you see that the version of the kotlin-serialization library that you need to use is <strong>1.3.2</strong>.</li>
</ul>
<p>Head into your project level build.gradle file, and inside the buildscript object, in the dependencies section, put in classpath for the kotlin-serialization library with the version you need.</p>
<pre><code class="lang-kotlin">dependencies {
        ...
        classpath <span class="hljs-string">"org.jetbrains.kotlin:kotlin-serialization:X.Y.Z"</span>
 }
</code></pre>
<p>Then, head over to your application build.gradle file and do these two things:</p>
<ol>
<li>Add the <strong>id ‘org.jetbrains.kotlin.plugin.serialization’</strong> inside of the plugins at the top of the file:</li>
</ol>
<pre><code class="lang-kotlin">plugins {
   ...
   id <span class="hljs-string">'org.jetbrains.kotlin.plugin.serialization'</span>
}
</code></pre>
<ol start="2">
<li>At the bottom of the file, inside the dependencies section add <strong>implementation ‘org.jetbrains.kotlinx:kotlinx-serialization-json:X.Y.Z’</strong>:</li>
</ol>
<pre><code class="lang-kotlin">dependencies {
   ...
   implementation <span class="hljs-string">'org.jetbrains.kotlinx:kotlinx-serialization-json:X.Y.Z'</span>
}
</code></pre>
<p>Sync your project and you should be good to go.</p>
<p>Note that we are using the <strong>json</strong> format of the library, but there are other formats that are supported as well:</p>
<ul>
<li>Protocol Buffers</li>
<li>CBOR (Concise Binary Object Representation)</li>
<li>Properties</li>
<li>HOCON (Human Optimized Config Object Notation)</li>
</ul>
<blockquote>
<p>⚠️ If you encounter any errors, make sure the versions you put are correct</p>
</blockquote>
<h2 id="heading-how-to-build-your-data-class">How to Build Your Data Class</h2>
<p>In order to have something we can serialize and later de-serialize, we need to work with data classes. </p>
<p>Creating a data class is simple. If you are using Android Studio, just right click inside your project’s module and choose New Kotlin file. Enter your class name and then append the data keyword before it.</p>
<p>For the sake of this article, let's say we are working with an API that returns a list of users. Each user object has a range of attributes it can have (just to name a few):</p>
<ul>
<li>First name</li>
<li>Last name</li>
<li>Age</li>
<li>Birthdate</li>
<li>Id</li>
</ul>
<p>To make our data class serializable, all you need to do is add the <strong>@Serializable</strong> annotation above the class declaration.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Serializable</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span></span>(
   <span class="hljs-keyword">val</span> firstName: String,
   <span class="hljs-keyword">val</span> lastName: String,
   <span class="hljs-keyword">val</span> age: <span class="hljs-built_in">Int</span>,
   <span class="hljs-keyword">val</span> birthdate: Date,
   <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Long</span>
)
</code></pre>
<p>Pretty nifty, right?</p>
<p>Well, there’s more.</p>
<p>The variable that will hold the user’s first name is written as firstName. That means that in the response from our server, it needs to return in a field with the same name. </p>
<p>Sometimes, in API responses, the keys are not written in camelCase, but rather in kebab_case. That would mean that the key for first name, might be first_name. In that case, we would have to write it out like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Serializable</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span></span>(
   <span class="hljs-keyword">val</span> first_name: String,
   <span class="hljs-keyword">val</span> lastName: String,
   <span class="hljs-keyword">val</span> age: <span class="hljs-built_in">Int</span>,
   <span class="hljs-keyword">val</span> birthdate: Date,
   <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Long</span>
)
</code></pre>
<p>But that is not the <a target="_blank" href="https://kotlinlang.org/docs/coding-conventions.html">convention</a> for property names in Kotlin.</p>
<p>So what can we do?</p>
<p>We can use the <strong>@SerialName</strong> annotation. This allows us to mark what the name of the field will be from the response and then write anything as the property for it.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Serializable</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span></span>(
   <span class="hljs-meta">@SerialName(<span class="hljs-meta-string">"first_name"</span>)</span>
   <span class="hljs-keyword">val</span> firstName: String,
   <span class="hljs-keyword">val</span> lastName: String,
   <span class="hljs-keyword">val</span> age: <span class="hljs-built_in">Int</span>,
   <span class="hljs-keyword">val</span> birthdate: Date,
   <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Long</span>
)
</code></pre>
<h2 id="heading-how-to-serialize-and-de-serialize">How to Serialize and De-Serialize</h2>
<p>Now that our data class is set up, let’s enjoy the fruits of our labor. Whenever we need to serialize our data class, we will use the <a target="_blank" href="https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/encode-to-string.html">Json.encodeToString</a> method:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> dataAsString: String = Json.encodeToString(user)
</code></pre>
<p>When we run the above line of code, we will get our data class in string form.</p>
<p>De-serializing our data is as simple as serializing it. We will use the <a target="_blank" href="https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/decode-from-string.html">Json.decodeFromString</a> method:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> user: UserModel = Json.decodeFromString&lt;UserModel&gt;(dataAsString)
</code></pre>
<blockquote>
<p>✋ Notice that we specified which type of data we want to de-serialize to with the type parameter ().</p>
</blockquote>
<p><img src="https://images.unsplash.com/photo-1600176842064-635fe81d2441?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDMwfHxyZW1vdGUlMjBjb250cm9sfGVufDB8fHx8MTY3NTAxODM0NQ&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Image" width="600" height="400" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@macroman?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"&gt;Immo Wegmann / &lt;a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm<em>campaign=api-credit)</em></p>
<p>Time for some extra credit.</p>
<p>Let’s say that in your data class you have a field that you don’t want to serialize. If we take our UserModel class, imagine that we want to have a user’s actual picture (bitmap).</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Serializable</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span></span>(
   <span class="hljs-meta">@SerialName(<span class="hljs-meta-string">"first-name"</span>)</span>
   <span class="hljs-keyword">val</span> firstName: String,
   <span class="hljs-keyword">val</span> lastName: String,
   <span class="hljs-keyword">val</span> age: <span class="hljs-built_in">Int</span>,
   <span class="hljs-keyword">val</span> birthdate: Date,
   <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Long</span>,
   <span class="hljs-keyword">var</span> photo: Bitmap?
)
</code></pre>
<p>This is not something we will get from our API call, so how can we exclude it? Because if we don’t, our serialization will fail.</p>
<p>Here to our rescue is the <a target="_blank" href="https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/"><strong>@Transient</strong> annotation</a>.</p>
<pre><code>@Serializable
data <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span>(
   @<span class="hljs-title">SerialName</span>("<span class="hljs-title">first</span>-<span class="hljs-title">name</span>")
   <span class="hljs-title">val</span> <span class="hljs-title">firstName</span>: <span class="hljs-title">String</span>,
   <span class="hljs-title">val</span> <span class="hljs-title">lastName</span>: <span class="hljs-title">String</span>,
   <span class="hljs-title">val</span> <span class="hljs-title">age</span>: <span class="hljs-title">Int</span>,
   <span class="hljs-title">val</span> <span class="hljs-title">birthdate</span>: <span class="hljs-title">Date</span>,
   <span class="hljs-title">val</span> <span class="hljs-title">id</span>: <span class="hljs-title">Long</span>,
   @<span class="hljs-title">Transient</span>
   <span class="hljs-title">var</span> <span class="hljs-title">photo</span>: <span class="hljs-title">Bitmap</span>?
)</span>
</code></pre><p>This will exclude the marked field from being serialized and de-serialized.</p>
<ul>
<li>If you want to see a real life example of using serialization inside a project, you can check out a project I made <a target="_blank" href="https://medium.com/r?url=https%3A%2F%2Fgithub.com%2FTomerPacific%2Fmovies-presenter">here</a></li>
<li>And if you would like to read other articles I have written, you can go <a target="_blank" href="https://medium.com/r?url=https%3A%2F%2Fgithub.com%2FTomerPacific%2FMediumArticles">here</a></li>
<li>For more information about the kotlin-serialization library, you can go <a target="_blank" href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/basic-serialization.md#json-encoding">here</a></li>
</ul>
<p>Thank you for reading! Happy serializing.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Hide Your API Keys in an Android Application ]]>
                </title>
                <description>
                    <![CDATA[ Let's say that you are using a version control system and your project uses services that require API keys. Everything is all good when they're on your local machine, but you don’t want to share these API keys with the world. So how can you still pre... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/hide-your-api-keys-in-android/</link>
                <guid isPermaLink="false">66ba4ffb43a51af2a76f7565</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Mon, 16 Jan 2023 21:03:19 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/oleg-didenko-lMNo9SwBN_o-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Let's say that you are using a version control system and your project uses services that require API keys. Everything is all good when they're on your local machine, but you don’t want to share these API keys with the world.</p>
<p>So how can you still preserve your API keys within your application, but also hide them when you upload your code to your repository?</p>
<p>You probably want to be able to still use your API keys in a normal fashion inside your applications, but also not expose them.</p>
<h2 id="heading-what-are-secrets">What Are Secrets?</h2>
<p>That’s where <strong>secrets</strong> come in. Similar to those that you keep only to yourself, but in a developer kind of way.</p>
<p>Secrets can represent crucial information that is required by your application to operate, but should not be visible to anyone working outside the project. </p>
<p>These can be API keys or authorization tokens, but in essence a secret is any piece of authorization information that should only be used by you and you alone. It's similar to how you don’t want to share your password to a website with anyone else.</p>
<p>🚨 Disclaimer: Be aware that the solution provided in this article works for not exposing your secrets from your version control system. But since they are part of your application, they can still be discovered by decompiling your APK.</p>
<h2 id="heading-how-to-keep-your-secrets-safe">How to Keep Your Secrets Safe</h2>
<p>In your project, you should have a <strong>local.properties</strong> file under the root directory of your project</p>
<p>To make sure it is ignored by your version control system, open the .gitignore file and see that it is found there:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/1_Br5FcOmNI-SVp7QxWM3FYA.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You will need to import to your project the <a target="_blank" href="https://github.com/google/secrets-gradle-plugin">Secrets Gradle plugin</a>.</p>
<p>To do this, go to your project’s root build.gradle file and paste in the following line:</p>
<pre><code class="lang-kotlin">buildscript {
    dependencies {
        id <span class="hljs-string">'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'</span> version <span class="hljs-string">'2.0.1'</span> apply <span class="hljs-literal">false</span>
    }
}
</code></pre>
<p>Next, go to your app’s build.gradle file and paste in the following line:</p>
<pre><code class="lang-kotlin">plugins {
    ...
    id <span class="hljs-string">'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'</span>
}
</code></pre>
<p>Add your API key inside the local.properties file:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/1__T-HkbD9isK3IuCLdE1v5w.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can use your secret inside your <strong>AndroidManifest.xml</strong> file by adding a meta-data tag inside your application tag:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">application</span>
        <span class="hljs-attr">android:allowBackup</span>=<span class="hljs-string">"true"</span> 
        <span class="hljs-attr">.....</span>
                                     &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">activity</span>&gt;</span>
      ....
    <span class="hljs-tag">&lt;/<span class="hljs-name">activity</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">meta-data</span>
            <span class="hljs-attr">android:name</span>=<span class="hljs-string">"YOUR_API_KEY_NAME"</span>     /// <span class="hljs-attr">Choose</span> <span class="hljs-attr">any</span> <span class="hljs-attr">value</span> <span class="hljs-attr">here</span>
            <span class="hljs-attr">android:value</span>=<span class="hljs-string">"${API_KEY_NAME}"</span>/&gt;</span>    /// Write the name you gave inside your local.properties file
<span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span>
</code></pre>
<p>To access your API key, you can use the PackageManager to get the meta data:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> applicationInfo: ApplicationInfo = application.packageManager
                .getApplicationInfo(application.packageName, PackageManager.GET_META_DATA)
<span class="hljs-keyword">val</span> apiKey = applicationInfo.metaData[<span class="hljs-string">"YOUR_API_KEY_NAME"</span>]
</code></pre>
<p>Alternatively, you can also use the BuildConfig object to get it:</p>
<pre><code class="lang-kotlin">BuildConfig.YOUR_API_KEY_NAME
</code></pre>
<p>That’s it. Now you can rest easy knowing that your secrets won’t be exposed by your version control system.</p>
<p>Enjoy keeping your secrets. ㊙️</p>
<p>I used this on one of my recent projects, and you can see the source code (sans the secrets) <a target="_blank" href="https://github.com/TomerPacific/movies-presenter">here</a>.</p>
<p>And if you want to check out other articles that I have written, you can go <a target="_blank" href="https://github.com/TomerPacific/MediumArticles">here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
