<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Destiny Ed</title>
    <description>The latest articles on DEV Community by Destiny Ed (@destinyed).</description>
    <link>https://dev.to/destinyed</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%2F1059724%2F01e4ffcf-428c-49cb-8f7f-24bd323b29e9.jpeg</url>
      <title>DEV Community: Destiny Ed</title>
      <link>https://dev.to/destinyed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/destinyed"/>
    <language>en</language>
    <item>
      <title>How I built DocTextExtractor to power NotteChat's AI-powered document chat, and how you can integrate it into your own Flutter apps. http://bit.ly/4jWr2VZ</title>
      <dc:creator>Destiny Ed</dc:creator>
      <pubDate>Thu, 15 May 2025 07:33:43 +0000</pubDate>
      <link>https://dev.to/destinyed/how-i-built-doctextextractor-to-power-nottechats-ai-powered-document-chat-and-how-you-can-18l6</link>
      <guid>https://dev.to/destinyed/how-i-built-doctextextractor-to-power-nottechats-ai-powered-document-chat-and-how-you-can-18l6</guid>
      <description></description>
      <category>flutter</category>
      <category>ai</category>
      <category>dart</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>DocTextExtractor: A Flutter Package to Extract Text from Word, PDF, Google Docs, and Markdown</title>
      <dc:creator>Destiny Ed</dc:creator>
      <pubDate>Wed, 14 May 2025 21:06:39 +0000</pubDate>
      <link>https://dev.to/destinyed/doctextextractor-a-flutter-package-to-extract-text-from-word-pdf-google-docs-and-markdown-5gj4</link>
      <guid>https://dev.to/destinyed/doctextextractor-a-flutter-package-to-extract-text-from-word-pdf-google-docs-and-markdown-5gj4</guid>
      <description>&lt;p&gt;How I built DocTextExtractor to power NotteChat's AI-powered document chat, and how you can integrate it into your own Flutter apps.&lt;/p&gt;

&lt;p&gt;As a Flutter developer with a passion for simplifying complex problems, I created &lt;a href="https://pub.dev/packages/doc_text_extractor" rel="noopener noreferrer"&gt;DocTextExtractor&lt;/a&gt;—a lightweight, open-source Dart package that extracts text from &lt;code&gt;.doc&lt;/code&gt;, &lt;code&gt;.docx&lt;/code&gt;, &lt;code&gt;.pdf&lt;/code&gt;, Google Docs URLs, and &lt;code&gt;.md&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;This tool was born from the challenges I faced while building &lt;a href="https://nottechat.com" rel="noopener noreferrer"&gt;NotteChat&lt;/a&gt;, an app that allows users to chat with document content using AI. In this article, I’ll share how I built &lt;strong&gt;DocTextExtractor&lt;/strong&gt;, why it matters, and how you can integrate it into your own Flutter projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built DocTextExtractor
&lt;/h2&gt;

&lt;p&gt;NotteChat empowers students, professionals, and educators to interact with documents conversationally. Users simply paste a URL or upload a file to a document and can then summarize, explore, or ask questions about the content using AI.&lt;/p&gt;

&lt;p&gt;However, supporting multiple document formats (.doc, .docx, PDF, Google Docs, Markdown) posed a serious challenge. Most existing Flutter solutions only worked for specific formats, and there was no unified solution.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;DocTextExtractor&lt;/strong&gt;, with a goal to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support &lt;code&gt;.doc&lt;/code&gt;, &lt;code&gt;.docx&lt;/code&gt;, &lt;code&gt;.pdf&lt;/code&gt;, &lt;code&gt;.md&lt;/code&gt;, and Google Docs URLs&lt;/li&gt;
&lt;li&gt;Handle both local files and URLs&lt;/li&gt;
&lt;li&gt;Enable offline parsing for &lt;code&gt;.doc&lt;/code&gt; and &lt;code&gt;.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Power AI by providing clean, structured text&lt;/li&gt;
&lt;li&gt;Extract real filenames for better UX&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building DocTextExtractor
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Identifying the Need
&lt;/h3&gt;

&lt;p&gt;The core feature of NotteChat—chatting with document content—meant I needed a consistent way to extract text, regardless of format or source.&lt;/p&gt;

