Posts tagged with “flutter”

Solving Flutter Web Image Rendering Issues: A Cross-Platform Approach

If you've ever tried to display images in a Flutter app that needs to work seamlessly across web and mobile platforms, you've probably run into some frustrating limitations. Recently, I tackled this exact problem when our markdown image renderer started choking on web deployments due to CORS restrictions and lack of proper zoom functionality.

The Problem

Our original implementation was painfully simple - just Image.network(src) wrapped in a GestureDetector. This worked fine on mobile, or on web with --web-renderer html. However, the HTML web renderer is obsolete and will be removed shortly. We needed a more reliable solution.

The Solution: Platform-Specific Implementations

The key insight was to leverage Flutter's conditional imports to create platform-specific implementations while maintaining a clean, unified API.

Setting Up Conditional Imports

// Conditional imports for web
import 'web_image_stub.dart'
    if (dart.library.html) 'web_image_impl.dart';

This pattern lets you have different implementations for web vs mobile while keeping your main code clean. The stub file handles non-web platforms, while the implementation file contains the web-specific logic.

Web Implementation: HTML Elements to the Rescue

For web, I ditched Flutter's built-in image widgets entirely and went straight to HTML elements using HtmlElementView. This bypasses CORS issues since the browser handles the image loading directly.

final imgElement = html.ImageElement()
  ..src = src
  ..style.width = '100%'
  ..style.objectFit = 'contain'
  ..style.cursor = 'pointer';

The magic happens when you register this as a platform view. Flutter treats it like any other widget, but under the hood, it's pure HTML - which means it plays nicely with browser security policies.

Adding Zoom Functionality

The fullscreen implementation includes both mouse wheel and touch gesture zoom:

// Mouse wheel zoom
imgElement.onWheel.listen((event) {
  event.preventDefault();
  scale += event.deltaY > 0 ? -0.1 : 0.1;
  scale = scale.clamp(0.5, 3.0);
  imgElement.style.transform = 'scale($scale)';
});

For touch devices, I implemented pinch-to-zoom by tracking multiple touch points and calculating the distance between them. It's more complex than the mouse wheel version, but it gives web users the same intuitive zoom experience they expect.

Mobile Implementation: Keep It Simple

For mobile platforms, I stuck with the tried-and-true approach but improved the UX:

Dialog.fullscreen(
  backgroundColor: Colors.black,
  child: Stack(
    children: [
      Center(
        child: PhotoView(
          imageProvider: NetworkImage(url),
          minScale: PhotoViewComputedScale.contained,
          maxScale: PhotoViewComputedScale.covered * 4,
        ),
      ),
      // Close button positioned in top-right
    ],
  ),
)

The key improvements were switching to Dialog.fullscreen instead of a regular dialog and adding a proper close button with consistent styling.

Key Takeaways

  1. Conditional imports are your friend - They let you maintain clean separation between platform-specific code without cluttering your main logic.

  2. HTML elements can solve web-specific problems - When Flutter widgets don't cut it on web, dropping down to HTML often provides better browser compatibility.

  3. Consistent UX matters - Users expect zoom functionality on images, especially in fullscreen mode. Don't skimp on these details.

  4. Don't fight the platform - Web and mobile have different strengths. Embrace them instead of trying to force a one-size-fits-all solution.

The Result

After implementing these changes, our image handling works consistently across platforms. Web users get smooth zoom functionality without CORS headaches, mobile users get the native experience they expect, and the codebase remains maintainable with clear separation of concerns.

Sometimes the best solution isn't the most elegant one - it's the one that actually works for your users across all the platforms they're using.

Update your flutter app icons with a custom one

1. Prepare Your Icon Image

  • Create a square PNG image (preferably 1024x1024 pixels) for best quality

2. Use the flutter_launcher_icons Package

Add the package to your pubspec.yaml:

flutter pub add flutter_launcher_icons

3. Configure the Icons

Add this configuration to your pubspec.yaml file:

flutter_launcher_icons:
  android: true
  ios: true
  remove_alpha_ios: true
  image_path: "assets/icon/app_icon.png"  # Path to your icon image
  adaptive_icon_background: "#FFFFFF"  # For Android adaptive icons (optional)

4. Run the Package

Run this command in your terminal:

flutter pub get
dart run flutter_launcher_icons

done!

Why We Shouldn't Unconditionally Load Data in `didChangeDependencies

Flutter’s didChangeDependencies lifecycle method is a common source of redundant network requests and data reloads. Based on real-world code examples from a notes app, let’s explore why this happens and how to fix it.


When Does didChangeDependencies Trigger?

didChangeDependencies in Flutter triggers in these scenarios:

  • Immediately after initState()
  • When an InheritedWidget ancestor changes
  • When the widget's dependencies change

In the provided HomePage code:

@override  
void didChangeDependencies() {  
  super.didChangeDependencies();  
  navigateToPage(currentPageNumber); // ❌ Unconditional call  
}  

This causes duplicate API calls every time the event being triggerd.


The Fix: Initialize-Once Flag

class HomePageState extends State<HomePage> {  
  ...
  bool _isInitialized = false; // Add flag  

  @override  
  void didChangeDependencies() {  
    super.didChangeDependencies();  
    if (!_isInitialized) {  
      _isInitialized = true;  
      navigateToPage(currentPageNumber); // ✅ Only first load  
    }  
  }  
  ...
}  

Why This Works

  1. First Load: Initializes data once
  2. Subsequent Route Changes: Skips reload unless explicitly refreshed
  3. Memory Efficiency: Prevents duplicate API calls (evident from the NotesService cache-less implementation)

Key Takeaways

  1. didChangeDependencies isn’t just for initialization
  2. Always guard data-loading logic with flags

[Solution] The argument type 'Consumer' can't be assigned to the parameter type 'PreferredSizeWidget?'.

The error occurs because AppBar expects a widget that implements the PreferredSizeWidget interface, but Consumer<NoteModel> does not directly implement this interface. To solve this, you need to return an AppBar from within the Consumer builder method.

Here’s how you can do it:

Scaffold(
  appBar: PreferredSize(
    preferredSize: Size.fromHeight(kToolbarHeight),
    child: Consumer<NoteModel>(
      builder: (context, noteModel, child) {
        return AppBar(
          title: Text(
            'Your Title',
            style: TextStyle(
              color: noteModel.isPrivate ? Colors.red : Colors.green,
            ),
          ),
        );
      },
    ),
  ),
  body: // Your other widgets,
);

In this approach, I wrapped the Consumer<NoteModel> inside a PreferredSize widget to ensure it adheres to the PreferredSizeWidget interface required by appBar.

This should resolve the error while allowing you to update only the AppBar based on changes in your NoteModel.

Glory to ChatGPT!

Build command for deploying your flutter web app to cloudflare pages

set -x && if cd flutter; then git pull && cd .. ; else git clone https://github.com/flutter/flutter.git; (cd flutter && git fetch --tags && git checkout 3.22.3); fi && ls && flutter/bin/flutter doctor && flutter/bin/flutter clean && flutter/bin/flutter config --enable-web && cp .env.production .env && sed -i "s/VERSION_PLACEHOLDER/`git rev-parse --short HEAD`/" .env && flutter/bin/flutter build web --web-renderer html --base-href="/" --release