<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Adham Lou</title>
    <description>The latest articles on Forem by Adham Lou (@amine_karimii).</description>
    <link>https://forem.com/amine_karimii</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2792200%2Ff003afb7-1fea-46a1-9535-09739f1e665d.jpg</url>
      <title>Forem: Adham Lou</title>
      <link>https://forem.com/amine_karimii</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/amine_karimii"/>
    <language>en</language>
    <item>
      <title>Analytics in Android</title>
      <dc:creator>Adham Lou</dc:creator>
      <pubDate>Tue, 18 Feb 2025 11:01:22 +0000</pubDate>
      <link>https://forem.com/amine_karimii/analytics-in-android-1cfb</link>
      <guid>https://forem.com/amine_karimii/analytics-in-android-1cfb</guid>
      <description>&lt;h2&gt;
  
  
  Handling Multiple Analytics Providers in Android: The Struggle and the Solution
&lt;/h2&gt;

&lt;p&gt;In today's mobile development landscape, analytics play a crucial role in understanding user behavior, improving app performance, and making data-driven decisions. However, integrating multiple analytics providers in an Android project can quickly become a nightmare. From managing different SDKs to dealing with various APIs, event structures, and data formats, developers often find themselves entangled in a complex web of tracking code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenges of Using Multiple Analytics Providers
&lt;/h2&gt;

&lt;p&gt;Many Android apps rely on multiple analytics services such as Google Analytics, Firebase, Mixpanel, Amplitude, and others. While each of these tools provides valuable insights, handling them all at once introduces several challenges:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Inconsistent APIs and Event Structures&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Each provider has its own way of tracking events, user properties, and sessions. This leads to redundancy in the codebase and makes it difficult to maintain consistency in event tracking across all platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Increased Code Complexity&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Manually integrating multiple SDKs means writing a lot of repetitive code to ensure each event is logged in all services. This results in bloated code and increased chances of errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Harder Debugging and Maintenance&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When something goes wrong in event tracking, debugging can be a nightmare. Identifying whether the issue is with a specific provider’s SDK or the implementation itself becomes challenging.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Performance Overhead&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Each analytics SDK adds some level of overhead to the app, leading to increased memory usage and potential performance bottlenecks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing &lt;strong&gt;Analytiks&lt;/strong&gt;: A Unified Solution
&lt;/h2&gt;

&lt;p&gt;To address these challenges, &lt;strong&gt;Analytiks&lt;/strong&gt; provides a simple and elegant solution. &lt;a href="https://github.com/aminekarimii/analytiks" rel="noopener noreferrer"&gt;Analytiks&lt;/a&gt; is an open-source library that acts as an abstraction layer, allowing developers to integrate multiple analytics providers seamlessly with a single, unified API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Choose Analytiks?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified API:&lt;/strong&gt; Define events once and dispatch them to multiple providers effortlessly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Integration:&lt;/strong&gt; Reduce boilerplate code by handling multiple analytics providers through a single interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability:&lt;/strong&gt; Easier to manage and extend event tracking without modifying multiple SDK implementations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Optimization:&lt;/strong&gt; Reduce redundant calls and optimize performance by controlling event dispatching efficiently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How Analytiks Works
&lt;/h3&gt;