&lt;p&gt;Key Requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unified API for all formats&lt;/li&gt;
&lt;li&gt;Clean filename extraction&lt;/li&gt;
&lt;li&gt;Minimal dependencies&lt;/li&gt;
&lt;li&gt;Cross-platform support (iOS, Android, Web)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Choosing the Tech Stack
&lt;/h3&gt;

&lt;p&gt;I relied on the trusted Flutter/Dart ecosystem with the following tools and packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http&lt;/code&gt;: Fetch documents via URLs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;syncfusion_flutter_pdf&lt;/code&gt;: Parse and extract PDFs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;archive&lt;/code&gt; + &lt;code&gt;xml&lt;/code&gt;: Extract from &lt;code&gt;.docx&lt;/code&gt; and &lt;code&gt;.doc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;markdown&lt;/code&gt;: Convert &lt;code&gt;.md&lt;/code&gt; to plain text&lt;/li&gt;
&lt;li&gt;VS Code + GitHub for development and version control&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub repo: &lt;a href="https://github.com/Destiny-Ed/doc_text_extractor" rel="noopener noreferrer"&gt;github.com/Destiny-Ed/doc_text_extractor&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Designing the Core Logic
&lt;/h3&gt;

&lt;p&gt;At the heart of the package is the &lt;code&gt;TextExtractor&lt;/code&gt; class with a single &lt;code&gt;extractText()&lt;/code&gt; method.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Features:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified Return Type&lt;/strong&gt;: A &lt;code&gt;Record(text, filename)&lt;/code&gt; for easy use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Format Detection&lt;/strong&gt;: Checks HTTP Content-Type or file extension&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline Support&lt;/strong&gt;: No internet required for &lt;code&gt;.doc&lt;/code&gt; and &lt;code&gt;.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling&lt;/strong&gt;: Friendly exceptions (e.g., "Unsupported document type")&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Format-Specific Logic
&lt;/h3&gt;

&lt;p&gt;Each format was tackled with custom logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.doc&lt;/code&gt;: No existing Dart parser, so I created one using raw XML parsing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.docx&lt;/code&gt;: Unzipped and parsed &lt;code&gt;word/document.xml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.md&lt;/code&gt;: Used &lt;code&gt;markdown&lt;/code&gt; package for plain-text conversion&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PDF&lt;/code&gt;: Parsed using &lt;code&gt;syncfusion_flutter_pdf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Google Docs&lt;/code&gt;: Converted &lt;code&gt;/edit&lt;/code&gt; URLs to &lt;code&gt;/export?format=pdf&lt;/code&gt; and parsed as PDF&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Filename Extraction
&lt;/h3&gt;

