使用 flutter 生成 pdf 并将其上传到 firebase 存储可以在 MacO 中工作,但不能与 WebApp 一起使用

问题描述 投票:0回答:2

我正在为我工作的公司使用 Flutter 开发报价制作应用程序。该应用程序根据用户的选择生成 PDF 文档并将其存储在 Firebase 存储中。 PDF 生成和存储在 macOS 上运行得很好,但在 Flutter Web 上运行该应用程序时遇到了问题。

问题:

这个问题特别出现在 Flutter Web 上。尝试将生成的 PDF 文件存储在 Firebase 存储中时,应用程序会抛出错误:completer.complete。错误消息没有提供有关问题根本原因的太多信息,这使得我很难查明确切的问题。

代码:

我已经在 Firebase Storage 上分享了执行 PDF 生成和存储的代码 pdf_generator.dart

import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_core/firebase_core.dart';

import 'dart:io';

// Function to get the current quotation number
Future<int> _getQuotationNumber() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int quotationNumber = prefs.getInt('quotation_number') ?? 0;
  return quotationNumber;
}

// Function to increment the quotation number
Future<void> _incrementQuotationNumber() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int quotationNumber = await _getQuotationNumber();
  quotationNumber++;
  prefs.setInt('quotation_number', quotationNumber);
}

