<?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: Mambaur Roziq</title>
    <description>The latest articles on Forem by Mambaur Roziq (@heyroziq).</description>
    <link>https://forem.com/heyroziq</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%2F3299890%2Fa540e2dc-c12d-450e-bc3a-b160e3770d74.png</url>
      <title>Forem: Mambaur Roziq</title>
      <link>https://forem.com/heyroziq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/heyroziq"/>
    <language>en</language>
    <item>
      <title>Setting Up Flutter CI/CD with GitHub Actions and Firebase App Distribution Securely</title>
      <dc:creator>Mambaur Roziq</dc:creator>
      <pubDate>Mon, 30 Jun 2025 04:02:28 +0000</pubDate>
      <link>https://forem.com/heyroziq/setting-up-flutter-cicd-with-github-actions-and-firebase-app-distribution-securely-346a</link>
      <guid>https://forem.com/heyroziq/setting-up-flutter-cicd-with-github-actions-and-firebase-app-distribution-securely-346a</guid>
      <description>&lt;p&gt;Automating Flutter app build and distribution saves time, enforces consistency, and enables rapid delivery to testers and users. This guide will help you set up a secure Flutter CI/CD pipeline using GitHub Actions and Firebase App Distribution while keeping sensitive files like keystores and environment files out of your repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Automate Flutter CI/CD?
&lt;/h2&gt;

&lt;p&gt;Building and distributing manually introduces inconsistency, human error, and repetitive overhead, especially when working in teams or maintaining frequent updates. By using CI/CD:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You automatically build release APK/AAB files on each push or pull request.&lt;/li&gt;
&lt;li&gt;Builds are consistent across environments.&lt;/li&gt;
&lt;li&gt;You can distribute builds to testers automatically via Firebase App Distribution.&lt;/li&gt;
&lt;li&gt;You keep sensitive files secure and out of version control.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Files You Should Not Commit to Git
&lt;/h2&gt;

&lt;p&gt;To keep your app secure, the following files should remain out of your repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt; files containing API keys or environment configurations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;android/app/upload-keystore.jks&lt;/code&gt; or any keystore used for signing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;android/key.properties&lt;/code&gt; containing keystore passwords and paths.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google-services.json&lt;/code&gt; and GoogleService-Info.plist (optional but recommended).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Managing Sensitive Files with GitHub Secrets
&lt;/h2&gt;

&lt;p&gt;Since GitHub Actions cannot access files ignored by Git, we use GitHub Secrets and &lt;code&gt;base64&lt;/code&gt; encoding to handle these files securely during CI/CD.&lt;/p&gt;

&lt;p&gt;To add GitHub Secrets for your Flutter CI/CD, go to your repository on GitHub, click &lt;code&gt;Settings&lt;/code&gt; at the top, then select &lt;code&gt;Secrets and variables&lt;/code&gt; in the left sidebar under the &lt;code&gt;Security&lt;/code&gt; section. Choose the &lt;code&gt;Actions&lt;/code&gt; tab and click &lt;code&gt;New repository secret&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storing &lt;code&gt;.env&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Copy your entire &lt;code&gt;.env&lt;/code&gt; file content and save it under &lt;code&gt;ENV_FILE&lt;/code&gt; in your GitHub Secrets.&lt;/p&gt;

&lt;p&gt;In your workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Generate .env file
  run: |
    echo "${{ secrets.ENV_FILE }}" &amp;gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling &lt;code&gt;key.properties&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Save your &lt;code&gt;key.properties&lt;/code&gt; content to a secret named &lt;code&gt;KEY_PROPERTIES&lt;/code&gt;. Ensure the &lt;code&gt;storeFile&lt;/code&gt; path matches where you will place your keystore during CI/CD. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;storePassword=your_store_password
keyPassword=your_key_password
keyAlias=your_key_alias
storeFile=your-keystore.jks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate it in your workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Generate key.properties
  run: |
    echo "${{ secrets.KEY_PROPERTIES }}" &amp;gt; android/key.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Managing the Keystore File
&lt;/h3&gt;

&lt;p&gt;Since GitHub Secrets do not accept files, convert your keystore to &lt;code&gt;base64&lt;/code&gt; on your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base64 upload-keystore.jks &amp;gt; upload-keystore.jks.base64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the output and save it in GitHub Secrets as &lt;code&gt;KEYSTORE_BASE64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In your workflow, decode it before the build step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Decode keystore
  run: |
    echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode &amp;gt; android/app/your-keystore.jks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Firebase Configuration
&lt;/h3&gt;

