skip to Main Content

I am developing a quotation maker app using Flutter for the company I work with. The app generates a PDF document based on the user’s selections and stores it in Firebase Storage. The PDF generation and storage work perfectly fine on macOS, but I encounter an issue when running the app on Flutter Web.

Problem:

The issue arises specifically on Flutter Web. When attempting to store the generated PDF file in Firebase Storage, the app throws an error: completer.complete. The error message doesn’t provide much information about the root cause of the problem, making it challenging for me to pinpoint the exact issue.

Code:

I have shared the code that performs the PDF generation and storage in Firebase Storage on
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

2

Answers


  1. Chosen as BEST ANSWER

    the code works with me when i added storageBucket line to the main

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

    storageBucket: "TEST",

      messagingSenderId: "TEST",
      projectId: "TEST");
    

  2. you should call Firebase.initializeApp once in app.

    so you have to call Firebase.initializeApp in main function only.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search