返回

Flutter常见错误:method isn't defined 解决方法

Android

Flutter: "method isn't defined" 和其他常见错误解决方法

最近有朋友问我一个 Flutter 代码的问题, 看起来挺典型的,这里拿出来分享一下,顺便把解决思路和方法都写清楚。

一、 问题

这段代码想实现一个简单的 PDF 和 EPUB 阅读器,用了 flutter_pdfviewepub_kitty_lib 这两个库。但是,运行的时候会报一堆错误,不知道怎么回事。原代码如下:

import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart'; // PDF görüntüleme için
import 'package:epub_kitty_lib/epub_kitty_lib.dart'; // EPUB okumak için

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PDF & EPUB Reader',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  String? selectedFileType; // Seçilen dosya türünü saklamak için değişken

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PDF & EPUB Reader'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // PDF veya EPUB dosyasını seçmek için buton
            ElevatedButton(
              onPressed: () {
                // PDF dosyasını seçme işlemi
                setState(() {
                  selectedFileType = 'pdf';
                });
              },
              child: Text('PDF Dosyası Aç'),
            ),
            ElevatedButton(
              onPressed: () {
                // EPUB dosyasını seçme işlemi
                setState(() {
                  selectedFileType = 'epub';
                });
              },
              child: Text('EPUB Dosyası Aç'),
            ),
            // Seçilen dosyaya göre içerik göstermek
            selectedFileType == 'pdf'
                ? Expanded(child: PdfViewer()) // PDF görüntüleme widget'ı
                : selectedFileType == 'epub'
                    ? Expanded(child: EpubViewer()) // EPUB görüntüleme widget'ı
                    : Container(),
          ],
        ),
      ),
    );
  }
}

class PdfViewer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: PDFView(
        filePath: 'assets/sample.pdf', // Burada PDF dosyanın yolunu giriyoruz
      ),
    );
  }
}

class EpubViewer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: FutureBuilder<EpubKitty>(
        future: EpubKitty.readEpub('assets/sample.epub'), // Burada EPUB dosyasının yolunu giriyoruz
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          } else if (snapshot.hasError) {
            return Text('Bir hata oluştu: ${snapshot.error}');
          } else if (snapshot.hasData) {
            final book = snapshot.data;
            return EpubKittyReader(
              book: book!,
            );
          } else {
            return Text('EPUB dosyası bulunamadı');
          }
        },
      ),
    );
  }
}

遇到的错误信息:

  1. The method 'readEpub' isn't defined for the type 'EpubKitty'.
  2. The method 'EpubKittyReader' isn't defined for the type 'EpubViewer'.
  3. Constructors for public widgets should have a named 'key' parameter.
  4. Constructors for public widgets should have a named 'key' parameter.
  5. Invalid use of a private type in a public API.
  6. Constructors for public widgets should have a named 'key' parameter.
  7. Constructors for public widgets should have a named 'key' parameter.

二、 问题原因分析

咱们一条一条来看这些错误:

  1. 'readEpub' isn't defined: EpubKitty 类里没有 readEpub 这个方法。 可能是方法名写错了, 或者库的版本不对, 根本没有提供这个方法。
  2. 'EpubKittyReader' isn't defined: EpubViewer 类里面没有叫 EpubKittyReader 的东西。可能是根本就没定义这个 Widget, 或者是名字写错了。
  3. Constructors for public widgets... key parameter: Widget 的构造函数最好加一个 key 参数, 尤其是在列表或者复杂布局里, 可以帮助 Flutter 更高效地管理 Widget。
  4. (同3)
  5. Invalid use of a private type...: 在公开的 API 里用了一个私有的类型。
  6. (同3)
  7. (同3)

三、 解决方案

下面给出具体的解决方案。

3.1 解决 "The method 'readEpub' isn't defined"

先去 pub.dev 搜一下 epub_kitty_lib 这个库,看看它的文档。 找到该库之后,发现压根没有名为 epub_kitty_lib的库, 最接近的是epub_kitty。八成是这里出错了!

pubspec.yaml 文件里:

dependencies:
  epub_kitty_lib: ^版本号  # 找到这一行, 然后改成下面这样

改成:

dependencies:
  epub_kitty: ^1.0.2 #使用您需要的版本号

pubspec.yaml的修改后, 在项目根目录的命令行运行:

flutter pub get

让 Flutter 重新获取依赖。

接着,修改代码里的引用:

import 'package:epub_kitty/epub_kitty.dart'; // 原来是 epub_kitty_lib

根据文档, 正确的读取 EPUB 文件的方法是 EpubKitty.read()

// ... 其他代码 ...
  future: EpubKitty.read('assets/sample.epub'),  //原来是 EpubKitty.readEpub(...)
// ... 其他代码 ...

3.2 解决 "The method 'EpubKittyReader' isn't defined"

epub_kitty 库的文档里说明了, 它提供了一个 EpubView Widget 用于展示 EPUB 内容。所以,我们应该用 EpubView,而不是 EpubKittyReader