Future<String> generateQuotationPDF(
  Map<String, String> selectedItemNamesWithSKU,
  Map<String, int> quantities,
  Map<String, double> sellingPrices,
  Map<String, String> itemSku,
  Map<String, double> itemCostPrices,
  Map<String, double> itemPrices,
  double totalCost,
  double totalSellingPrice,
  double discount,
) async {
  // Initialize Firebase
  await Firebase.initializeApp();
  final pdf = pw.Document();

  // Create the PDF file name with the quotation number
  String directoryName = 'quotation_pdfs';

  final appDocumentsDir = await getApplicationDocumentsDirectory();
  String directoryPath = '${appDocumentsDir.path}/$directoryName';
  Directory(directoryPath).create(recursive: true);

  // Increment the quotation number
  await _incrementQuotationNumber();
  int quotationNumber = await _getQuotationNumber();

  String fileName =
      '$directoryName/PC-${quotationNumber.toString().padLeft(3, '0')}.pdf';

  final pdfPath = '${appDocumentsDir.path}/$fileName';

  // Load the logo image
  final logoImage = pw.MemoryImage(
    (await rootBundle.load('assets/cazalogo.webp')).buffer.asUint8List(),
  );
  // Get the current date
  final currentDate = DateTime.now();

  // Format the date to show only the date part (YYYY-MM-DD)
  final formattedDate =
      "${currentDate.year}-${currentDate.month.toString().padLeft(2, '0')}-${currentDate.day.toString().padLeft(2, '0')}";

  // Add content to the PDF document
  pdf.addPage(
    pw.Page(
      build: (pw.Context context) {
        final List<List<String?>> tableData = [
          ['Item', 'SKU', 'Qty', 'Cost', 'Selling Price'], // Header row
          for (String type in selectedItemNamesWithSKU.keys)
            if (selectedItemNamesWithSKU[type] != null &&
                quantities[selectedItemNamesWithSKU[type]] != 0)
              [
                selectedItemNamesWithSKU[type], // Item name with SKU
                itemSku[selectedItemNamesWithSKU[type]] ?? '', // SKU
                quantities[selectedItemNamesWithSKU[type]]?.toString() ?? '',
                '${(itemCostPrices[selectedItemNamesWithSKU[type]] ?? 0.0) * (quantities[selectedItemNamesWithSKU[type]] ?? 0)} BHD',
                '${(itemPrices[selectedItemNamesWithSKU[type]] ?? 0.0) * (quantities[selectedItemNamesWithSKU[type]] ?? 0)} BHD',
              ],
        ];

        return pw.Column(
          crossAxisAlignment: pw.CrossAxisAlignment.start,
          children: [
            // Display the logo/image
            pw.Center(
              child: pw.Image(logoImage, width: 50, height: 50),
            ),
            // Display the quotation number
            pw.Text('Quotation #PC-${quotationNumber}',
                style: const pw.TextStyle(fontSize: 20)),

            pw.Divider(),
            pw.SizedBox(height: 6),
            pw.Text('Cazasouq Trading W.L.L',
                style: const pw.TextStyle(fontSize: 10)),
            pw.Text('Cazasouq Shop 983d Block 332 Road 3221 Bu ashira',
                style: const pw.TextStyle(fontSize: 10)),
            // Display the date of creation
            pw.Text('$formattedDate',
                style: const pw.TextStyle(
                    fontSize: 10)), // Adjust font size if needed

            pw.SizedBox(height: 20),
            pw.Text('Quotation Details',
                style: const pw.TextStyle(fontSize: 15)),
            pw.Divider(),
            pw.SizedBox(height: 10),
            // Display the chosen items only in the table
            // ignore: deprecated_member_use
            pw.Table.fromTextArray(
              headerStyle:
                  pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 10),
              cellStyle: const pw.TextStyle(
                  fontSize: 7), // Smaller font size for items in the table
              border: pw.TableBorder.all(),
              headerDecoration:
                  const pw.BoxDecoration(color: PdfColors.grey300),
              cellHeight: 25,
              cellAlignments: {
                0: pw.Alignment.centerLeft,
                1: pw.Alignment.center,
                2: pw.Alignment.center,
                3: pw.Alignment.center,
                4: pw.Alignment.center,
              },
              headerHeight: 20,
              headerPadding: const pw.EdgeInsets.all(5),
              cellPadding: const pw.EdgeInsets.all(5),
              data: tableData,
              // Set the widths of each column here using the 'columnWidths' property
              columnWidths: {
                0: const pw.FlexColumnWidth(5), // Item column width
                1: const pw.FlexColumnWidth(2), // SKU column width
                2: const pw.FlexColumnWidth(1), // Quantity column width
                3: const pw.FlexColumnWidth(2), // Cost (BHD) column width
                4: const pw.FlexColumnWidth(
                    2), // Selling Price (BHD) column width
              },
            ),
            pw.SizedBox(height: 10),
            pw.Row(
              mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
              children: [
                pw.Text('Total Cost: $totalCost BHD',
                    style: const pw.TextStyle(fontSize: 9)),
                pw.Text('Total Selling Price: $totalSellingPrice BHD',
                    style: const pw.TextStyle(fontSize: 9)),
              ],
            ),
            pw.SizedBox(height: 5),
            pw.Row(
              mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
              children: [
                pw.Text('Discount: $discount BHD',
                    style: const pw.TextStyle(fontSize: 9)),
                pw.Text(
                    'Selling Price After Discount: ${totalSellingPrice - discount} BHD',
                    style: const pw.TextStyle(fontSize: 9)),
              ],
            ),
            pw.SizedBox(height: 5),
            pw.Row(
              mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
              children: [
                pw.Text('Margin: ${totalSellingPrice - totalCost} BHD',
                    style: const pw.TextStyle(fontSize: 9)),
                pw.Text(
                    'Margin After Discount: ${totalSellingPrice - totalCost - discount} BHD',
                    style: const pw.TextStyle(fontSize: 9)),
              ],
            ),
            pw.SizedBox(height: 5),
            pw.Text(
                'Selling Price After Discount + VAT: ${(totalSellingPrice - discount) * 1.1} BHD',
                style: const pw.TextStyle(fontSize: 9)),
          ],
        );
      },
    ),
  );

  // Save the PDF file to a file
  final file = File(pdfPath);
  await file.writeAsBytes(await pdf.save());

  // Store the PDF file in Firestore
  final firebaseStorage = FirebaseStorage.instance;
  final reference = firebaseStorage.ref().child(fileName);
  final uploadTask = reference.putFile(file);

  // Wait for the upload to complete
  final TaskSnapshot snapshot = await uploadTask;

  // Check if the upload was successful
  if (snapshot.state == TaskState.success) {
    // Get the download URL of the uploaded PDF file
    final downloadUrl = await snapshot.ref.getDownloadURL();

    // Optionally, show a message or perform other actions after the PDF is uploaded
    print('PDF quotation uploaded to: $downloadUrl');

    // Return the download link
    return downloadUrl;
  } else {
    throw FirebaseException(
        plugin: 'firebase_storage',
        code: 'object-not-found',
        message: 'No object exists at the desired reference.');
  }
}

pcbuilder.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:google_fonts/google_fonts.dart';
import '/functions/pdf_generator.dart';
import '/functions/product_data_loader.dart';
import '/functions/product_calculations.dart';
import 'package:firebase_core/firebase_core.dart';

import '/model/pcparts.dart';

class PCBuilderExcel extends StatefulWidget {
  @override
  _PCBuilderExcelState createState() => _PCBuilderExcelState();
}

