Skip to content

[flutter_markdown] Invalid image URI crashes the app (instead of just showing an error at the image area) #158428

@kaboc

Description

@kaboc

Steps to reproduce

  1. Run the code sample below.
  2. Remove https from the URI in the text field.
  3. Press on the Preview button to go to the preview page.
  4. See the error is shown in the body of the page, i.e. in the Markdown widget.
  5. Go back to the main page with the back button.
  6. See the error is shown in the whole page, so the app is no longer working.

Expected results

The image is not rendered, and an error widget is used instead like below.

Actual results

The Markdown widget itself causes an error, and then, to make matters worse, the whole app stops working.

This means that when my app has a Markdown editing feature, just a user's mistype in an image URI in the Markdown image notation will get the user into trouble. Nothing can be done any more and the editor content is lost (unless the app has state restoration logic). As long as it can happen, I cannot use the package for production.

FYI, the error differs depending on the typo:

  • URI starting with ://

    Invalid empty scheme (at character 1)

  • URI starting with ttps://

    Unsupported operation: Cannot extract a file path from a ttps URI

Code sample

Code sample
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';

void main() {
  runApp(
    MaterialApp(home: const EditorPage()),
  );
}

class EditorPage extends StatefulWidget {
  const EditorPage();

  @override
  State<EditorPage> createState() => _EditorPageState();
}

class _EditorPageState extends State<EditorPage> {
  late final TextEditingController _controller = TextEditingController()
    ..text = '# Image\n\n'
        '![](https://avatars.githubusercontent.com/u/20254485?v=4)';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            TextFormField(
              controller: _controller,
              maxLines: 10,
            ),
            ElevatedButton(
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (_) => PreviewPage(markdown: _controller.text),
                ),
              ),
              child: const Text('Preview'),
            ),
          ],
        ),
      ),
    );
  }
}

class PreviewPage extends StatelessWidget {
  const PreviewPage({required this.markdown});

  final String markdown;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Preview'),
      ),
      body: SafeArea(
        child: Markdown(data: markdown),
      ),
    );
  }
}

Screenshots or Video

Screenshots / Video demonstration

Logs

Logs
======== Exception caught by widgets library =======================================================
The following FormatException was thrown building MediaQuery(MediaQueryData(size: Size(694.0, 596.0), devicePixelRatio: 1.0, textScaler: no scaling, platformBrightness: Brightness.light, padding: EdgeInsets.zero, viewPadding: EdgeInsets.zero, viewInsets: EdgeInsets.zero, systemGestureInsets: EdgeInsets.zero, alwaysUse24HourFormat: true, accessibleNavigation: false, highContrast: false, onOffSwitchLabels: false, disableAnimations: false, invertColors: false, boldText: false, navigationMode: traditional, gestureSettings: DeviceGestureSettings(touchSlop: null), displayFeatures: [], supportsShowingSystemContextMenu: false)):
Invalid empty scheme (at character 1)
://avatars.githubusercontent.com/u/20254485?v=4
^

The relevant error-causing widget was: 
  SafeArea SafeArea:file:///D:/xxxx/lib/main.dart:64:13
When the exception was thrown, this was the stack: 
#0      _Uri._fail (dart:core/uri.dart:1737:5)
#1      new _Uri.notSimple (dart:core/uri.dart:1601:9)
#2      Uri.parse (dart:core/uri.dart:1138:17)
#3      MarkdownBuilder._buildImage (package:flutter_markdown/src/builder.dart:604:25)
#4      MarkdownBuilder.visitElementAfter (package:flutter_markdown/src/builder.dart:511:11)
#5      Element.accept (package:markdown/src/ast.dart:53:15)
#6      Element.accept (package:markdown/src/ast.dart:50:17)
#7      MarkdownBuilder.build (package:flutter_markdown/src/builder.dart:202:12)
#8      _MarkdownWidgetState._parseMarkdown (package:flutter_markdown/src/widget.dart:405:25)
#9      _MarkdownWidgetState.didChangeDependencies (package:flutter_markdown/src/widget.dart:348:5)
#10     StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5766:11)
#11     ComponentElement.mount (package:flutter/src/widgets/framework.dart:5593:5)
...     Normal element mounting (40 frames)
#51     Element.inflateWidget (package:flutter/src/widgets/framework.dart:4468:16)
#52     MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7035:36)
#53     MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7047:32)
...     Normal element mounting (338 frames)
#391    Element.inflateWidget (package:flutter/src/widgets/framework.dart:4468:16)
#392    MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7035:36)
#393    Element.updateChild (package:flutter/src/widgets/framework.dart:3963:18)
#394    Element.updateChildren (package:flutter/src/widgets/framework.dart:4150:32)
#395    MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:7060:17)
#396    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#397    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#398    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5780:11)
#399    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#400    StatefulElement.update (package:flutter/src/widgets/framework.dart:5803:5)
#401    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#402    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#403    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#404    ProxyElement.update (package:flutter/src/widgets/framework.dart:5946:5)
#405    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#406    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#407    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#408    ProxyElement.update (package:flutter/src/widgets/framework.dart:5946:5)
#409    _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:105:11)
#410    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#411    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#412    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5780:11)
#413    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#414    StatefulElement.update (package:flutter/src/widgets/framework.dart:5803:5)
#415    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#416    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#417    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#418    ProxyElement.update (package:flutter/src/widgets/framework.dart:5946:5)
#419    _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:105:11)
#420    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#421    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#422    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5780:11)
#423    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#424    StatefulElement.update (package:flutter/src/widgets/framework.dart:5803:5)
#425    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#426    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#427    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5780:11)
#428    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#429    StatefulElement.update (package:flutter/src/widgets/framework.dart:5803:5)
#430    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#431    SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6907:14)
#432    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#433    SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6907:14)
#434    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#435    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#436    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#437    ProxyElement.update (package:flutter/src/widgets/framework.dart:5946:5)
#438    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#439    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#440    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#441    ProxyElement.update (package:flutter/src/widgets/framework.dart:5946:5)
#442    Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#443    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#444    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5780:11)
#445    Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#446    BuildScope._tryRebuild (package:flutter/src/widgets/framework.dart:2693:15)
#447    BuildScope._flushDirtyElements (package:flutter/src/widgets/framework.dart:2752:11)
#448    BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3048:18)
#449    WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1162:21)
#450    RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:468:5)
#451    SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1397:15)
#452    SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1318:9)
#453    SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1176:5)
#454    _invoke (dart:ui/hooks.dart:312:13)
#455    PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:5)
#456    _drawFrame (dart:ui/hooks.dart:283:31)
====================================================================================================

Flutter Doctor output

Doctor output
[√] Flutter (Channel stable, 3.24.4, on Microsoft Windows [Version 10.0.22631.4317], locale ja-JP)
    • Flutter version 3.24.4 on channel stable at D:\xxxx\flutter\sdk
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 603104015d (2 weeks ago), 2024-10-24 08:01:25 -0700
    • Engine revision db49896cf2
    • Dart version 3.5.4
    • DevTools version 2.37.3

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
    • Android SDK at D:\xxxx\android\sdk
    • Platform android-35, build-tools 35.0.0
    • ANDROID_HOME = D:\xxxx\android\sdk
    • Java binary at: D:\xxxx\java\jdks\jbr-17.0.11\bin\java
    • Java version OpenJDK Runtime Environment JBR-17.0.11+1-1312.2-nomod (build 17.0.11+1-b1312.2)
    • All Android licenses accepted.

[X] Chrome - develop for the web (Cannot find Chrome executable at .\Google\Chrome\Application\chrome.exe)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.11.5)
    • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
    • Visual Studio Community 2022 version 17.11.35327.3
    • Windows 10 SDK version 10.0.22621.0

[!] Android Studio (not installed)
    • Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/to/windows-android-setup for detailed instructions).

[√] IntelliJ IDEA Ultimate Edition (version 2024.2)
    • IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.5
    • Flutter plugin version 82.1.3
    • Dart plugin version 242.22855.32

[√] VS Code (version 1.94.2)
    • VS Code at C:\Users\xxxx\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.98.0

[√] Connected device (2 available)
    • Windows (desktop) • windows • windows-x64    • Microsoft Windows [Version 10.0.22631.4317]
    • Edge (web)        • edge    • web-javascript • Microsoft Edge 130.0.2849.56

[√] Network resources
    • All expected network resources are available.

! Doctor found issues in 2 categories.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: imagesLoading, displaying, rendering imagesc: crashStack traces logged to the consolefound in release: 3.24Found to occur in 3.24found in release: 3.27Found to occur in 3.27has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: flutter_markdownflutter/packages flutter_markdownpackageflutter/packages repository. See also p: labels.r: fixedIssue is closed as already fixed in a newer versionteam-ecosystemOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions