Search code examples
flutterazureflaskiframeazure-blob-storage

How do I display PDF/image documents stored in Azure storage using Flask + Flutter


I am using a flask backend and flutter frontend to build a website. I am trying to make it have the functionality of uploading a document on one upload.dart page (to an azure storage), and then displaying the document on a confirmation uploaded.dart page. However, I'm just getting a blank screen when I run my code to display the document (other things appear, but the space for the image/pdf is blank).

This is my app.py flask code

from flask import Flask
from flask import send_from_directory, render_template
from flask import redirect, url_for, request, jsonify
import os 
from datetime import datetime, timedelta
from werkzeug.utils import secure_filename
from flask_cors import CORS
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
from azure.storage.blob import generate_blob_sas, BlobSasPermissions


app = Flask(__name__)
azure_blob_storage_origin = "https://aishore.blob.core.windows.net"
CORS(app, resources={r"/*": {"origins": [azure_blob_storage_origin, "*"]}}) 

connect_str = 'DefaultEndpointsProtocol=CONNECTION_STRING_ETC'
if not connect_str:
    raise ValueError("Please set the AZURE_STORAGE_CONNECTION_STRING environment variable")
container_name = "documents" # azure storage account container name

blob_service_client = BlobServiceClient.from_connection_string(conn_str=connect_str) 
# generates container if it doesn't already exist 
try:
    container_client = blob_service_client.get_container_client(container=container_name) 
    container_client.get_container_properties()
except Exception as e:
    container_client = blob_service_client.create_container(container_name) # create a container in the storage account if it does not exist


@app.route('/')
def render_page():
    return render_template('/index.html')

# uploading documents page 
@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return jsonify({
            "message": "no file available", 
            "status": "fail"
            }), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({
            "message": "no selected file", 
            "status": "fail"
            }), 400
    if file: 
        timestamp = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
        filename = secure_filename(f"{timestamp}_{file.filename}")
        try:
            blob_client = container_client.get_blob_client(filename)
            blob_client.upload_blob(file.stream, overwrite=True)
            file_url = blob_client.url

            return jsonify({
                "message": "File uploaded successfully",
                "status": "success",
                "file_url": file_url,
            }), 200

        except Exception as e: 
            return jsonify({
                "message": "Failed to upload file, Error ${e}",
                "status": "fail", 
            }), 500

# rerouting for uploaded files to either display the image or provide a download link to the pdf 
@app.route('/upload/<path:filename>', methods=['GET'])
def uploaded_file(filename):
    try:
        blob_client = container_client.get_blob_client(filename)
        sas_token = generate_blob_sas(
            account_name=blob_service_client.account_name,
            container_name=container_name,
            blob_name=blob_client.blob_name,
            account_key=blob_service_client.credential.account_key,
            permission=BlobSasPermissions(read=True),
            expiry=datetime.now(datetime.UTC)() + timedelta(hours=1)
        )
        signed_url = f"{blob_client.primary_endpoint}/{container_name}/{blob_client.blob_name}?{sas_token}"
        return jsonify({
            "file_url": signed_url,
            "status": "success"
        })

    except Exception as e:
        return jsonify({"message": f"Failed to retrieve file: {str(e)}", "status": "fail"}), 500
if __name__ == '__main__':
    app.run(debug=True)

This is my uploaded.dart flutter code:

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'dart:html' as html;
import 'package:http/http.dart' as http;

class UploadedPage extends StatefulWidget {
  final String signedUrl;

  const UploadedPage({
    super.key,
    required this.signedUrl,
  });
  @override
  State<UploadedPage> createState() => _UploadedPageState();
}

class _UploadedPageState extends State<UploadedPage> {

Widget _displayPDFInIframe(String url) {
  return SizedBox(
    height: 600,
    width: 800,
    child: HtmlElementView(
      viewType: 'iframe',
      key: UniqueKey(),
      onPlatformViewCreated: (int viewId) {
        final iframe = html.IFrameElement()
          ..src = url
          ..style.border = 'none'
          ..style.width = '100%'
          ..style.height = '100%';
        html.document.getElementById('iframe_$viewId')?.append(iframe);
      },
    ),
  );
}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                'File uploaded successfully!',
                style: TextStyle(fontSize: 24),
              ),
              const SizedBox(height: 16.0),
              SelectableText('TEMP URL ${widget.signedUrl}'),

              const SizedBox(height: 16.0),
              _displayPDFInIframe(widget.signedUrl),
            ],
          ),
        ),
      ),
    );
  }
}