&lt;p&gt;With Analytiks, integrating multiple analytics providers is as easy as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Analytiks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomAnalytiksAddon&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MixpanelAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;SegmentAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;flushIntervalInSeconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;trackApplicationLifecycleEvents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize the addons&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;analytiks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're good to go!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;analytiks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"your_event_name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;analytiks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pushAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using &lt;strong&gt;Analytiks&lt;/strong&gt;, developers can simplify event tracking, improve maintainability, and ensure consistent data collection across different platforms. If you're looking for a cleaner, more efficient way to handle analytics in your Android app, check out &lt;a href="https://github.com/aminekarimii/analytiks" rel="noopener noreferrer"&gt;Analytiks on GitHub&lt;/a&gt; and start optimizing your analytics implementation today!&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>analytics</category>
    </item>
    <item>
      <title>From Movie Night to Android App: Turning a Simple Idea into a Real Product</title>
      <dc:creator>Adham Lou</dc:creator>
      <pubDate>Tue, 11 Feb 2025 17:14:47 +0000</pubDate>
      <link>https://forem.com/amine_karimii/from-movie-night-to-android-app-turning-a-simple-idea-into-a-real-product-2b1</link>
      <guid>https://forem.com/amine_karimii/from-movie-night-to-android-app-turning-a-simple-idea-into-a-real-product-2b1</guid>
      <description>&lt;p&gt;Have you ever had a simple idea during a casual moment that turned into something bigger? That’s exactly what happened with &lt;strong&gt;MovieMatcher&lt;/strong&gt;, my Android app designed to help friends, couples, or groups pick a movie together effortlessly. It all started with a frustrating movie night and ended with a full-fledged Android app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Movie Night Indecision
&lt;/h2&gt;

&lt;p&gt;Like many, I love watching movies with friends and family. But every time we planned a movie night, we spent &lt;strong&gt;way too much time&lt;/strong&gt; debating which movie to watch. Some liked action, others preferred drama, and we always ended up scrolling endlessly through streaming platforms without making a decision.&lt;/p&gt;

&lt;p&gt;At one point, I thought: &lt;em&gt;What if there was an app that helped us decide?&lt;/em&gt; Something simple, fun, and interactive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea: A Movie Matching App
&lt;/h2&gt;

&lt;p&gt;The goal was clear: &lt;strong&gt;Create an app that helps two users agree on a movie quickly.&lt;/strong&gt; The concept was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each user swipes through a set of movie options.&lt;/li&gt;
&lt;li&gt;If both users like the same movie, it’s a match!&lt;/li&gt;
&lt;li&gt;If not, they keep swiping until they find a common choice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Tech Behind the App
&lt;/h2&gt;

&lt;p&gt;As an Android developer, I saw this as a great opportunity to build something practical while experimenting with modern Android tools. Here’s what I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jetpack Compose&lt;/strong&gt;: For a smooth and declarative UI experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firebase&lt;/strong&gt;: To handle real-time synchronization between users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TMDB API&lt;/strong&gt;: To fetch movie details dynamically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ktor Client&lt;/strong&gt;: For efficient network requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DataStore Preferences&lt;/strong&gt;: To store user preferences and settings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges and Solutions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Syncing:&lt;/strong&gt; Since two users needed to interact with the same session, I used Firebase’s real-time database to instantly update movie lists and matches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Randomized Movie Lists:&lt;/strong&gt; Instead of showing the same popular titles repeatedly, I implemented a system that fetches random movies in batches from TMDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeping It Lightweight:&lt;/strong&gt; I focused on a simple, intuitive UI to ensure users could quickly navigate and make decisions without distractions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Creating a Landing Page with AI
&lt;/h2&gt;

&lt;p&gt;To help promote &lt;strong&gt;MovieMatcher&lt;/strong&gt;, I decided to build a landing page. Instead of designing everything manually, I leveraged AI-powered tools to streamline the process:&lt;br&gt;
Already talked about it in this article: &lt;a href="https://dev.to/amine_karimii/web-developers-you-are-cheating-3gl0"&gt;Web developers you are cheating&lt;/a&gt;&lt;br&gt;
You can check directly what it  looks like: &lt;br&gt;
&lt;a href="https://moviematch.app/" rel="noopener noreferrer"&gt;moviematch.app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building &lt;strong&gt;MovieMatcher&lt;/strong&gt; reinforced some key principles for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solve real problems&lt;/strong&gt; – even a small frustration like movie indecision can inspire a useful app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep it simple&lt;/strong&gt; – the best apps focus on doing one thing well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use modern tools&lt;/strong&gt; – leveraging Jetpack Compose, Firebase, and Ktor made development more efficient and scalable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;I’m currently working on new features like &lt;strong&gt;filters for genres&lt;/strong&gt;, &lt;strong&gt;watchlist integration&lt;/strong&gt;, and &lt;strong&gt;personalized recommendations&lt;/strong&gt;. The journey from a simple movie night struggle to a working Android app has been exciting, and I can’t wait to see where it goes next.&lt;/p&gt;

</description>
      <category>android</category>
      <category>programming</category>
      <category>ai</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Shine Text Effect in Jetpack Compose</title>
      <dc:creator>Adham Lou</dc:creator>
      <pubDate>Fri, 07 Feb 2025 23:00:22 +0000</pubDate>
      <link>https://forem.com/amine_karimii/shine-text-effect-in-jetpack-compose-50fe</link>
      <guid>https://forem.com/amine_karimii/shine-text-effect-in-jetpack-compose-50fe</guid>
      <description>&lt;p&gt;Shimmer effects are a great way to enhance the user experience by indicating loading states with a smooth animated placeholder.&lt;br&gt;
I always use it in the call-to-action events, it gave a more premium effect.&lt;/p&gt;

&lt;p&gt;In this post, we will implement a shining text based on the shimmering effect in Jetpack Compose using this example from &lt;a href="https://gist.github.com/L10n42/9361c9729b743012a0b3499cc28f42d0" rel="noopener noreferrer"&gt;this gist&lt;/a&gt; and extend it with a method to update the shimmer color dynamically.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is a Shimmer Effect?
&lt;/h2&gt;

&lt;p&gt;A shimmer effect is an animated gradient overlay that creates an illusion of content loading. It gives users a visual cue that data is being fetched or processed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementing Shimmer in Jetpack Compose
&lt;/h2&gt;

&lt;p&gt;We will create a shimmer effect using a custom &lt;code&gt;ShaderBrush&lt;/code&gt; that animates across the text.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Define the ShimmeringText Composable
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ShimmeringText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;shimmerColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;textStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TextStyle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalTextStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;animationSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DurationBasedAnimationSpec&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LinearEasing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;infiniteTransition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberInfiniteTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ShimmeringTextTransition"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;shimmerProgress&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;infiniteTransition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animateFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;targetValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;infiniteRepeatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animationSpec&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ShimmerProgress"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;brush&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shimmerProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;ShaderBrush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createShader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Shader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;initialXOffset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;totalSweepDistance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentPosition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initialXOffset&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;totalSweepDistance&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;shimmerProgress&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;LinearGradientShader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;colors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transparent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shimmerColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transparent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentPosition&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brush&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;brush&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 2: Updating Shimmer Color Dynamically
&lt;/h3&gt;

&lt;p&gt;To enhance the shimmer effect, we will use a utility function to adjust the shimmer color dynamically. The &lt;code&gt;brighter()&lt;/code&gt; function blends the color with white, creating a brighter effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;brighter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;blendRatio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3f&lt;/span&gt; &lt;span class="c1"&gt;// Adjust to control brightness&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;red&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;red&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;blendRatio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;blendRatio&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;coerceIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;green&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;green&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;blendRatio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;blendRatio&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;coerceIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;blue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;blendRatio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;blendRatio&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;coerceIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we modify the &lt;code&gt;ShimmeringText&lt;/code&gt; function to apply this color transformation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;DynamicShimmeringText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;baseColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;textStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TextStyle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalTextStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;shimmerColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;brighter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;ShimmeringText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;shimmerColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shimmerColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;textStyle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textStyle&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Using the Shimmer Effect in a UI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ShimmerScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;DynamicShimmeringText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Book a Demo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baseColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Gray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final result ✨
&lt;/h3&gt;

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

&lt;h6&gt;
  
  
  this example is the adapted version of the tutorial
&lt;/h6&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>design</category>
    </item>
    <item>
      <title>DeepSeek vs. ChatGPT in Jetpack Compose: Which One Generates the Best Results?</title>
      <dc:creator>Adham Lou</dc:creator>
      <pubDate>Wed, 05 Feb 2025 11:46:12 +0000</pubDate>
      <link>https://forem.com/amine_karimii/deepseek-vs-chatgpt-in-jetpack-compose-which-one-generates-the-best-results-ke2</link>
      <guid>https://forem.com/amine_karimii/deepseek-vs-chatgpt-in-jetpack-compose-which-one-generates-the-best-results-ke2</guid>
      <description>&lt;p&gt;Introduction&lt;/p&gt;

&lt;p&gt;Jetpack Compose has revolutionized Android UI development by offering a declarative approach that simplifies UI creation. As AI-powered coding assistants become more prevalent, developers increasingly rely on tools like DeepSeek and ChatGPT to streamline their workflows. But which one provides the best results when generating Jetpack Compose code? In this post, we’ll compare these two AI assistants using real-world prompts and evaluate their strengths and weaknesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Scenarios
&lt;/h2&gt;

&lt;p&gt;To make a fair comparison, we provided DeepSeek and ChatGPT with the following prompts and evaluated their outputs:&lt;/p&gt;

&lt;p&gt;Create a to-do list with a decent UI – Evaluating UI quality, state management, and performance.&lt;/p&gt;

&lt;p&gt;Convert an image into a composable – Testing adaptability and code accuracy.&lt;/p&gt;

&lt;p&gt;Generate a paginated list with API calls – Checking efficiency in handling real-world scenarios.&lt;/p&gt;

&lt;p&gt;Implement a dark mode toggle – Assessing how well state and UI updates are managed.&lt;/p&gt;

&lt;p&gt;Handle user authentication in Jetpack Compose – Measuring security best practices and implementation correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a To-Do List with a Decent UI
&lt;/h2&gt;

&lt;p&gt;DeepSeek: Generated a functional to-do list with a modern UI, proper state management using remember, and followed Jetpack Compose best practices. The design was minimal yet effective.&lt;/p&gt;

&lt;p&gt;ChatGPT: Produced a similar to-do list but with unnecessary complexity in state handling. Some UI elements were not as polished as DeepSeek's output.&lt;/p&gt;

&lt;p&gt;Winner: DeepSeek – More concise and visually appealing UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Convert an Image into a Composable
&lt;/h2&gt;

&lt;p&gt;DeepSeek: Accurately converted the image into a composable function, leveraging Canvas and ImageBitmap where necessary. It optimized performance by efficiently managing resources.&lt;/p&gt;

&lt;p&gt;ChatGPT: Generated a working composable but occasionally misused Jetpack Compose drawing APIs, leading to suboptimal rendering performance.&lt;/p&gt;

&lt;p&gt;Winner: DeepSeek – More accurate and optimized image conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Generate a Paginated List with API Calls
&lt;/h2&gt;

&lt;p&gt;DeepSeek: Implemented efficient pagination using LazyColumn and proper handling of API requests via ViewModel and remember. It followed recommended Kotlin Coroutines practices.&lt;/p&gt;

&lt;p&gt;ChatGPT: Successfully implemented pagination but had minor inefficiencies in network call handling, leading to unnecessary recompositions.&lt;/p&gt;

&lt;p&gt;Winner: DeepSeek – Better efficiency in API integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Implement a Dark Mode Toggle
&lt;/h2&gt;

&lt;p&gt;DeepSeek: Used MaterialTheme and proper state management to ensure seamless dark mode switching, providing a smooth user experience.&lt;/p&gt;

&lt;p&gt;ChatGPT: Achieved the same functionality but required additional refactoring to match best practices.&lt;/p&gt;

&lt;p&gt;Winner: DeepSeek – More streamlined implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Handle User Authentication in Jetpack Compose
&lt;/h2&gt;

&lt;p&gt;DeepSeek: Generated a secure and modular authentication flow using Firebase Authentication with proper state and session handling.&lt;/p&gt;

&lt;p&gt;ChatGPT: Created a functional authentication flow but included some redundant code and less secure token management practices.&lt;/p&gt;

&lt;p&gt;Winner: DeepSeek – More secure and maintainable implementation.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Based on our test scenarios, DeepSeek consistently outperformed ChatGPT in generating Jetpack Compose code that is accurate, efficient, and adheres to best practices. While ChatGPT remains a strong contender, DeepSeek currently provides superior real-world Jetpack Compose development results.&lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>ai</category>
      <category>deepseek</category>
    </item>
    <item>
      <title>Web developers... YOU are cheating!</title>
      <dc:creator>Adham Lou</dc:creator>
      <pubDate>Tue, 04 Feb 2025 11:30:23 +0000</pubDate>
      <link>https://forem.com/amine_karimii/web-developers-you-are-cheating-3gl0</link>
      <guid>https://forem.com/amine_karimii/web-developers-you-are-cheating-3gl0</guid>
      <description>&lt;p&gt;As an Android developer, I’ve spent years dealing with complex UI states, lifecycles, dependency injection, and, of course, the occasional Gradle sync nightmares. Web development? Never really touched it—always seemed like another world. But recently, I needed to create a landing page for my app, MovieMatcher, and let’s just say... web development feels like cheating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How I Ended Up Making a Landing Page&lt;/strong&gt;&lt;br&gt;
I needed a simple way to showcase MovieMatcher. An app without a landing page? Doesn’t feel right. But writing HTML, CSS, and dealing with front-end frameworks? No, thanks. I wanted the easiest, most efficient way to get it done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enter V0.ai: My Personal Web Developer&lt;/strong&gt;&lt;br&gt;
I came across V0.ai, an AI-powered tool that basically builds a website for you. I described what I wanted—a clean, simple landing page with a hero section, feature highlights, and a call-to-action button—and within seconds, it spits out a fully responsive React-based page. That’s it. No XML layouts, no RecyclerViews, no worrying about different screen densities. Just text-to-UI magic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Making It Fancy with Framer Motion&lt;/strong&gt;&lt;br&gt;
Now, the page worked, but it felt... plain. That’s where Framer Motion came in. Adding animations in Framer felt too easy compared to Android’s animation APIs. Want a smooth fade-in? One line of code. Slide effects? Another line. Meanwhile, in Android, I’d be dealing with state changes, remembering lifecycle quirks, and probably debugging some weird animation glitch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can you imagine that this page: &lt;a href="http://www.moviematch.app" rel="noopener noreferrer"&gt;www.moviematch.app&lt;/a&gt; is AI-generated? &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Deploying? One-Click Magic with Netlify&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final step: putting it online. I pushed my code to GitHub, linked it to &lt;strong&gt;Netlify&lt;/strong&gt;, and boom—it was live in minutes. No Firebase Hosting setup, no worrying about app bundles, just an instant deployment. Any update? Just push to GitHub, and Netlify handles the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts: Is Web Development Just Easier?&lt;/strong&gt;&lt;br&gt;
After this experience, I have to say—web development is ridiculously smooth. Setting up a web project felt like a walk in the park compared to the hoops we jump through in Android development. I’m not saying I’m switching teams, but I definitely have a newfound appreciation for how effortless things can be on the web side.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Feature Flags in Android with Jetpack Compose</title>
      <dc:creator>Adham Lou</dc:creator>
      <pubDate>Mon, 03 Feb 2025 09:54:49 +0000</pubDate>
      <link>https://forem.com/amine_karimii/feature-flags-in-android-with-jetpack-compose-4ik</link>
      <guid>https://forem.com/amine_karimii/feature-flags-in-android-with-jetpack-compose-4ik</guid>
      <description>&lt;p&gt;Feature flags are an essential tool for controlling feature rollouts, enabling A/B testing, and managing experimental features without requiring a new app release. In this guide, we’ll implement feature flags in an Android app using Jetpack Compose, DataStore Preferences, and app flavors.&lt;/p&gt;

&lt;p&gt;Why Use Feature Flags?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature flags allow you to:&lt;/li&gt;
&lt;li&gt;Enable or disable features dynamically&lt;/li&gt;
&lt;li&gt;Deploy incomplete features safely&lt;/li&gt;
&lt;li&gt;Roll out features to specific user groups&lt;/li&gt;
&lt;li&gt;Experiment with new ideas without affecting all users&lt;/li&gt;
&lt;li&gt;Now, let's walk through the implementation!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up App Flavors
&lt;/h2&gt;

&lt;p&gt;We need to define app flavors in the build to manage different feature flag configurations based on the app version (e.g., &lt;code&gt;free&lt;/code&gt;, &lt;code&gt;paid&lt;/code&gt;, &lt;code&gt;beta&lt;/code&gt;).gradle file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;flavorDimensions&lt;/span&gt; &lt;span class="s2"&gt;"version"&lt;/span&gt;
    &lt;span class="n"&gt;productFlavors&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;free&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="s2"&gt;"version"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;paid&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="s2"&gt;"version"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup allows us to maintain separate feature flag configurations for each flavor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Adding DataStore for Persistent Storage
&lt;/h2&gt;

&lt;p&gt;Jetpack DataStore (Preferences) is ideal for storing feature flags because it is lightweight and supports asynchronous data reading and writing.&lt;/p&gt;

&lt;p&gt;First, add the dependency to your &lt;code&gt;build.gradle&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"androidx.datastore:datastore-preferences:1.0.0"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a DataStore helper class to manage feature flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataStore&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;preferencesDataStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"feature_flags"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureFlagManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;FEATURE_X&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;booleanPreferencesKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"feature_x"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isFeatureXEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;preferences&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;FEATURE_X&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;setFeatureX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;FEATURE_X&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class provides a Flow to observe feature flag changes and a function to update flags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Providing Different Feature Flags for Each Flavor
&lt;/h2&gt;

&lt;p&gt;We’ll create separate JSON files for each flavor to predefine feature flags.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;free/src/main/assets/feature_flags.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature_x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;paid/src/main/assets/feature_flags.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature_x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we can load these values at app startup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFeatureFlagsFromAssets&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;assetName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"feature_flags.json"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;inputStream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;InputStream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assetName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bufferedReader&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;jsonObject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;jsonObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;asSequence&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;associateWith&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;jsonObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;emptyMap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Using Feature Flags in Jetpack Compose
&lt;/h2&gt;

&lt;p&gt;We can now use the FeatureFlagManager to control UI elements dynamically in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;FeatureXScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;featureFlagManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FeatureFlagManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isFeatureXEnabled&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;featureFlagManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFeatureXEnabled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nc"&gt;Column&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Feature X is ${if (isFeatureXEnabled) "&lt;/span&gt;&lt;span class="nc"&gt;Enabled&lt;/span&gt;&lt;span class="s"&gt;" else "&lt;/span&gt;&lt;span class="nc"&gt;Disabled&lt;/span&gt;&lt;span class="s"&gt;"}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isFeatureXEnabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* Do something */&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Try Feature X"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;By implementing feature flags with DataStore and app flavors, we gain greater flexibility in managing features dynamically. This approach enables safer releases and better control over feature rollouts. 🚀&lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>kotlin</category>
      <category>java</category>
    </item>
    <item>
      <title>5 weeks -&gt; Android App</title>
      <dc:creator>Adham Lou</dc:creator>
      <pubDate>Fri, 31 Jan 2025 14:43:13 +0000</pubDate>
      <link>https://forem.com/amine_karimii/5-weeks-android-app-1l87</link>
      <guid>https://forem.com/amine_karimii/5-weeks-android-app-1l87</guid>
      <description>&lt;p&gt;Five weeks. That’s all it took to bring MovieMatch from concept to a fully functional app. When I first set out to build it, I knew I wanted a seamless way for users to match movies with friends or partners, but I had no idea how quickly it would come together.&lt;/p&gt;

