<?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" version="2.0"><channel><title><![CDATA[Elatmani.dev — Dev Notes]]></title><description><![CDATA[Elatmani.dev — Dev Notes]]></description><link>https://blog.elatmani.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/69ee4f743d6a492cdd2f6928/291b6fb1-428e-43f6-919a-c79b0c169e9e.png</url><title>Elatmani.dev — Dev Notes</title><link>https://blog.elatmani.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 10 Oct 2026 23:57:45 GMT</lastBuildDate><atom:link href="https://blog.elatmani.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How I Built a Small Tool to Inspect Dart Package Dependencies
]]></title><description><![CDATA[If you work with Flutter for long enough, you eventually run into this kind of error:
Because device_info_plus 13.2.0 depends on win32 ^6.0.1
and file_picker >=8.3.3 <12.0.0-beta.1 depends on win32 ^5]]></description><link>https://blog.elatmani.dev/how-i-built-a-small-tool-to-inspect-dart-package-dependencies</link><guid isPermaLink="true">https://blog.elatmani.dev/how-i-built-a-small-tool-to-inspect-dart-package-dependencies</guid><dc:creator><![CDATA[houssam elatmani]]></dc:creator><pubDate>Thu, 13 Aug 2026 11:52:55 GMT</pubDate><content:encoded><![CDATA[<p>If you work with Flutter for long enough, you eventually run into this kind of error:</p>
<pre><code class="language-text">Because device_info_plus 13.2.0 depends on win32 ^6.0.1
and file_picker &gt;=8.3.3 &lt;12.0.0-beta.1 depends on win32 ^5.9.0,
device_info_plus ^13.2.0 is incompatible with file_picker ^11.0.2.
</code></pre>
<p>At first, this looks simple: two packages depend on <code>win32</code>, but on different versions.</p>
<p>The annoying part is figuring out <strong>where that dependency comes from and what version constraint a specific package version actually declares</strong>.</p>
<p>So I built a small tool to make that easier.</p>
<h2>The problem</h2>
<p>Imagine a Flutter project with:</p>
<pre><code class="language-yaml">dependencies:
  file_picker: ^11.0.2
  device_info_plus: ^13.2.0
</code></pre>
<p>Running:</p>
<pre><code class="language-bash">flutter pub get
</code></pre>
<p>fails because:</p>
<pre><code class="language-text">file_picker
    → win32 ^5.9.0

device_info_plus
    → win32 ^6.0.1
</code></pre>
<p>These constraints don't overlap.</p>
<p>The problem becomes more interesting when the dependency isn't direct.</p>
<p>You might have:</p>
<pre><code class="language-text">my_app
└── package_a
    └── package_b
        └── win32
</code></pre>
<p>Looking only at your <code>pubspec.yaml</code> doesn't tell you the whole story.</p>
<h2>Pub already knows the answer</h2>
<p>The Dart Pub API contains the published <code>pubspec</code> for every package version.</p>
<p>For example:</p>
<pre><code class="language-bash">curl -s https://pub.dev/api/packages/file_picker
</code></pre>
<p>The response contains all published versions.</p>
<p>We can then select a specific version with <code>jq</code>:</p>
<pre><code class="language-bash">curl -s https://pub.dev/api/packages/file_picker |
jq '.versions[] | select(.version == "11.0.2") | .pubspec.dependencies'
</code></pre>
<p>For <code>file_picker 11.0.2</code>, this gives:</p>
<pre><code class="language-json">{
  "flutter": {
    "sdk": "flutter"
  },
  "flutter_web_plugins": {
    "sdk": "flutter"
  },
  "flutter_plugin_android_lifecycle": "^2.0.22",
  "plugin_platform_interface": "^2.1.8",
  "ffi": "^2.1.3",
  "path": "^1.9.0",
  "win32": "^5.9.0",
  "cross_file": "^0.3.4+2",
  "web": "^1.1.0",
  "dbus": "^0.7.11"
}
</code></pre>
<p>Now the problem is immediately clear:</p>
<pre><code class="language-text">file_picker 11.0.2
    └── win32 ^5.9.0
</code></pre>
<p>And:</p>
<pre><code class="language-text">device_info_plus 13.2.0
    └── win32 ^6.0.1