Sorry for the code spam, I'm not sure what part of the code I might be having issues.

Because I'm using an SAS token (or trying to) my access level is set to private. However, I did notice that when I printed widget.signedUrl on the website, it doesn't have an SAS token, which I'm not sure is the cause of the issue. I've tried changing the code in app.py to make signedUrl have the SAS token but have been getting errors when doing that. I've also tried not using an iframe, but no matter what I try, I either get error icons or just nothing at all. I can provide whatever other code I've tried but I think using iframes is closest to what I might want.


Solution

  • Check the below Flask server code for uploading files to Azure Blob Storage and retrieving signed URLs for those files. On the client side, there's a Flutter application designed to fetch and display files (images and PDFs) from the Flask server.

    The below Flask code uploads files to Azure Blob Storage and generate SAS (Shared Access Signature) URLs for downloading those files.

    Added cors CORS(app, resources={r"/upload/*": {"origins": "*"}}) to allow cors from all urls without this I was facing cors issue in Flutter.

    from flask import Flask, render_template, jsonify, request
    .....
    
    app = Flask(__name__)
    CORS(app, resources={r"/upload/*": {"origins": "*"}})  # Specify the resource path for CORS
    .....
    ....
    

    enter image description here

    Make sure to add cors in the Storage account

    enter image description here

    The Flutter code fetches and displays an image from a URL endpoint using HTTP requests.

    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:http/http.dart' as http;
    import 'package:cached_network_image/cached_network_image.dart';
    import 'package:flutter_cached_pdfview/flutter_cached_pdfview.dart'; 
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Display image from URL',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: DisplayFileFromUrl(),
        );
      }
    }
    
    class DisplayFileFromUrl extends StatefulWidget {
      DisplayFileFromUrl({Key? key}) : super(key: key);
    
      @override
      _DisplayFileFromUrlState createState() => _DisplayFileFromUrlState();
    }
    
    class _DisplayFileFromUrlState extends State<DisplayFileFromUrl> {
      String? fileUrl; 
      bool isLoading = false;
      late String baseUrl;
      late String filename;
    
      @override
      void initState() {
        super.initState();
        baseUrl = 'http://127.0.0.1:5000/upload/';
        filename = 'sample.jpg/.pdf'; // Replace with your filename 
        fetchFile();
      }
    
      Future<void> fetchFile() async {
        setState(() {
          isLoading = true;
        });
    
        final String url = '$baseUrl$filename';
    
        try {
          final response = await http.get(Uri.parse(url));
    
          if (response.statusCode == 200) {
            final Map<String, dynamic> responseData = json.decode(response.body);
            fileUrl = responseData['file_url']; 
    
            setState(() {
              isLoading = false;
            });
          } else {
            throw Exception('Failed to load file');
          }
        } catch (error) {
          print('Error fetching file: $error');
          setState(() {
            isLoading = false;
          });
        }
      }
    
      Widget buildFileWidget() {
        if (isLoading) {
          return Center(child: CircularProgressIndicator());
        } else if (fileUrl != null) {
          if (fileUrl!.toLowerCase().endsWith('.pdf')) {
            
            return PDF().cachedFromUrl(fileUrl!);
          } else {
            
            return CachedNetworkImage(
              imageUrl: fileUrl!,
              placeholder: (context, url) => CircularProgressIndicator(),
              errorWidget: (context, url, error) => Icon(Icons.error),
              fit: BoxFit.contain, 
            );
          }
        } else {
          return Center(child: Text('File not found'));
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Display File from URL'),
          ),
          body: Center(
            child: buildFileWidget(),
          ),
        );
      }
    }
    

    Image display:

    enter image description here

    For PDF display, I am getting a black page. I followed this document and used flutter_pdfview, but encountered the error "TargetPlatform.windows is not yet supported by the pdfview_flutter plugin."

    This package documentation clearly says that flutter_pdfview is supported only for iOS and Android platforms. Consider the syncfusion_flutter_pdfviewer package instead.