简体   繁体   中英

How can I convert a Dart model class to a list without a builder?

In a Flutter app I have an rxDart bloc which takes a JSON string from an API server and converts is to a list of a class (Sale).

In addition to displaying the data on a ListView widget, I want to print the data using the package pdf: 1.3.23 .

The pdf package has a builder but won't take a ListView so I need to convert my list of Sales to List> so I can pass it to Table.fromTextArray.

Or, more generally, how do I create a pdf from a parsed API response? Or, how can I create a pdf from data? Printing the data from a flutter screen has to be the second most common requirement after displaying the data but I can find no clues in srearches as to how to go about that.

I get this message when I try to pass my SalesResponse object to Table.fromTextArray.

The argument type 'SalesResponse' can't be assigned to the parameter type List< List< String>>.

Here is my code so far:

Sale model:

class Sale {
  int _id;
  int _billId;
  String _item;
  int _qty;
  double _price;

  Sale(sale) {
    _id = sale['id'];
    _billId = sale['bill_id'];
    _item = sale['item'];
    _qty = sale['qty'];
    _price = (sale['price'] as num).toDouble();
  }

  int get id => _id;
  int get billId => _billId;
  String get item => _item;
  int get qty => _qty;
  double get price => _price;
}

Here is my sales response model

import 'package:exactpos_mobile/model/sale.dart';

class SalesResponse {
  List<Sale> _sales = [];
  String error;

  SalesResponse.fromJson(Map<String, dynamic> parsedJson) {
    print(parsedJson);
    List<Sale> temp = [];
    if (parsedJson['sales'] != null) {
      for (int i = 0; i < parsedJson['sales'].length; i++) {
        Sale _sale = Sale(parsedJson['sales'][i]);
        temp.add(_sale);
      }
      _sales = temp;    
    }
  }

  SalesResponse.withError(String errorValue)
      : _sales = List(),
        error = errorValue;

  List<Sale> get sales => _sales;
}

Sales repository

class SaleRepository{
  ApiProvider _apiProvider = ApiProvider();

  Future<SalesResponse> getSales(int billId){
    return _apiProvider.getSales(billId);
  }
}

Sales Bloc

import 'package:rxdart/rxdart.dart';

class SalesBloc {
  final SaleRepository _repository = SaleRepository();
  final PublishSubject<SalesResponse> _subject = PublishSubject<SalesResponse>();
  getSales(int billId) async {
    SalesResponse response = await _repository.getSales(billId); 
    _subject.sink.add(response);
  }
  void dispose() async{
    await _subject.drain();
    _subject.close();
  }
  PublishSubject<SalesResponse> get subject => _subject;
}
final salesBloc = SalesBloc();

Here is my widget screen

SalesResponse salesList;

class SalesScreen extends StatefulWidget {

  static const routeName = '/sales';

  final Bill bill;
  final Tbl table;

  SalesScreen({    
    Key key,
    @required this.table,
    @required this.bill
   }) : super(key: key);

  @override
  State<StatefulWidget> createState() => SalesScreenState();      
}

class SalesScreenState extends State<SalesScreen> {

  @override
  void initState() {
    super.initState();

    salesBloc.getSales(widget.bill.id);

    salesList = salesBloc.getSales(widget.bill.id); 

      print('hello');
      print(salesList.sales);
      print('hello');
  }