</code></pre>
<h2>Turning it into a small tool</h2>
<p>I didn't want to type the API command manually every time.</p>
<p>The first version is just a small Bash script:</p>
<pre><code class="language-bash">#!/bin/bash

read -p "Package name: " PACKAGE
read -p "Version: " VERSION

echo
echo "Dependencies for $PACKAGE $VERSION:"
echo

curl -fsSL "https://pub.dev/api/packages/$PACKAGE" |
  jq --arg version "$VERSION" '
    .versions[]
    | select(.version == $version)
    | .pubspec.dependencies
  '
</code></pre>
<p>Now I can run:</p>
<pre><code class="language-bash">./pub_deps.sh
</code></pre>
<p>and enter:</p>
<pre><code class="language-text">Package name: file_picker
Version: 11.0.2
</code></pre>
<p>The tool returns the dependencies declared by that exact package version.</p>
<h2>A more useful version</h2>
<p>The next step is to make the tool answer a more specific question:</p>
<pre><code class="language-text">Package: file_picker
Version: 11.0.2
Dependency: win32

Result:
win32 ^5.9.0
</code></pre>
<p>And if the package doesn't directly depend on <code>win32</code>:</p>
<pre><code class="language-text">Package: some_package
Version: 2.0.0
Dependency: win32

Result:
No direct dependency on win32.
</code></pre>
<p>This distinction matters because a package can depend on another package that eventually depends on <code>win32</code>.</p>
<h2>Direct vs transitive dependencies</h2>
<p>There are two different situations:</p>
<pre><code class="language-text">file_picker
└── win32
</code></pre>
<p>Here, <code>win32</code> is a direct dependency of <code>file_picker</code>.</p>
<p>But you can also have:</p>
<pre><code class="language-text">package_a
└── package_b
    └── package_c
        └── win32
</code></pre>
<p>Here, <code>win32</code> is a transitive dependency.</p>
<p>Dart's <code>pub deps</code> command is useful once the project successfully resolves:</p>
<pre><code class="language-bash">dart pub deps --style=tree
</code></pre>
<p>But there is an important limitation: if <code>pub get</code> fails, there is no resolved dependency graph to inspect.</p>
<p>That's exactly the situation where querying the published package metadata becomes useful.</p>
<h2>Why I think this is useful</h2>
<p>Package conflicts are common in Flutter projects, especially when working with plugins that depend on platform-specific packages.</p>
<p>The error message from Pub is technically correct, but sometimes you want to investigate the package versions yourself:</p>
<pre><code class="language-text">Which version introduced this dependency?

What version constraint does this package actually declare?

Did the dependency change between two releases?

Which package is preventing me from upgrading?
</code></pre>
<p>Instead of opening several package pages and manually searching through their versions, a small dependency inspector can answer these questions quickly.</p>
<h2>The next step</h2>
<p>The Bash version is only the beginning.</p>
<p>A proper tool could provide something like:</p>
<pre><code class="language-text">Dart Dependency Inspector

Package A
file_picker 11.0.2
    └── win32 ^5.9.0

Package B
device_info_plus 13.2.0
    └── win32 ^6.0.1

--------------------------------
CONFLICT

Required ranges:

file_picker:
&gt;=5.9.0 &lt;6.0.0

device_info_plus:
&gt;=6.0.1 &lt;7.0.0