enum PdfGenerationState {
  notStarted,
  loading,
  success,
  error,
}

void main() async {
  // Pass the Firebase web configuration options
  var firebaseOptions = const FirebaseOptions(
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: "",
  );

  // Make sure to call WidgetsFlutterBinding.ensureInitialized() before Firebase.initializeApp()
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: firebaseOptions);

  runApp(PCBuilderExcel());
}

class _PCBuilderExcelState extends State<PCBuilderExcel> {
  Map<String, double> itemPrices = {};
  Map<String, String> itemSku = {};
  Map<String, String> selectedItemNames = {};
  Map<String, int> quantities = {};
  Map<String, double> sellingPrices = {};
  double discount = 0.0;
  double totalCost = 0.0;
  double totalSellingPrice = 0.0;
  List<String> allItemNames = [];
  Map<String, double> itemCostPrices = {};

  // Define a Map to hold the TextEditingController for each item
  Map<String, TextEditingController> quantityControllers = {};
  bool showExtraCards = false; // Flag to show extra cards

  void initState() {
    super.initState();
    loadProductData(
      allItemNames,
      itemCostPrices,
      itemPrices,
      itemSku,
      selectedItemNames,
      quantities,
      _updateState,
    );

    // Initialize the quantityControllers with default values set to '1'
    for (var item in allItemNames) {
      quantityControllers[item] = TextEditingController(text: '1');
    }

    // Initialize the selectedItemNames map with the null value for all items
    selectedItemNames = {};
    for (var item in allItemNames) {
      selectedItemNames[item] = ''; // Use an empty string instead of null
    }
  }

  void _updateState() {
    setState(() {});
  }

  double calculateTotalCost() {
    return ProductCalculations.calculateTotalCost(
      selectedItemNames,
      itemCostPrices,
      quantities,
    );
  }

  double calculateTotalSellingPrice() {
    return ProductCalculations.calculateTotalSellingPrice(
      selectedItemNames,
      sellingPrices,
    );
  }

  double calculateMargin() {
    return ProductCalculations.calculateMargin(
      selectedItemNames,
      sellingPrices,
      itemCostPrices,
      quantities,
    );
  }

  double calculateSellingPriceAfterDiscount() {
    return ProductCalculations.calculateSellingPriceAfterDiscount(
      selectedItemNames,
      sellingPrices,
      discount,
    );
  }

  double calculateMarginAfterDiscount() {
    return ProductCalculations.calculateMarginAfterDiscount(
      selectedItemNames,
      sellingPrices,
      itemCostPrices,
      quantities,
      discount,
    );
  }

  void updateSellingPrice(String type) {
    ProductCalculations.updateSellingPrice(
      type,
      selectedItemNames,
      itemPrices,
      sellingPrices,
      itemCostPrices,
      quantities,
      () {
        setState(() {});
      },
    );
  }

  void _onGenerateQuotationButtonPressed() async {
    // Call the function to generate the PDF quotation
    await generateQuotationPDF(
      selectedItemNames, // Pass selectedItemNames as selectedItemNamesWithSKU
      quantities,
      sellingPrices,
      itemSku,
      itemCostPrices,
      itemPrices,
      calculateTotalCost(),
      calculateTotalSellingPrice(),
      discount,
    );
  }

  // Define the getIconForType method here
  IconData getIconForType(String type) {
    switch (type) {
      case 'CPU':
        return Icons.computer;
      case 'GPU':
        return Icons.videogame_asset;
      case 'RAM':
        return Icons.memory;
      case 'Storage':
        return Icons.storage;
      case 'Motherboard':
        return Icons.device_hub;
      default:
        return Icons.category;
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PC Builder Excel',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        // Use the NotoSans font as the default font
        fontFamily: GoogleFonts.notoSans().fontFamily,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('PC Builder Excel'),
          centerTitle: true,
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
                      const SizedBox(height: 10),
                      ElevatedButton(
                        onPressed: _onGenerateQuotationButtonPressed,
                        child: const Text('Generate Quotation'),
                      ),

the error

flutter firebase dart web-applications storage
2个回答
0
投票

您应该在应用程序中调用 Firebase.initializeApp 一次。

所以你必须只在主函数中调用Firebase.initializeApp。


0
投票

当我将 storageBucket 行添加到 main 时,代码适用于我

  apiKey: "TEST",
  appId: "TEST",
  

存储桶:“测试”,

  messagingSenderId: "TEST",
  projectId: "TEST");
© www.soinside.com 2019 - 2024. All rights reserved.