Search code examples
fluttervue.jswebviewassetswebview-flutter

Intercepting Remote Image Requests in Flutter WebView and Fallback to Local Assets


Hello Flutter Community,

I am currently working on a project that involves a Flutter application which essentially serves as a shell for a Vue3 web app, embedded via the webview_flutter package. However, I am encountering an issue related to the loading times of remote images.

The Vue3 web app includes numerous heavy images which, when loaded remotely, can take a significant amount of time and consequently impact the user experience. To alleviate this, I am exploring ways to bundle these images with the Flutter application and fall back to these local assets when possible.

My idea is to have Flutter intercept each remote image request. If the required image is available in the local assets, Flutter should override the request and return the local image instead. If not, the remote request proceeds as usual.

Conceptually, the flow would be as follows:

  1. A request for a remote image is made.
  2. Flutter intercepts the request.
  3. Flutter checks if the requested image exists in the local assets.
  4. If it does, Flutter returns the local image. If not, Flutter allows the remote request to proceed.

Unfortunately, I am finding it difficult to find relevant resources for this particular problem. Most Flutter plugins or solutions seem to address the context of local HTML with local assets, whereas my scenario is about remote HTML with local assets.

Is such a logic feasible in Flutter? If so, could you guide me in the right direction in terms of implementation, or suggest any relevant plugins or tools that might be of use?

Additionally, if there are alternative approaches to optimize the loading of these heavy images in my context, I would be happy to hear your suggestions!

Thank you for your time and help.

Best Regards


Solution

  • I wanted to share the solution I found for my issue.

    Initially, someone (who? it disappeared) suggested hijacking the Flutter cache, but after some research, I found that this approach would be quite complex, given that it's tied to some low-level features of Flutter and Android.

    Instead, I used the flutter_inappwebview plugin. I use it to create a local server, allowing access to resources via a local URL.

    On the VueJS side, I created a component that I named LazyImage in my Vue3 application, which behaves as follows: it tries to access the asset locally and if successful, it displays it. If not, it falls back to the remote URL.

    Here is a proof of concept of what it looks like:

    main.dart

    import 'package:flutter/material.dart';
    import 'package:flutter_inappwebview/flutter_inappwebview.dart';
    
    InAppLocalhostServer localhostServer = new InAppLocalhostServer();
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await localhostServer.start();
    
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => new _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      InAppWebViewController? webViewController;
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: SafeArea(
              child: InAppWebView(
                initialOptions: InAppWebViewGroupOptions(
                  crossPlatform: InAppWebViewOptions(),
                ),
                onWebViewCreated: (InAppWebViewController controller) {
                  webViewController = controller;
                  //This is where my vue webapp is located, it could be remote
                  controller.loadUrl(urlRequest: URLRequest(url: Uri.parse("http://192.168.1.16:8080")));
                },
              ),
            ),
          ),
        );
      }
    }
    

    And on the Vue.js side:

    LazyImage.vue

    <template>
        <div>
          <img v-if="imageExists" :src="imageUrl" @error="loadFallbackImage" />
        </div>
    </template>
      
    <script>
    export default {
      data() {
        return {
          imageUrl: 'http://localhost:8080/help/image.jpg',
          imageExists: true,
        };
      },
      methods: {
        loadFallbackImage() {
          this.imageUrl = require('@/assets/fallback_image.jpg');
        },
      },
    };
    </script>
    

    So, in conclusion, the flutter_inappwebview plugin coupled with a custom Vue component helped me solve the issue of leveraging local assets over remote assets, if available, without the need to tamper with lower-level features.

    Thank you all for your time and help. This question can now be considered closed.

    Best Regards