修改 EpubViewer 类:

class EpubViewer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: FutureBuilder<Uint8List>( // 修改这里的泛型
        future: EpubKitty.read('assets/sample.epub'),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          } else if (snapshot.hasError) {
            return Text('Bir hata oluştu: ${snapshot.error}');
          } else if (snapshot.hasData) {
            final data = snapshot.data; //直接获得了Uint8List
             return EpubView(
              controller: EpubController(
                document: EpubDocument.openData(data!), // 从Uint8List打开
              ),
             );
          } else {
            return Text('EPUB dosyası bulunamadı');
          }
        },
      ),
    );
  }
}

解释一下:

  • EpubKitty.read() 返回的是一个 Future<Uint8List>,所以 FutureBuilder 的泛型要改成 Uint8List
  • 使用EpubController去控制EpubView.
  • 使用EpubDocument.openData(),传入 Uint8List 数据来创建 EpubDocument

3.3 解决 "Constructors for public widgets... key parameter"

MyApp, HomeScreen, PdfViewer, EpubViewer 这几个类的构造函数加上 key 参数:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key); // 加上 key

  @override
  Widget build(BuildContext context) {
    // ... 其他代码 ...
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key); // 加上 key

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

// ... 其他类的构造函数也一样 ...

class PdfViewer extends StatelessWidget {
  const PdfViewer({Key? key}) : super(key: key);

    @override
  Widget build(BuildContext context) {
   // ... 其他代码 ...
  }
}

class EpubViewer extends StatelessWidget{
    const EpubViewer({Key? key}) : super(key: key);
     @override
  Widget build(BuildContext context) {
   // ... 其他代码 ...
  }
}

3.4 解决"Invalid use of a private type..."

_HomeScreenState被使用了. 这就是引起第五个错误的原因。 因为类名前面的下划线在Dart语言中意味着该类为私有类,你无法从别的库中对其进行访问。 删除类名之前的下划线_即可.

class HomeScreenState extends State<HomeScreen> { // 删除下划线
  String? selectedFileType;

  // ... rest of your code
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  HomeScreenState createState() => HomeScreenState(); // 改成公开的
}

3.5 进阶:错误处理和用户体验

现在代码基本能跑了,但是还可以改进一下:

  • 更友好的错误提示: 如果 PDF 或 EPUB 文件加载失败,应该给用户更明确的提示,而不是只显示 "Bir hata oluştu"。可以根据具体的错误类型,显示不同的提示信息。
  • 文件选择: 现在代码里写死了文件路径 (assets/sample.pdf, assets/sample.epub)。 可以用 file_picker 这样的库,让用户自己选择文件。
  • 加载指示器: 文件比较大的时候,加载需要时间。可以加一个更漂亮的加载指示器,比如一个转圈的进度条,或者一个带动画的图标。
  • 处理空数据: 如果snapshot.data是空的, 也就是读取epub失败, 需要给出提示而不是直接崩溃。

3.6 文件路径问题 和 Assets 配置

别忘了, 你需要在 pubspec.yaml 里声明 assets 文件夹:

flutter:
  assets:
    - assets/

而且,确保 sample.pdfsample.epub 这两个文件真的放在了 assets 文件夹里! (相对于pubspec.yaml的路径)。

四, 修改后完整可运行代码.

import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:epub_kitty/epub_kitty.dart';
import 'dart:typed_data';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PDF & EPUB Reader',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  HomeScreenState createState() => HomeScreenState();
}

class HomeScreenState extends State<HomeScreen> {
  String? selectedFileType;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PDF & EPUB Reader'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () {
                setState(() {
                  selectedFileType = 'pdf';
                });
              },
              child: Text('PDF Dosyası Aç'),
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  selectedFileType = 'epub';
                });
              },
              child: Text('EPUB Dosyası Aç'),
            ),
            selectedFileType == 'pdf'
                ? Expanded(child: PdfViewer())
                : selectedFileType == 'epub'
                ? Expanded(child: EpubViewer())
                : Container(),
          ],
        ),
      ),
    );
  }
}

class PdfViewer extends StatelessWidget {
  const PdfViewer({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: PDFView(
        filePath: 'assets/sample.pdf',
      ),
    );
  }
}

class EpubViewer extends StatelessWidget {
  const EpubViewer({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: FutureBuilder<Uint8List>(
        future: EpubKitty.read('assets/sample.epub'),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          } else if (snapshot.hasError) {
            return Text('Hata: ${snapshot.error}');
          }
          else if (snapshot.hasData){
            final data = snapshot.data;
            return EpubView(
                  controller: EpubController(
                    document: EpubDocument.openData(data!),
                  ),
            );
          }
           else {
            return Text('EPUB dosyası bulunamadı veya okunamadı.');
          }
        },
      ),
    );
  }
}

这下就清楚多了吧? 记住,遇到问题多查文档,多看报错信息,一步一步来,总能解决的!