&lt;p&gt;Generate a Firebase CI token using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;firebase login:ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store it in GitHub Secrets as &lt;code&gt;FIREBASE_TOKEN&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Your GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;Create a workflow file, for example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.github/workflows/flutter-firebase-distribution.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Build &amp;amp; Distribute Flutter App

on:
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    name: Build and distribute Android
    runs-on: ubuntu-latest

    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - name: Set up Flutter
        uses: subosito/flutter-action@v2
        with:
          channel: stable

      - name: Generate .env file
        run: |
          echo "${{ secrets.ENV_FILE }}" &amp;gt; .env

      - name: Generate key.properties
        run: |
          echo "${{ secrets.KEY_PROPERTIES }}" &amp;gt; android/key.properties

      - name: Decode keystore
        run: |
          echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode &amp;gt; android/app/your-keystore.jks

      - name: Install dependencies
        run: flutter pub get

      - name: Build APK
        run: flutter build apk --release

      - name: Install Firebase CLI
        run: npm install -g firebase-tools

      - name: Upload to Firebase App Distribution
        run: |
          firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk \
            --app &amp;lt;App ID&amp;gt; \
            --release-notes "Automated build from GitHub Actions" \
            --groups "tester-group"
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow automates building and distributing your Flutter app using GitHub Actions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;on&lt;/code&gt; defines when the workflow runs. Here, it triggers manually (&lt;code&gt;workflow_dispatch&lt;/code&gt;), on every push to &lt;code&gt;main&lt;/code&gt;, and when a &lt;code&gt;pull request&lt;/code&gt; targets main.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jobs&lt;/code&gt; contains the tasks the workflow will run. Here, there is a single job named build.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;build&lt;/code&gt; runs on &lt;code&gt;ubuntu-latest&lt;/code&gt; and handles the Flutter build and distribution process.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;steps&lt;/code&gt; are the tasks executed in order:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Checkout your repository code.&lt;/li&gt;
&lt;li&gt;Set up Flutter using the stable channel.&lt;/li&gt;
&lt;li&gt;Generate the &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;key.properties&lt;/code&gt; files from GitHub Secrets.&lt;/li&gt;
&lt;li&gt;Decode your &lt;code&gt;keystore&lt;/code&gt; for signing.&lt;/li&gt;
&lt;li&gt;Install dependencies with &lt;code&gt;flutter pub get&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build a release APK.&lt;/li&gt;
&lt;li&gt;Install the Firebase CLI.&lt;/li&gt;
&lt;li&gt;Upload the APK to Firebase App Distribution for testers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This workflow ensures that every push or pull request to &lt;code&gt;main&lt;/code&gt; automatically builds your Flutter app and distributes it to your testers without manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Start and Monitor Your Flutter CI/CD
&lt;/h2&gt;

&lt;p&gt;To start your Flutter CI/CD pipeline, you can trigger the workflow automatically by pushing commits to the &lt;code&gt;main&lt;/code&gt; branch or by creating or updating a pull request targeting &lt;code&gt;main&lt;/code&gt;. You can also run it manually using the “Run workflow” button in the Actions tab on your GitHub repository, which uses &lt;code&gt;workflow_dispatch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once triggered, GitHub Actions will automatically start building your Flutter app, following the steps defined in your workflow file. You can monitor the progress and logs of each step in real time by going to the Actions tab in your repository, selecting the running workflow, and expanding the job and steps to see detailed output for debugging or confirming successful builds and distribution. This allows you to ensure your Flutter app builds consistently and is distributed automatically without manual steps each time you update your code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sxgvt8meavrbece9mo4.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%2F5sxgvt8meavrbece9mo4.png" alt="Github Action Flutter" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Setting up a secure Flutter CI/CD pipeline using GitHub Actions and Firebase App Distribution involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeping sensitive files out of your repository.&lt;/li&gt;
&lt;li&gt;Managing secrets securely using GitHub Secrets and base64 encoding.&lt;/li&gt;
&lt;li&gt;Automating build, test, and distribution processes.&lt;/li&gt;
&lt;li&gt;Providing consistent, reliable builds to your testers automatically.&lt;/li&gt;
&lt;li&gt;Once set up, you will save hours of manual work and ensure your Flutter app delivery process is robust, secure, and scalable.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flutter</category>
      <category>cicd</category>
      <category>githubactions</category>
      <category>apk</category>
    </item>
    <item>
      <title>Dio vs HTTP in Flutter: A Practical, Clear Comparison</title>
      <dc:creator>Mambaur Roziq</dc:creator>
      <pubDate>Sat, 28 Jun 2025 05:47:23 +0000</pubDate>
      <link>https://forem.com/heyroziq/dio-vs-http-in-flutter-a-practical-clear-comparison-2id8</link>
      <guid>https://forem.com/heyroziq/dio-vs-http-in-flutter-a-practical-clear-comparison-2id8</guid>
      <description>&lt;p&gt;When building Flutter applications that interact with APIs, two of the most commonly used packages are &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;dio&lt;/code&gt;. Both allow you to perform HTTP requests and handle responses, but they serve different needs and offer different levels of flexibility and scalability depending on the complexity of your application.&lt;/p&gt;