No compatible version exists.
</code></pre>
<p>It could also compare package versions and show when a dependency changed between releases.</p>
<p>That would turn a simple API wrapper into something more useful: a small <strong>Dart/Flutter dependency investigation tool</strong>.</p>
<p>Sometimes a useful developer tool starts with a frustrating <code>pub get</code> error.</p>
]]></content:encoded></item><item><title><![CDATA[How to Build a 100% Automated Media Server for Free]]></title><description><![CDATA[Automated Media Stack: Jellyfin, Sonarr, and Prowlarr on Ubuntu
Setting up a home media server doesn't have to be complicated. In this guide, we will deploy a full "Arr" stack using Docker. This setup]]></description><link>https://blog.elatmani.dev/how-to-build-a-100-automated-media-server-for-free</link><guid isPermaLink="true">https://blog.elatmani.dev/how-to-build-a-100-automated-media-server-for-free</guid><dc:creator><![CDATA[houssam elatmani]]></dc:creator><pubDate>Wed, 13 May 2026 11:34:07 GMT</pubDate><content:encoded><![CDATA[<hr />
<h1>Automated Media Stack: Jellyfin, Sonarr, and Prowlarr on Ubuntu</h1>
<p>Setting up a home media server doesn't have to be complicated. In this guide, we will deploy a full "Arr" stack using Docker. This setup includes automated downloading, indexing, and a beautiful streaming interface.</p>
<h2>🏗️ The Infrastructure</h2>
<p>We are using a standard Ubuntu server. This stack includes:</p>
<ul>
<li><p><strong>Jellyfin</strong>: The media server (like your personal Netflix).</p>
</li>
<li><p><strong>Sonarr</strong>: Automates TV show management.</p>
</li>
<li><p><strong>Prowlarr</strong>: Manages your indexers and trackers.</p>
</li>
<li><p><strong>qBittorrent</strong>: The download engine.</p>
</li>
<li><p><strong>FlareSolverr &amp; Byparr</strong>: Bypasses search blocks (Cloudflare).</p>
</li>
</ul>
<hr />
<h2>1. Initial Server Setup</h2>
<p>First, update your system and install Docker if you haven't already.</p>
<pre><code class="language-bash"># Update system
sudo apt update &amp;&amp; sudo apt upgrade -y

# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
sudo apt install docker-compose-plugin -y
</code></pre>
<h2>2. Create the Directory Structure</h2>
<p>Organization is key for Docker volumes to work correctly.</p>
<pre><code class="language-bash">mkdir -p ~/houssam/p7/{media/{downloads,tv,movies},configs}
cd ~/houssam/p7
</code></pre>
<h2>3. The Docker Compose Configuration</h2>
<p>Create the <code>docker-compose.yml</code> file:</p>
<pre><code class="language-yaml">services:
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Africa/Casablanca
      - WEBUI_PORT=8080
    volumes:
      - ./qbittorrent-config:/config
      - /home/your_username/houssam/p7/media/downloads:/downloads
    ports:
      - "8080:8080"
    restart: unless-stopped

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Africa/Casablanca
    volumes:
      - ./sonarr-config:/config
      - /home/your_username/houssam/p7/media/tv:/tv
      - /home/your_username/houssam/p7/media/downloads:/downloads
    ports:
      - "8989:8989"
    restart: unless-stopped

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Africa/Casablanca
    volumes:
      - ./prowlarr-config:/config
    ports:
      - "9696:9696"
    restart: unless-stopped

  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Africa/Casablanca
    volumes:
      - ./jellyfin-config:/config
      - /home/your_username/houssam/p7/media/tv:/data/tvshows
      - /home/your_username/houssam/p7/media/movies:/data/movies
    ports:
      - "8096:8096"
    restart: unless-stopped

  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    environment:
      - LOG_LEVEL=info
      - TZ=Africa/Casablanca
    ports:
      - "8191:8191"
    restart: unless-stopped
</code></pre>
<p><strong>Deploy the stack:</strong></p>
<pre><code class="language-bash">docker compose up -d
</code></pre>
<hr />
<h2>4. Post-Installation Checklist</h2>
<h3>🗂️ Step 1: qBittorrent</h3>
<ul>
<li><p><strong>URL</strong>: <code>http://YOUR_IP:8080</code></p>
</li>
<li><p><strong>Action</strong>: Change the default username/password in Settings immediately.</p>
</li>
</ul>
<h3>📺 Step 2: Sonarr</h3>
<ul>
<li><p><strong>URL</strong>: <code>http://YOUR_IP:8989</code></p>
</li>
<li><p><strong>Setup</strong>: Add your root folder as <code>/tv</code>.</p>
</li>
<li><p><strong>Connect qBittorrent</strong>: Settings → Download Clients → Add qBittorrent.</p>
</li>
<li><p><em>Host</em>: <code>qbittorrent</code> (if using Docker network) or your Server IP.</p>
</li>
</ul>
<h3>🔍 Step 3: Prowlarr</h3>
<ul>
<li><p><strong>URL</strong>: <code>http://YOUR_IP:9696</code></p>
</li>
<li><p><strong>Connect Sonarr</strong>: Settings → Apps → Add Sonarr.</p>
</li>
<li><p><em>Server</em>: <code>http://sonarr:8989</code></p>
</li>
<li><p><em>API Key</em>: Copy this from Sonarr (Settings → General).</p>
</li>
</ul>
<h3>🎬 Step 4: Jellyfin</h3>
<ul>
<li><p><strong>URL</strong>: <code>http://YOUR_IP:8096</code></p>
</li>
<li><p><strong>Action</strong>: Follow the wizard and point your libraries to <code>/data/tvshows</code> and <code>/data/movies</code>.</p>
</li>
</ul>
<hr />
<h2>📱 Level Up: Control Everything From Your Phone &amp; TV</h2>
<p>The best part of this stack is that you don't need to use a browser once it's set up. You can manage downloads and watch movies using dedicated apps.</p>
<h3>1. Watch Anywhere: Jellyfin Mobile &amp; Android TV</h3>
<p>The official <strong>Jellyfin app</strong> is your portal to your movie library.</p>
<ul>
<li><p><strong>Mobile</strong>: Available on iOS and Android. Enter your server address: <code>http://YOUR_IP:8096</code>.</p>
</li>
<li><p><strong>Android TV</strong>: You can install the Jellyfin app directly on your <strong>Smart TV, Nvidia Shield, or Chromecast with Google TV</strong> for a full cinema experience in your living room.</p>
</li>
<li><p><strong>Pro Tip</strong>: For iPad or iPhone, try <strong>Swiftfin</strong>—it’s a native player that is incredibly fast.</p>
</li>
</ul>
<h3>2. Manage on the Go: LunaSea or nzb360</h3>
<p>These apps allow you to search for a new movie while you're out and have it ready when you get home.</p>
<ul>
<li><p><strong>LunaSea (iOS/Android)</strong>: Clean and minimal. Add a "New Profile" for Sonarr using your Server IP and API Key.</p>
</li>
<li><p><strong>nzb360 (Android)</strong>: The power user choice. It supports the entire stack and gives you a beautiful "Coming Soon" calendar.</p>
</li>
</ul>
<hr />
<h2>Extra types :</h2>
<hr />
<h2>🔐 Public vs. Private Indexers: Why Quality Matters</h2>
<p>When you first set up Prowlarr, you might be tempted to just add every public site you find. However, not all torrents are created equal.</p>
<h3>Public Indexers (The "Wild West")</h3>
<p>Public sites (like 1337x or The Pirate Bay) are open to everyone.</p>
<ul>
<li><p><strong>The Problem</strong>: Torrents are often old, "dead" (no seeders), or low quality. Because anyone can upload, you also run a higher risk of downloading fake files or broken releases.</p>
</li>
<li><p><strong>Speed</strong>: Since there is no requirement to "seed" (share) back, many people download and immediately stop sharing, leading to slow speeds.</p>
</li>
</ul>
<h3>Private Indexers (The "VIP Club")</h3>
<p>Private indexers are closed communities that require an account.</p>
<ul>
<li><p><strong>The Benefit</strong>: These sites have strict rules. Members <strong>must</strong> maintain a good "ratio" (share as much as they download), which means lightning-fast download speeds and high-quality files that stay alive for years.</p>
</li>
<li><p><strong>Quality Control</strong>: Files are often verified by the community to ensure they are high-definition and safe.</p>
</li>
</ul>
<hr />
<h2>🎟️ How to Get Into Private Indexers (For Free)</h2>
<p>Most private trackers are "Invite Only," but you don't need to pay for them. Here is how you can get in for free:</p>
<ol>
<li><p><strong>Monitor "Open Signups"</strong>: Many great private trackers open their doors for a few hours every month to let new members in.</p>
</li>
<li><p><strong>Use Reddit</strong>: Join the <a href="https://www.reddit.com/r/OpenSignups/"><strong>r/OpenSignups</strong></a> community. Set up an alert so you get a notification the moment a high-quality private indexer opens its registration.</p>
</li>
<li><p><strong>Interview/Application</strong>: Some elite trackers (like MAM or RED) allow you to join by passing a simple "knowledge interview" about how torrenting works.</p>
</li>
</ol>
<h3>Integrating with Prowlarr</h3>
<p>Once you get an account on a private site, simply go to <strong>Prowlarr &gt; Add Indexer</strong>, search for the site name, and enter your <strong>API Key</strong> or <strong>Cookie</strong> from that site. Prowlarr will then automatically sync that high-quality source to Sonarr.</p>
<hr />
<p><strong>That’s it! You now have a high-end streaming service that you own and control completely. Happy watching!</strong></p>
]]></content:encoded></item><item><title><![CDATA[I Got Tired of Rewriting OAuth in Every Flutter App — So I Built a Package]]></title><description><![CDATA[Building oauth_connection — Flutter OAuth Without the Headache

How I built a reusable Flutter package to drop Google, Facebook, and Instagram sign-in into any project in minutes — not days.

Tags: fl]]></description><link>https://blog.elatmani.dev/i-got-tired-of-rewriting-oauth-in-every-flutter-app-so-i-built-a-package</link><guid isPermaLink="true">https://blog.elatmani.dev/i-got-tired-of-rewriting-oauth-in-every-flutter-app-so-i-built-a-package</guid><dc:creator><![CDATA[houssam elatmani]]></dc:creator><pubDate>Thu, 30 Apr 2026 22:52:06 GMT</pubDate><content:encoded><![CDATA[<h1>Building <code>oauth_connection</code> — Flutter OAuth Without the Headache</h1>
<blockquote>
<p>How I built a reusable Flutter package to drop Google, Facebook, and Instagram sign-in into any project in minutes — not days.</p>
</blockquote>
<p><strong>Tags:</strong> <code>flutter</code> <code>dart</code> <code>oauth</code> <code>bloc</code> <code>open-source</code></p>
<hr />
<p>Every new project I started had the same painful ritual: configure Google OAuth, set up Facebook Auth, wrestle with Instagram's WebView flow, wire up a BLoC, handle errors, translate messages… all over again. So I stopped repeating myself and built <strong>oauth_connection</strong> — a Flutter package that bundles all of it into a single, drop-in integration.</p>
<hr />
<h2>The Problem Worth Solving</h2>
<p>OAuth integration sounds simple on paper. In practice, it is three separate SDKs, three sets of platform setup steps, three state machines to manage, and a handful of edge cases — cancelled logins, token fetch failures, missing backend responses — that all need graceful handling. Multiply that by every project and you have a reliable recipe for wasted days.</p>
<p>I wanted a package with a clear contract: <strong>initialize once, drop a button widget anywhere, and get a clean token back</strong> (or a typed error state). No boilerplate scattered across your codebase.</p>
<blockquote>
<p>💡 <strong>Design goal:</strong> The consuming app should only care about two things — the success token and the failure reason. Everything else is the package's problem.</p>
</blockquote>
<hr />
<h2>What's Inside the Package</h2>
<p>The package covers three providers, each with sign-in and sign-up flows:</p>
<table>
<thead>
<tr>
<th>Provider</th>
<th>SDK Used</th>
<th>Flow Type</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Google</strong></td>
<td><code>google_sign_in ^7.2.0</code></td>
<td>Native SDK with event stream</td>
</tr>
<tr>
<td><strong>Facebook</strong></td>
<td><code>flutter_facebook_auth ^7.1.6</code></td>
<td>Native SDK dialog</td>
</tr>
<tr>
<td><strong>Instagram</strong></td>
<td>Custom (<code>webview_flutter</code>)</td>
<td>WebView + one-time code intercept</td>
</tr>
</tbody></table>
<p>Each provider follows the same structure: a <strong>Controller</strong> (singleton, handles SDK calls and HTTP), a <strong>Cubit</strong> (BLoC state management), and ready-to-use <strong>Button widgets</strong> with built-in loading and error states.</p>
<hr />
<h2>Architecture at a Glance</h2>
<p>The package is organized around a clean three-layer architecture. The UI never talks to the SDK directly — it only fires a Cubit method and listens to typed states.</p>
<pre><code>┌─────────────────────────────────────────────┐
│         UI Layer                            │
│   SignInButton / SignUpButton               │
└────────────────────┬────────────────────────┘
                     ↕
┌─────────────────────────────────────────────┐
│         State Layer (BLoC / Cubit)          │
│   GoogleAuthCubit / FacebookAuthCubit /     │
│   InstagramAuthCubit                        │
└────────────────────┬────────────────────────┘
                     ↕
┌─────────────────────────────────────────────┐
│         Controller Layer                    │
│   GoogleAuthController /                   │
│   FacebookAuthController /                 │
│   InstagramAuthController                  │
└────────────────────┬────────────────────────┘
                     ↕
┌─────────────────────────────────────────────┐
│         SDK &amp; Network Layer                 │
│   google_sign_in · flutter_facebook_auth ·  │
│   WebView · http                            │
└─────────────────────────────────────────────┘
</code></pre>
<hr />
<h2>Setting Up the Controllers</h2>
<p>Each controller is a singleton initialized once — ideally in <code>main()</code> before <code>runApp</code>. Here's Google as an example:</p>
<pre><code class="language-dart">void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Google OAuth controller once
  await GoogleAuthController.init(
    serverClientId: 'YOUR_SERVER_CLIENT_ID',
    clientId:       'YOUR_CLIENT_ID',
    host:           'api.yourapp.com',
    loginEndpoint:  '/auth/google/login',
    registerEndpoint: '/auth/google/register',
    debugLogs: true, // flip off in production
  );

  runApp(const MyApp());
}
</code></pre>
<p>Facebook and Instagram follow the same pattern — call <code>FacebookAuthController.init()</code> and <code>InstagramAuthController.init()</code> with your app credentials and backend endpoints.</p>
<hr />
<h2>Dropping a Button Into Your UI</h2>
<p>Once initialized, adding sign-in is a single widget. The button manages its own loading state — it disables itself while the OAuth flow is in progress, so you never need a separate boolean in your page's state.</p>
<pre><code class="language-dart">GoogleSignInButton(
  showErrorMessage: true,
  onSuccess: (GoogleAuthSuccess state) {
    // state.accessToken is ready — store it, navigate, etc.
    saveToken(state.accessToken);
    navigateHome();
  },
  onFailure: (GoogleAuthFailure state) {
    // state.translatedMessage is already localized
    showSnackBar(state.translatedMessage);
  },
)
</code></pre>
<p>The same API shape exists for <code>FacebookSignInButton</code>, <code>FacebookSignUpButton</code>, <code>GoogleSignUpButton</code>, <code>InstagramSignInButton</code>, and <code>InstagramSignUpButton</code>.</p>
<hr />
<h2>How the Google Flow Works Internally</h2>
<p>The Google controller takes advantage of the new event-stream API introduced in <code>google_sign_in</code> v7. Rather than awaiting a one-shot future, it subscribes to a stream of authentication events — which handles both fresh logins and re-authentication cleanly.</p>
<pre><code class="language-dart">// Inside GoogleAuthController.configure()
_googleSignIn.authenticationEvents.listen((event) async {
  if (event is GoogleSignInAuthenticationEventSignIn) {
    currentUser = event.user;

    if (action == SIGN_IN) {
      await _login(
        email: event.user.email,
        accessToken: await getAccessToken(),
      );
    } else if (action == SIGN_UP) {
      await _register(
        email: event.user.email,
        accessToken: await getAccessToken(),
      );
    }
  }
  // Resolve the Completer so the Cubit knows we're done
  _authCompleter?.complete();
});
</code></pre>
<p>A <code>Completer</code> bridges the event-driven SDK and the <code>await</code>-friendly Cubit interface — the Cubit simply does <code>await controller.login()</code> and knows the flow is finished when it returns, regardless of success or failure.</p>
<hr />
<h2>Instagram's Custom WebView Flow</h2>
<p>Instagram's OAuth doesn't have an official Flutter SDK, so I built one inside the package. It opens a <code>WebView</code> pointed at Instagram's authorization URL, intercepts the redirect containing the one-time code, and pipes it to your backend.</p>
<pre><code class="language-dart">final String fullAuthUrl =
  "https://api.instagram.com/oauth/authorize"
  "?client_id=$clientId"
  "&amp;redirect_uri=${Uri.encodeComponent(redirectUrl)}"
  "&amp;scope=instagram_business_basic"
  "&amp;response_type=code";

FlutterInstagramApi.login(
  context,
  instaConfig: InstaConfig(
    clientId: clientId,
    instaUrl: fullAuthUrl,
  ),
  oneTimeCode: (code) async {
    // Exchange the code with your backend
    await _login(code);
    onComplete();
  },
  onCancel: () {
    errorCode = "CANCELLED";
    onComplete();
  },
);
</code></pre>
<hr />
<h2>Error Handling &amp; Typed States</h2>
<p>One of the design decisions I'm most satisfied with is the typed error system. The Cubit emits discrete states:</p>
<ul>
<li><code>GoogleAuthInitial</code></li>
<li><code>GoogleAuthLoading</code></li>
<li><code>GoogleAuthSuccess</code> — carries <code>accessToken</code></li>
<li><code>GoogleAuthFailure</code> — carries <code>errorCode</code> + <code>translatedMessage</code></li>
</ul>
<p>The error codes map to real scenarios:</p>
<table>
<thead>
<tr>
<th>Error Code</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>CANCELLED</code></td>
<td>User dismissed the OAuth popup</td>
</tr>
<tr>
<td><code>NOT_SUPPORTED</code></td>
<td>Platform doesn't support the auth flow</td>
</tr>
<tr>
<td><code>FETCH_TOKEN_FAILED</code></td>
<td>Backend responded but returned no token</td>
</tr>
<tr>
<td><code>FETCH_TOKEN_ERROR</code></td>
<td>Network or parsing error with backend</td>
</tr>
<tr>
<td><code>REGISTERING_USER_FAILED</code></td>
<td>Backend rejected the new user registration</td>
</tr>
<tr>
<td><code>LOGIN_ERROR</code></td>
<td>Unexpected SDK-level error</td>
</tr>
</tbody></table>
<hr />
<h2>Built-in Localization — 4 Languages</h2>
<p>The package ships with its own <code>OAuthConnection.delegate</code> that you add alongside your app's localization delegates. All error and UI strings are available in:</p>
<ul>
<li>🇺🇸 English</li>
<li>🇪🇸 Spanish</li>
<li>🇫🇷 French</li>
<li>🇸🇦 Arabic (including RTL support)</li>
</ul>
<pre><code class="language-dart">MaterialApp(
  localizationsDelegates: [
    OAuthConnection.delegate, // ← add this
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ],
  supportedLocales: const [
    Locale('en'),
    Locale('es'),
    Locale('fr'),
    Locale('ar'),
  ],
)
</code></pre>
<hr />
<h2>Key Dependencies</h2>
<table>
<thead>
<tr>
<th>Package</th>
<th>Version</th>
<th>Role</th>
</tr>
</thead>
<tbody><tr>
<td><code>google_sign_in</code></td>
<td>^7.2.0</td>
<td>New stream-based auth event API</td>
</tr>
<tr>
<td><code>flutter_facebook_auth</code></td>
<td>^7.1.6</td>
<td>Facebook login dialog + token exchange</td>
</tr>
<tr>
<td><code>flutter_bloc</code></td>
<td>^9.1.1</td>
<td>Clean, testable state management</td>
</tr>
<tr>
<td><code>webview_flutter</code></td>
<td>^4.13.1</td>
<td>Custom Instagram OAuth WebView</td>
</tr>
<tr>
<td><code>sign_in_button</code></td>
<td>^4.1.0</td>
<td>Branded button styles out of the box</td>
</tr>
<tr>
<td><code>font_awesome_flutter</code></td>
<td>^10.8.0</td>
<td>Provider icons for buttons</td>
</tr>
</tbody></table>
<hr />
<h2>Why I Built This</h2>
<p>The Flutter ecosystem has individual packages for each OAuth provider — but nothing that unifies them behind a consistent API, manages state via BLoC, talks to your own backend, and handles errors in multiple languages. <strong>oauth_connection</strong> fills that gap.</p>
<p>The real value isn't the lines of code. It's that the next time I start a project that needs social login, I spend ten minutes on configuration, not two days on integration.</p>
<p>I plan to expand the package with Apple Sign-In support and a more flexible backend integration API. If you try it out or spot an improvement, I'd love to hear from you.</p>
<hr />
<p><em>Built with Flutter · BLoC · Google Sign-In · Facebook Auth · Instagram OAuth</em></p>
<hr />
<p>📦 <strong>Check out the package on GitHub:</strong>
<a href="https://github.com/houssam15/oauth_connections">github.com/houssam15/oauth_connections</a></p>
<p>If it helped you, drop a ⭐ — it means a lot!</p>
]]></content:encoded></item></channel></rss>