&lt;p&gt;To enhance UX, I added a &lt;code&gt;_extractFilename()&lt;/code&gt; method that pulls names from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Content-Disposition&lt;/code&gt; headers (e.g., &lt;code&gt;filename="report.docx"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;URL segments (e.g., &lt;code&gt;https://example.com/readme.md&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Google Docs metadata (fallback if unavailable)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Testing &amp;amp; Refinement
&lt;/h3&gt;

&lt;p&gt;I tested with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.doc&lt;/code&gt;: Legacy Word file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.docx&lt;/code&gt;: Modern reports&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.md&lt;/code&gt;: GitHub README files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PDF&lt;/code&gt;: Academic papers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Google Docs&lt;/code&gt;: Shared documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edge cases included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing headers&lt;/li&gt;
&lt;li&gt;Large files (&amp;gt;10MB)&lt;/li&gt;
&lt;li&gt;Unsupported formats&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Publishing as a Package
&lt;/h3&gt;

&lt;p&gt;To make DocTextExtractor reusable, I decided to publish it on pub dev, and that is also one of the reasons I'm writing this article&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Published on &lt;a href="https://pub.dev/packages/doc_text_extractor" rel="noopener noreferrer"&gt;Pub.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MIT Licensed&lt;/li&gt;
&lt;li&gt;Included example app&lt;/li&gt;
&lt;li&gt;Wrote a detailed README with usage examples&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why It Matters to NotteChat
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DocTextExtractor&lt;/strong&gt; is the backbone of NotteChat's AI-powered chat with documents.&lt;/p&gt;

&lt;p&gt;It enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI Chat&lt;/strong&gt;: Clean text fed into AI (e.g., "Summarize this PDF")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline Use&lt;/strong&gt;: Great for areas with limited internet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart UX&lt;/strong&gt;: Real filenames and helpful error messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versatile Support&lt;/strong&gt;: For modern and legacy users&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Use DocTextExtractor in Flutter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Add the Dependency
&lt;/h3&gt;

&lt;p&gt;Add &lt;a href="https://pub.dev/packages/doc_text_extractor" rel="noopener noreferrer"&gt;doc_text_extractor&lt;/a&gt; to your app’s pubspec.yaml.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yaml&lt;br&gt;
dependencies:&lt;br&gt;
  doc_text_extractor: ^1.0.0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;code&gt;flutter pub get&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Import and Initialize
&lt;/h3&gt;

&lt;p&gt;Import the package and create a &lt;strong&gt;TextExtractor&lt;/strong&gt; instance:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextExtractor&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: Extract Text from a URL
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;extractText&lt;/strong&gt; to process URLs for .doc, .docx, .md, PDF, or Google Docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;processDocumentUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extractText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Filename: &lt;/span&gt;&lt;span class="si"&gt;$filename&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Text: &lt;/span&gt;&lt;span class="si"&gt;${text.substring(0, 100)}&lt;/span&gt;&lt;span class="s"&gt;...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Pass text to AI service (e.g., for NotteChat’s AI chat)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Error: &lt;/span&gt;&lt;span class="si"&gt;$e&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Show user-friendly error (e.g., "Please convert .doc to .docx")&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;
  
  
  Example usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;processDocumentUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'https://raw.githubusercontent.com/user/repo/main/README.md'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;processDocumentUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'https://docs.google.com/document/d/EXAMPLE_ID/edit'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Extract Text from a Local File
&lt;/h3&gt;

&lt;p&gt;For local files (e.g., user-uploaded .md or .doc), set isUrl: false:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;processLocalFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extractText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;isUrl:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Filename: &lt;/span&gt;&lt;span class="si"&gt;$filename&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Text: &lt;/span&gt;&lt;span class="si"&gt;${text.substring(0, 100)}&lt;/span&gt;&lt;span class="s"&gt;...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Use text in app logic&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Error: &lt;/span&gt;&lt;span class="si"&gt;$e&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;getTemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;processLocalFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${dir.path}&lt;/span&gt;&lt;span class="s"&gt;/sample.md'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Integrate with your preferred AI API
&lt;/h3&gt;

&lt;p&gt;You can now use the extracted text in your app with AI tools like OpenAI, Gemini, or Sonar APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_handleDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extractText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Update session title&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;sessionTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'Session &lt;/span&gt;&lt;span class="si"&gt;${DateTime.now().toIso8601String().split('T')[0]}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;$filename&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Summarize with AI (e.g., Sonar API)&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;sonarService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SonarService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sonarService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;queryDocument&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;'Summarize this document'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Display in UI&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Session: &lt;/span&gt;&lt;span class="si"&gt;$sessionTitle&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Summary: &lt;/span&gt;&lt;span class="si"&gt;$summary&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Enhance UX with Error Handling
&lt;/h3&gt;

&lt;p&gt;Add loading dialogs for large files and user-friendly errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Unsupported document type'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&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;'Unsupported format. Try converting to .docx or PDF.'&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;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;DocTextExtractor&lt;/strong&gt; started as a necessity for NotteChat but evolved into a powerful, standalone Flutter package. It’s now available for anyone building document-based apps, AI tools, or productivity platforms.&lt;/p&gt;

&lt;p&gt;Try it out: &lt;a href="https://pub.dev/packages/doc_text_extractor" rel="noopener noreferrer"&gt;https://pub.dev/packages/doc_text_extractor&lt;/a&gt;&lt;br&gt;
View the source: &lt;a href="https://github.com/Destiny-Ed/doc_text_extractor" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you found this helpful or end up using the package, feel free to drop a ⭐ on &lt;a href="https://github.com/Destiny-Ed/doc_text_extractor" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or share your feedback. I’d love to hear how you’re using it!&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;h1&gt;
  
  
  DocTextExtractor #NotteChat #Flutter #AI #DestinyEd #Dart
&lt;/h1&gt;

</description>
      <category>programming</category>
      <category>flutter</category>
      <category>dart</category>
      <category>google</category>
    </item>
    <item>
      <title>Stop wasting time on lengthy documents! I’m excited to introduce NotteChat, an AI-powered app that reads PDFs and Word docs for you. Upload any file, ask questions, and get answers in seconds. Download at https://nottechat.com</title>
      <dc:creator>Destiny Ed</dc:creator>
      <pubDate>Wed, 23 Apr 2025 14:16:15 +0000</pubDate>
      <link>https://dev.to/destinyed/stop-wasting-time-on-lengthy-documents-im-excited-to-introduce-nottechat-an-ai-powered-app-that-3lnd</link>
      <guid>https://dev.to/destinyed/stop-wasting-time-on-lengthy-documents-im-excited-to-introduce-nottechat-an-ai-powered-app-that-3lnd</guid>
      <description></description>
      <category>ai</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
      <category>news</category>
    </item>
    <item>
      <title>Flutter Animations: From Beginner Basics to Heroic Transitions</title>
      <dc:creator>Destiny Ed</dc:creator>
      <pubDate>Mon, 07 Apr 2025 18:16:48 +0000</pubDate>
      <link>https://dev.to/destinyed/flutter-animations-from-beginner-basics-to-heroic-transitions-1adf</link>
      <guid>https://dev.to/destinyed/flutter-animations-from-beginner-basics-to-heroic-transitions-1adf</guid>
      <description>&lt;p&gt;Animations can transform a good Flutter app into a great one, making your UI feel alive and intuitive. Whether you’re new to Flutter or ready to level up, this tutorial will guide you from simple animations to advanced transitions. We’ll start with beginner-friendly implicit animations, move to explicit animations for more control, and finish with the stunning Hero animation that ties screens together like magic. By the end, you’ll have the skills to animate anything in Flutter. &lt;/p&gt;

&lt;p&gt;We are going to group this into Part (From basic animations and advanced and Hero animations)&lt;/p&gt;

&lt;p&gt;For a more visual tutorial, check out my youtube channel : &lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/3W8gMH3Zoyg"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Ready to get moving? Let’s jump in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Basic Implicit Animations
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Implicit animations&lt;/em&gt; are perfect for beginners because Flutter handles the animation logic for you. You define the start and end states, and Flutter smoothly transitions between them. Let’s begin with AnimatedContainer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 1&lt;/strong&gt;: Coloring and Growing a Box with AnimatedContainer&lt;/p&gt;

&lt;p&gt;The AnimatedContainer widget animates changes to properties like size, color, or padding. Let’s make a box that grows and changes color when tapped.&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:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: AnimatedContainerDemo());
  }
}

class AnimatedContainerDemo extends StatefulWidget {
  @override
  _AnimatedContainerDemoState createState() =&amp;gt; _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State&amp;lt;AnimatedContainerDemo&amp;gt; {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AnimatedContainer Demo')),
      body: Center(
        child: GestureDetector(
          onTap: () {
            setState(() {
              _isExpanded = !_isExpanded;
            });
          },
          child: AnimatedContainer(
            duration: Duration(seconds: 1),
            width: _isExpanded ? 200 : 100,
            height: _isExpanded ? 200 : 100,
            color: _isExpanded ? Colors.blue : Colors.red,
            curve: Curves.easeInOut,
          ),
        ),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;AnimatedContainer transitions its properties over a duration (1 second here).&lt;/p&gt;

&lt;p&gt;Tapping toggles _isExpanded, changing the width, height, and color.&lt;/p&gt;

&lt;p&gt;curve: Curves.easeInOut ensures a smooth start and stop.&lt;/p&gt;

&lt;p&gt;If you run this and tap the box. It grows from 100x100 (red) to 200x200 (blue) with a silky-smooth animation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Intermediate Implicit Animations
&lt;/h2&gt;

&lt;p&gt;Now that you’ve got the basics, let’s explore more implicit animations: &lt;strong&gt;AnimatedOpacity&lt;/strong&gt; for fading effects and &lt;strong&gt;AnimatedCrossFade&lt;/strong&gt; for switching widgets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 2&lt;/strong&gt;: Fading Text with AnimatedOpacity&lt;/p&gt;

&lt;p&gt;Let’s fade a text widget in and out with a button press.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AnimatedOpacityDemo extends StatefulWidget {
  @override
  _AnimatedOpacityDemoState createState() =&amp;gt; _AnimatedOpacityDemoState();
}

class _AnimatedOpacityDemoState extends State&amp;lt;AnimatedOpacityDemo&amp;gt; {
  bool _isVisible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AnimatedOpacity Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedOpacity(
              opacity: _isVisible ? 1.0 : 0.0,
              duration: Duration(seconds: 1),
              child: Text(
                'Hello, Flutter!',
                style: TextStyle(fontSize: 30),
              ),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _isVisible = !_isVisible;
                });
              },
              child: Text('Toggle Fade'),
            ),
          ],
        ),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AnimatedOpacity&lt;/strong&gt; changes the opacity from 0.0 (invisible) to 1.0 (visible).&lt;/p&gt;