&lt;p&gt;The journey started with design. I spent the first week sketching out wireframes, defining the user flow, and ensuring the experience was intuitive. Given how many movie recommendation apps exist, I wanted MovieMatch to feel fresh—minimalistic yet engaging.&lt;/p&gt;

&lt;p&gt;Once the design was ready, development kicked off. With Android native powering the mobile app and Firebase handling the real-time database, the app quickly took shape. The biggest challenge was synchronizing sessions between users in real-time while keeping performance smooth. But with Firebase’s real-time capabilities, the experience became seamless.&lt;/p&gt;

&lt;p&gt;Testing and fine-tuning took up the final stretch. Handling edge cases, ensuring cross-device compatibility, and polishing UI interactions made sure the app felt refined.&lt;/p&gt;

&lt;p&gt;For the landing page, I used Next.js and Tailwind, making deployment quick and styling effortless. The site needed to be lightweight yet visually appealing, and this stack delivered exactly that.&lt;/p&gt;

&lt;p&gt;By the fifth week, MovieMatch was ready. What started as a simple idea turned into a fully functional app, built in just over a month. The journey reinforced that with the right tools and focus, shipping a quality product doesn’t have to take forever.&lt;/p&gt;

</description>
      <category>android</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Web Development Feels Like Magic Compared to Android</title>
      <dc:creator>Adham Lou</dc:creator>
      <pubDate>Thu, 30 Jan 2025 15:27:34 +0000</pubDate>
      <link>https://forem.com/amine_karimii/web-development-feels-like-magic-compared-to-android-52l2</link>
      <guid>https://forem.com/amine_karimii/web-development-feels-like-magic-compared-to-android-52l2</guid>
      <description>&lt;p&gt;I’ve been a mobile developer for years, battling with OEM-specific bugs, App Store reviews, and the never-ending Android fragmentation. But recently, I decided to dip my toes into web development.&lt;/p&gt;

&lt;p&gt;TL;DR: As a long-time mobile developer, I tried building a website using Next.js, Tailwind, and v0.ai. The process was surprisingly smooth, with instant deployment via Netlify and no platform-specific headaches. Best of all, it cost only $10 for the domain name. While mobile dev remains close to my heart, web development sometimes feels like the easier path.&lt;/p&gt;

&lt;p&gt;I was curious about how easy it really was, I picked up Next.js and Tailwind CSS, using v0.ai to generate some components. Within minutes, I had a responsive layout, something that would have taken much longer on mobile. The best part? No XML, no Compose previews failing to render—just a smooth development experience.&lt;/p&gt;

&lt;p&gt;Deploying the project was another revelation. With a simple setup, I had my app live on Netlify, accessible to anyone with a browser. No waiting for Play Store approvals, no worrying about different devices handling updates inconsistently. Debugging was a breeze too—hot reload worked instantly, and browser dev tools provided all the insights I needed. Even better, the entire website was free to host, and I only had to pay $10 for the domain name.&lt;/p&gt;

&lt;p&gt;Of course, web development has its quirks—CSS specificity, browser inconsistencies, and learning new deployment pipelines. But compared to the hurdles I’ve faced in mobile, it felt almost too easy.&lt;/p&gt;

&lt;p&gt;Does this mean I’m leaving mobile behind? Not at all. Mobile development has its own charm, and there’s something special about building apps people carry with them everywhere. But I’ll admit—sometimes, taking the easy route is tempting.&lt;/p&gt;

&lt;p&gt;For those who want to see the results, you can check out the website here: &lt;a href="https://moviematch.app/" rel="noopener noreferrer"&gt;moviematch.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ai</category>
      <category>android</category>
      <category>tailwindcss</category>
    </item>
  </channel>
</rss>
