Flutter 资产数据库错误:getter 'length' 在 null 上被调用。接收者:空尝试调用:长度

时间:2021-03-31 19:51:19

标签: android flutter dart

我使用 sqflite 将现有的 database.db 文件添加到我的项目中。没有遇到错误,一切正常,但是... Flutter 调试控制台说:

Restarted application in 772ms.
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building FutureBuilder<List<Countries>>(dirty, state: _FutureBuilderState<List<Countries>>#d0317):

The getter 'length' was called on null.
Receiver: null
Tried calling: length

The relevant error-causing widget was
FutureBuilder<List<Countries>>
When the exception was thrown, this was the stack
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1      _HomeScreen.buildBody.<anonymous closure>
#2      _FutureBuilderState.build
#3      StatefulElement.build
#4      ComponentElement.performRebuild
...
════════════════════════════════════════════════════════════════════════════════
I/flutter (14052): Opening existing database

这是我的模型 Country.dart :

class Countries {
  int countryId;
  String countryName;
  String countryImageURL;
  
  //Constructor
  Countries({this.countryId, this.countryName, this.countryImageURL});

  // Extract a Product Object from a Map Oject
  Countries.fromMap(Map<String, dynamic> map) {
    countryId = map['country_id'];
    countryName = map['country_name'];
    countryImageURL = map['image'];
  }

  Map<String, dynamic> toMap() {
    var map = <String, dynamic>{
      'country_name': countryName,
      'image': countryImageURL
    };
    return map;
  }
}

这是我的 database_helper.dart 文件:

import 'dart:async';
import 'dart:io';
import 'package:city_travel_guide/model/Country.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'dart:typed_data';
import 'package:flutter/services.dart';

class DbHelper {
  static Database _db;

  Future<Database> get db async {
    if (_db != null) {
      return _db;
    } else {
      _db = await initDb();
      return _db;
    }
  }

  initDb() async {
    var dbFolder = await getDatabasesPath();
    String path = join(dbFolder, 'app.db');

    var exists = await databaseExists(path);

    if (!exists) {
      // Should happen only the first time you launch your application
      print("Creating new copy from asset");

      // Make sure the parent directory exists
      try {
        await Directory(dirname(path)).create(recursive: true);
      } catch (_) {}

      // Copy from asset
      ByteData data = await rootBundle.load(join("assets", "example.db"));
      List<int> bytes =
          data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);

      // Write and flush the bytes written
      await File(path).writeAsBytes(bytes, flush: true);
    } else {
      print("Opening existing database");
    }
    // open the database
    return await openDatabase(path);
  }

  Future<List<Countries>> getCountries() async {
    var dbClient = await db;
    var result = await dbClient.query('Country', orderBy: 'countryId');
    return result.map((data) => Countries.fromMap(data)).toList();
  }

这是我的 main.dart 文件:

import 'package:city_travel_guide/data/database_helper.dart';
import 'package:city_travel_guide/model/Country.dart';
import 'package:flutter/material.dart';
import 'widgets/maindrawer.dart';
import 'pages/search.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'City Travel Guide',
        theme: ThemeData.dark(),
        debugShowCheckedModeBanner: false,
        home: MyHome());
  }
}

class MyHome extends StatefulWidget {
  @override
  _HomeScreen createState() => _HomeScreen();
}

class _HomeScreen extends State<MyHome> {
  List<Countries> countries;
  final dbHelper = DbHelper();

  @override
  void initState() {
    dbHelper.initDb();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(
            'City Travel Guide',
            style: Theme.of(context).primaryTextTheme.headline6,
          ),
          actions: <Widget>[
            IconButton(
                icon: const Icon(Icons.search),
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(builder: (context) => SearchScreen()),
                  );
                }),
            IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
          ],
        ),
        drawer: Drawer(child: MainDrawer()),
        body: buildBody(),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {},
        ));
  }

  buildBody() {
    return FutureBuilder<List<Countries>>(
        future: dbHelper.getCountries(),
        builder: (context, snapshot) {
          return ListView.builder(
            itemCount: snapshot.data.length,
            itemBuilder: (context, index) {
              return ListTile(title: Text(snapshot.data[index].countryName));
            },
          );
        });
  }
}

如何在我的资产数据库中列出项目并在应用程序中查看?

2 个答案:

答案 0 :(得分:0)

FutureBuilder 是一个异步请求。在构建列表之前,请始终检查快照是否包含数据。 做:

buildBody() {
    return FutureBuilder<List<Countries>>(
        future: dbHelper.getCountries(),
        builder: (context, snapshot) {
        if (snapshot.hasData && snapshot.data.length > 0) // This ensures that you have at least one or more countries available.
          return ListView.builder(
            itemCount: snapshot.data.length,
            itemBuilder: (context, index) {
              return ListTile(title: Text(snapshot.data[index].countryName));
            },
          );
       else if (snapshot.hasData && snapshot.data.length == 0) 
          return Center(child:Text("There are no countries available"));
        return Center(
                      child: CircularProgressIndicator(
                    valueColor: AlwaysStoppedAnimation<Color>(
                        Theme.of(context).primaryColor),
                  )); // This would display a loading animation before your data is ready
        });
  }

答案 1 :(得分:0)

在使用snapshot.data.lenght之前,您必须检查是否有来自未来的数据很容易,因为如果snapshot.data为null(操作尚未完成),则lenght调用null所以你必须这样做

正确的代码

  buildBody() {
    return FutureBuilder<List<Countries>>(
        future: dbHelper.getCountries(),
        builder: (context, snapshot) {
         if(snapshot.hasdata&&snapshot.data.runtimetype==List){
          return ListView.builder(
            itemCount: snapshot.data.length,
            itemBuilder: (context, index) {
              return ListTile(title: Text(snapshot.data[index].countryName));
            },
          );
         }else{
               return Proggresindicator()//or any loading widgets
             }
        });
  }
}

并且您可以添加对未来发生的任何执行的检查