&lt;p&gt;This article will help you understand why you may want to use &lt;code&gt;dio&lt;/code&gt; over &lt;code&gt;http&lt;/code&gt; in Flutter, what benefits &lt;code&gt;dio&lt;/code&gt; brings to real-world applications, and what trade-offs you should consider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding http in Flutter
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;http&lt;/code&gt; package is a lightweight and minimalistic HTTP client for Flutter and Dart. It is straightforward to use for making GET, POST, PUT, and DELETE requests. However, it is a low-level package, providing only the basic building blocks for network communication without advanced features.&lt;/p&gt;

&lt;p&gt;For example, fetching data from an API using &lt;code&gt;http&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode == 200) {
  final data = jsonDecode(response.body);
  print(data);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You must manually handle JSON decoding, error management, and token handling. While this is acceptable for small, simple applications, it can lead to repetitive code and higher maintenance in more complex projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding dio in Flutter
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dio&lt;/code&gt; is a feature-rich HTTP client designed for scalability and ease of use in real-world Flutter applications. It comes with advanced capabilities out of the box, including automatic JSON decoding, request and response interceptors, file uploads and downloads with progress reporting, request cancellation, and flexible configuration options like base URLs and global headers.&lt;/p&gt;

&lt;p&gt;Fetching the same data with &lt;code&gt;dio&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final dio = Dio();
final response = await dio.get('https://api.example.com/data');
print(response.data);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;dio&lt;/code&gt; automatically decodes the JSON response, simplifying your code and reducing the risk of repetitive parsing logic scattered across your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Benefits of Using dio Over http
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Automatic JSON Decoding
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;http&lt;/code&gt;, you are responsible for decoding JSON from the response body manually using jsonDecode. &lt;code&gt;dio&lt;/code&gt;, on the other hand, automatically decodes JSON responses and makes the data directly accessible, reducing boilerplate and potential decoding errors throughout your codebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'package:dio/dio.dart';

final dio = Dio();
final response = await dio.get('https://jsonplaceholder.typicode.com/posts/1');

print(response.data['title']);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Global Configuration Support
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;dio&lt;/code&gt;, you can configure a base URL, default headers, connection and receive timeouts, and other settings globally. This removes the need to configure these settings in every request, ensuring consistency and reducing duplication in your networking code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: Duration(seconds: 10),
  headers: {'Authorization': 'Bearer YOUR_TOKEN'},
));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;http&lt;/code&gt;, such configurations need to be handled manually on each request, increasing boilerplate and potential inconsistencies in headers or timeouts across your requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interceptors for Request and Response Handling
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dio&lt;/code&gt; offers interceptors, which allow you to inspect, modify, or handle requests, responses, and errors globally before they reach your app logic. This is particularly useful for adding authentication tokens automatically, logging requests and responses for debugging, or handling specific status codes (such as redirecting users to a login page on 401 Unauthorized errors) without duplicating code.&lt;/p&gt;

&lt;p&gt;While &lt;code&gt;http&lt;/code&gt; does not have built-in support for interceptors, achieving similar functionality requires wrapping your request logic in custom methods, which increases complexity and reduces clarity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final dio = Dio();

dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    options.headers['Authorization'] = 'Bearer token123';
    print('Requesting: ${options.uri}');
    return handler.next(options);
  },
  onResponse: (response, handler) {
    print('Received: ${response.statusCode}');
    return handler.next(response);
  },
  onError: (error, handler) {
    print('Error: ${error.message}');
    return handler.next(error);
  },
));

await dio.get('https://jsonplaceholder.typicode.com/posts/1');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Built-in Support for File Uploads
&lt;/h2&gt;

&lt;p&gt;Uploading files with &lt;code&gt;dio&lt;/code&gt; is straightforward using its &lt;code&gt;FormData&lt;/code&gt; API, which simplifies multipart form uploads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FormData formData = FormData.fromMap({
  'file': await MultipartFile.fromFile(file.path),
});
await dio.post('/upload', data: formData);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In contrast, using &lt;code&gt;http&lt;/code&gt; for file uploads requires managing &lt;code&gt;MultipartRequest&lt;/code&gt;, which is more verbose and less intuitive, especially when handling multiple files or fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downloading Files with Progress Reporting
&lt;/h2&gt;

&lt;p&gt;In applications where you need to download files and display download progress to users, dio offers built-in support for tracking progress during downloads. You can easily update progress indicators in your UI using the progress callback provided by &lt;code&gt;dio&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;http&lt;/code&gt; for downloading files requires handling streams manually, which can be complex and error-prone for beginners and increases the amount of code you need to maintain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await dio.download(
  'https://speed.hetzner.de/100MB.bin',
  '/local/path/100MB.bin',
  onReceiveProgress: (received, total) {
    if (total != -1) {
      print('${(received / total * 100).toStringAsFixed(0)}% downloaded');
    }
  },
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Request Cancellation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dio&lt;/code&gt; allows you to cancel an ongoing request using a &lt;code&gt;CancelToken&lt;/code&gt;. This is particularly useful in Flutter apps where a user may navigate away from a screen before a request completes, allowing you to save resources and prevent unnecessary UI updates.&lt;/p&gt;

&lt;p&gt;In contrast, &lt;code&gt;http&lt;/code&gt; does not support canceling requests once they have been initiated, which can lead to wasted network calls and memory usage in your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CancelToken cancelToken = CancelToken();

dio.get(
  'https://jsonplaceholder.typicode.com/posts',
  cancelToken: cancelToken,
);

// To cancel:
cancelToken.cancel('Request cancelled by user.');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Error Handling with Structured Information
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dio&lt;/code&gt; provides a structured &lt;code&gt;DioError&lt;/code&gt; object that gives you detailed information about what went wrong in your request, including the error type, the request that caused the error, and any available response data. This enables more effective error handling and user feedback in your app.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http&lt;/code&gt; provides basic exception handling, and you often need to parse error responses manually to extract meaningful error information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
  await dio.get('https://api.example.com/data');
} on DioError catch (e) {
  if (e.type == DioExceptionType.connectionTimeout) {
    print('Connection timed out');
  } else if (e.response?.statusCode == 404) {
    print('Data not found');
  } else {
    print('Other error: ${e.message}');
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;http&lt;/code&gt; package is slightly faster than &lt;code&gt;dio&lt;/code&gt; in raw network calls because it has fewer abstractions and less overhead. However, in real-world applications, this difference is negligible, as the network latency itself dominates the total request time.&lt;/p&gt;

&lt;p&gt;The trade-off of slightly lower raw performance with &lt;code&gt;dio&lt;/code&gt; is often acceptable considering the significant gains in developer productivity, code maintainability, and scalability for larger applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should You Use &lt;code&gt;http&lt;/code&gt;?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When you are building a small app with minimal API requests.&lt;/li&gt;
&lt;li&gt;When you want a lightweight dependency with the smallest possible app size.&lt;/li&gt;
&lt;li&gt;When you do not need advanced features like file uploads, downloads, interceptors, or cancellation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Should You Use &lt;code&gt;dio&lt;/code&gt;?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When your app requires user authentication and token management across many requests.&lt;/li&gt;
&lt;li&gt;When your app involves file uploads or downloads with progress reporting.&lt;/li&gt;
&lt;li&gt;When you need structured, centralized error handling.&lt;/li&gt;
&lt;li&gt;When you want scalable, clean network architecture for a project that will grow over time.&lt;/li&gt;
&lt;li&gt;When you prefer automatic JSON decoding and global configuration to reduce repetitive code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Choosing between &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;dio&lt;/code&gt; in Flutter depends on your application's current and future needs. While &lt;code&gt;http&lt;/code&gt; is an excellent choice for simple applications that only require a few API requests, &lt;code&gt;dio&lt;/code&gt; provides a powerful, scalable, and developer-friendly solution for apps that require advanced networking capabilities and clean architecture.&lt;/p&gt;

&lt;p&gt;If you are building a production-ready Flutter application that needs to handle complex API interactions, token management, file uploads, downloads with progress, or centralized error handling, using &lt;code&gt;dio&lt;/code&gt; will make your code cleaner, easier to maintain, and more scalable as your app grows.&lt;/p&gt;

&lt;p&gt;If you would like, I can also prepare a ready-to-use dio_service.dart structure to help you set up a clean, scalable networking layer in your Flutter project.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>http</category>
      <category>dio</category>
    </item>
  </channel>
</rss>