&lt;p&gt;The button toggles _isVisible, triggering the fade over 1 second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3: Switching Widgets with &lt;strong&gt;AnimatedCrossFade&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AnimatedCrossFade&lt;/strong&gt; lets you transition between two widgets with a crossfade effect. Let’s switch between two colored boxes in this example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AnimatedCrossFadeDemo extends StatefulWidget {
  @override
  _AnimatedCrossFadeDemoState createState() =&amp;gt; _AnimatedCrossFadeDemoState();
}

class _AnimatedCrossFadeDemoState extends State&amp;lt;AnimatedCrossFadeDemo&amp;gt; {
  bool _showFirst = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AnimatedCrossFade Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedCrossFade(
              firstChild: Container(
                width: 100,
                height: 100,
                color: Colors.green,
              ),
              secondChild: Container(
                width: 100,
                height: 100,
                color: Colors.purple,
              ),
              crossFadeState: _showFirst
                  ? CrossFadeState.showFirst
                  : CrossFadeState.showSecond,
              duration: Duration(seconds: 1),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _showFirst = !_showFirst;
                });
              },
              child: Text('Switch'),
            ),
          ],
        ),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AnimatedCrossFade&lt;/strong&gt; fades between firstChild (green box) and secondChild (purple box).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;crossFadeState&lt;/strong&gt; determines which child is visible, with a 1-second transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Explicit Animations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Implicit&lt;/strong&gt; animations are easy, but &lt;strong&gt;explicit&lt;/strong&gt; animations give you full control using &lt;strong&gt;AnimationController&lt;/strong&gt; and &lt;strong&gt;Tween&lt;/strong&gt;. Let’s create a rotating icon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 4:&lt;/strong&gt; Spinning Icon&lt;/p&gt;

&lt;p&gt;We’ll make a star icon spin 360 degrees repeatedly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class RotationDemo extends StatefulWidget {
  @override
  _RotationDemoState createState() =&amp;gt; _RotationDemoState();
}

class _RotationDemoState extends State&amp;lt;RotationDemo&amp;gt;
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation&amp;lt;double&amp;gt; _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    _animation = Tween&amp;lt;double&amp;gt;(begin: 0, end: 2 * 3.14159).animate(_controller)
      ..addListener(() {
        setState(() {});
      });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Rotation Demo')),
      body: Center(
        child: Transform.rotate(
          angle: _animation.value,
          child: Icon(Icons.star, size: 100),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          if (_controller.isAnimating) {
            _controller.stop();
          } else {
            _controller.repeat();
          }
        },
        child: Icon(Icons.play_arrow),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How It Works:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AnimationController&lt;/strong&gt; manages the animation’s timing (2 seconds per cycle).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tween&lt;/strong&gt; maps values from 0 to 2π (a full rotation).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vsync&lt;/strong&gt;: this syncs the animation to the screen refresh rate, requiring &lt;strong&gt;SingleTickerProviderStateMixin&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The floating action button toggles the spinning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: Advanced Custom Animation
&lt;/h2&gt;

&lt;p&gt;In this part, we are going to combine multiple animations for a more complex effect: a ball that bounces up and down while changing size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 5&lt;/strong&gt;: Bouncing Ball&lt;/p&gt;

&lt;p&gt;This example uses &lt;strong&gt;AnimatedBuilder&lt;/strong&gt; for efficiency and combines position and size animations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class BounceDemo extends StatefulWidget {
  @override
  _BounceDemoState createState() =&amp;gt; _BounceDemoState();
}

class _BounceDemoState extends State&amp;lt;BounceDemo&amp;gt;
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation&amp;lt;double&amp;gt; _height;
  late Animation&amp;lt;double&amp;gt; _size;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 1),
      vsync: this,
    )..repeat(reverse: true);

    _height = Tween&amp;lt;double&amp;gt;(begin: 0, end: 200).animate(
      CurvedAnimation(parent: _controller, curve: Curves.bounceOut),
    );
    _size = Tween&amp;lt;double&amp;gt;(begin: 50, end: 30).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bouncing Ball Demo')),
      body: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Stack(
            children: [
              Positioned(
                bottom: _height.value,
                left: MediaQuery.of(context).size.width / 2 - _size.value / 2,
                child: Container(
                  width: _size.value,
                  height: _size.value,
                  decoration: BoxDecoration(
                    color: Colors.orange,
                    shape: BoxShape.circle,
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AnimatedBuilder&lt;/strong&gt; rebuilds only the animated part, improving performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;_height&lt;/strong&gt; uses &lt;strong&gt;Curves.bounceOut&lt;/strong&gt; for a realistic bounce effect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;_size&lt;/strong&gt; shrinks the ball as it rises, adding depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;repeat(reverse: true)&lt;/strong&gt; loops the animation back and forth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5: Hero Animation
&lt;/h2&gt;

&lt;p&gt;For the final part, let’s create a Hero animation—a seamless transition between two screens, often used for images or icons in galleries or detail views.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 6&lt;/strong&gt;: Image Transition with Hero&lt;/p&gt;

&lt;p&gt;We’ll build a list screen with an image that, when tapped, transitions smoothly to a larger version on a detail screen.&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:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: HeroListScreen());
  }
}

class HeroListScreen extends StatelessWidget {
  final image = "https://picsum.photos/200/300";
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Hero Animation - List')),
      body: ListView(
        children: [
          GestureDetector(
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) =&amp;gt; HeroDetailScreen(
imageUrl : image,
heroTag : "hero_id"
)),
              );
            },
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Hero(
                tag: 'hero_id',
                child: Image.network(
                  image,
                  width: 150,
                  height: 150,
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class HeroDetailScreen extends StatelessWidget {
  final String heroTag; 
  final String imageUrl;
  HeroDetailsScreen({required this.heroTag, required this.imageUrl});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Hero Animation - Detail')),
      body: Center(
        child: Hero(
          tag: heroTag,
          child: Image.network(
            imageUrl,
            width: 300,
            height: 300,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How It Works:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Hero&lt;/strong&gt; widget wraps the image on both screens, linked by a unique tag (hero_id).&lt;/p&gt;

&lt;p&gt;Tapping the image navigates to HeroDetailScreen, and Flutter animates the image from 150x150 to 300x300, adjusting position and scale.&lt;/p&gt;

&lt;p&gt;The back button reverses the animation seamlessly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Congratulations if you go to this part!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You’ve just unlocked the power of Flutter animations! Here’s how to keep going:&lt;/p&gt;

&lt;p&gt;Experiment and try other implicit widgets like AnimatedPadding or AnimatedPositioned.&lt;/p&gt;

&lt;p&gt;Customize the &lt;strong&gt;Hero&lt;/strong&gt; animation by adding a &lt;strong&gt;flightShuttleBuilder&lt;/strong&gt; property to the &lt;strong&gt;Hero&lt;/strong&gt; widget for effects like rotation or fade during transitions.&lt;/p&gt;

&lt;p&gt;Explore advanced topics by looking into &lt;strong&gt;PageRouteBuilder&lt;/strong&gt; for custom page transitions or &lt;strong&gt;Rive&lt;/strong&gt; for pre-designed animations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There you have it!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You've successfully completed your crash course in Flutter animations! You’ve gone from basic boxes that grow and fade, to spinning icons, bouncing balls, and even the show-stopping Hero transition. &lt;/p&gt;

&lt;p&gt;These skills are your toolkit to make apps that don’t just work, but wow. &lt;/p&gt;

&lt;p&gt;Thank you for reading. Keep animating with Flutter! &lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