  @override
  void dispose() {
    salesBloc.dispose();
    super.dispose();
  }
    @override
    Widget build(BuildContext context) {
    return Scaffold(
              drawer: AppDrawer(),
              appBar: AppBar(
                        backgroundColor: Colors.blue,

                        automaticallyImplyLeading: false,
                        centerTitle : true,
                        title: Text(widget.bill.billNumber.toString(), 
                                    style: TextStyle(color: Colors.white)
                                    ),
                        leading: IconButton(icon:Icon(Icons.arrow_back),
                                  onPressed:() =>
                        Navigator.of(context).pushAndRemoveUntil(
                          MaterialPageRoute(
                             builder: (context) =>
                               BillsScreen(table: widget.table)), (Route<dynamic> route) => false)
                        )
              ),
              body: SafeArea(
                child: Container(
                       child: 
                          StreamBuilder<SalesResponse>(
                              stream: salesBloc.subject.stream,          
                              builder: (context, AsyncSnapshot<SalesResponse> snapshot) {
                                     if (snapshot.hasData) {
                                        if (snapshot.data.error != null && snapshot.data.error.length > 0) {
                                           return _buildErrorWidget(snapshot.data.error);
                                        }
                                        return _buildSalesListWidget(snapshot.data);
                                     } 
                              },
                          )
              ),
        ),

              floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
                 floatingActionButton: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Row(
                              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                              children: <Widget>[

                                 FloatingActionButton(
                                    heroTag: 1,
                                    backgroundColor: Colors.yellow,
                                    onPressed: () {
                                        Printing.layoutPdf(onLayout:(format)=> 
                                           buildReceipt(widget.table, 
                                                        widget.bill, 
                                                        salesList));
                                    },
                                    child: Icon(FontAwesomeIcons.print),
                                 ),
                              ],
                    ),
                )

    );

  }

  Widget _buildSalesListWidget(SalesResponse data) {

          return ListView.builder(            
                     itemCount: data.sales.length,
                     itemBuilder: (context, index) {

                         return ListTile(                                             
                                  title: Text(                                            
                                              data.sales[index].item,                                          
                                              style: TextStyle(
                                                         fontWeight: FontWeight.w500,        
                                                         fontSize: 20,
                                              ),
                                  ),
                                  subtitle: Text('qty' + ' ' + data.sales[index].qty.toString() + ', ' + 'price' + ' ' + data.sales[index].price.toStringAsFixed(2)),
                                  leading: Icon(
                                                FontAwesomeIcons.beer,
                                                color: Colors.blue[500],
                                  ),           
                                  onTap: () {},
                         );
                     },
        );
  }
 }

Finally, my pdf code

Future<List<int>> buildReceipt(Tbl table, Bill bill,  SalesResponse salesList)  async {

  const PdfPageFormat format = PdfPageFormat(160, 900);

  final Document pdf = Document();

    pdf.addPage(MultiPage(

      pageFormat: format, //  PdfPageFormat.a4.copyWith(marginBottom: 1.5 * PdfPageFormat.cm),

      crossAxisAlignment: CrossAxisAlignment.start,

      header: (Context context) {

        if (context.pageNumber == 1) {
          return null;
        }

        return Container(

            alignment: Alignment.centerRight,

            margin: const EdgeInsets.only(top: 3.0 * PdfPageFormat.mm),

            padding: const EdgeInsets.only(bottom: 3.0 * PdfPageFormat.mm),

            decoration: const BoxDecoration(
                border:
                    BoxBorder(bottom: true, width: 0.5, color: PdfColors.grey)),

            child: Text('Exact POS',
                style: Theme.of(context)
                    .defaultTextStyle
                    .copyWith(color: PdfColors.grey)));
      },
      footer: (Context context) {
        return Container(
            alignment: Alignment.centerRight,
            margin: const EdgeInsets.only(top: 1.0 * PdfPageFormat.cm),
            child: Text('Page ${context.pageNumber} of ${context.pagesCount}',
                style: Theme.of(context)
                    .defaultTextStyle
                    .copyWith(color: PdfColors.grey)));
      },
      build: (Context context) => <Widget>[
            Header(
                level: 0,
                child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Text('Exact POS', textScaleFactor: 2),
                      PdfLogo()
                    ]
                )
            ),

            Padding(padding: const EdgeInsets.all(4)),

            Paragraph(text: table.number),

            Table.fromTextArray(context: context, data: salesList  ),
      ]
    )
  );
  return pdf.save();
}

The problem is that Table.fromTextArray expects a List<List<String>> as data and you are passing a SalesResponse .

Here is a quick example of how you could do it:

Table.fromTextArray(context: context, data: <List<String>>[
  <String>['Id', 'Bill Id', 'Item', 'Qty', 'Price'],
  ...salesList.sales.map((s) =>
  ['${s.id}', '${s.billId}', '${s.item}', '${s.qty}', '${s.price}']),
])

Or create a getter in Sale to get the list, like this:

List<String> get stringList => ['$id', '$billId', '$item', '$qty', '$price'];

Then use it like this:

Table.fromTextArray(context: context, data: <List<String>>[
  <String>['Id', 'Bill Id', 'Item', 'Qty', 'Price'],
  ...salesList.sales.map((sale) => sale.stringList),
])

Or directly create a getter in SalesResponse to get the list of list, like this:

List<List<String>> get salesStringList =>
    <List<String>>[
      <String>['Id', 'Bill Id', 'Item', 'Qty', 'Price'],
      ...sales.map((sale) => sale.stringList),
    ];

Then use it like this:

Table.fromTextArray(context: context, data: salesList.salesStringList)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM