You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中Dart回调传入子类参数引发TypeError问题求助

问题原因分析

出现Type '(Job) => ListTile' is not a subtype of type '(Model) => Widget'错误,核心有两个问题:

  • _ListPageBuilderState未跟随父类使用泛型<T extends Model>,导致widget类型被推断为原始ListPageBuilder而非带泛型的ListPageBuilder<T>,此时listItemBuilder被视为Widget Function(Model),和传入的Widget Function(Job)类型不兼容。
  • items列表声明为动态类型var items = [],未约束为List<T>,仓库返回的数据无法匹配泛型类型,传给listItemBuilder时触发类型错误。
解决方案

按以下步骤修改代码:

1. 给State类添加泛型支持

修改_ListPageBuilderState的定义,让它继承自带泛型的State<ListPageBuilder<T>>:

class _ListPageBuilderState<T extends Model> extends State<ListPageBuilder<T>> {
  // 其余代码后续调整
}

2. 约束items列表的类型

把items的声明从动态类型改为List<T>:

List<T> items = [];

3. 修正fetchItems中的类型转换

在fetchItems方法中,将仓库返回的结果转换为List<T>(如果repository.list本身支持泛型可直接使用,否则用cast<T>()强制转换,前提是确定返回实例为T类型):

void fetchItems() async {
  if (nextPage == null) return;
  try {
    var response = await widget.repository.list(getFilters());
    var newItems = response.results.cast<T>();
    nextPage = response.next != null ? getPage(response.next!) : null;

    _isLoading = false;
    setState(() {
      items.addAll(newItems);
    });
  } on HttpException catch (e) {
    print(e);
  }
}

4. 修正StatefulWidget的createState方法

更新ListPageBuilder的createState方法,返回带泛型的State实例:

@override
State<ListPageBuilder<T>> createState() => _ListPageBuilderState<T>();
修改后的完整ListPageBuilder代码
class ListPageBuilder<T extends Model> extends StatefulWidget {
  final bool canView;
  final bool canAdd;
  final String modelVerboseName;
  final Repository repository;
  final void Function()? onItemTap;
  final Widget Function(T) listItemBuilder;

  const ListPageBuilder({
    super.key,
    required this.modelVerboseName,
    required this.canView,
    required this.canAdd,
    required this.repository,
    required this.listItemBuilder,
    this.onItemTap,
  });

  @override
  State<ListPageBuilder<T>> createState() => _ListPageBuilderState<T>();
}

class _ListPageBuilderState<T extends Model> extends State<ListPageBuilder<T>> {
  @override
  void initState() {
    super.initState();
    fetchItems();
  }

  List<T> items = [];
  int? nextPage = 1;
  bool _isLoading = true;
  final _scrollController = ScrollController();
  final _searchController = TextEditingController();

  String? getSearchTerm() {
    return _searchController.text.length >= 3 ? _searchController.text : null;
  }

  Map<String, dynamic> getFilters() {
    var filters = <String, dynamic>{"page": nextPage};
    var searchTerm = getSearchTerm();
    if (searchTerm != null) {
      filters["search"] = searchTerm;
    }
    return filters;
  }

  void fetchItems() async {
    if (nextPage == null) return;
    try {
      var response = await widget.repository.list(getFilters());
      var newItems = response.results.cast<T>();
      nextPage = response.next != null ? getPage(response.next!) : null;

      _isLoading = false;
      setState(() {
        items.addAll(newItems);
      });
    } on HttpException catch (e) {
      print(e);
    }
  }

  @override
  Widget build(BuildContext context) {
    _scrollController.addListener(() {
      var nextPageTriggerLimit =
          0.75 * _scrollController.position.maxScrollExtent;

      if (_scrollController.position.pixels > nextPageTriggerLimit &&
          !_isLoading) {
        _isLoading = true;
        fetchItems();
      }
    });

    return Scaffold(
      bottomNavigationBar: const AppNavigationBar(),
      floatingActionButton: widget.canAdd
          ? FloatingActionButton(
              onPressed: () {
                Navigator.pushNamed(context, "/customers/create");
              },
              child: const Icon(Icons.add),
            )
          : null,
      body: SafeArea(
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(8),
            child: widget.canView
                ? Column(
                    children: [
                      TextFormField(
                        decoration: InputDecoration(
                          hintText: "Rechercher un ${widget.modelVerboseName}",
                          suffixIcon: const Icon(Icons.search),
                        ),
                        controller: _searchController,
                        onChanged: (value) {
                          if (value.isEmpty || value.length >= 3) {
                            setState(() {
                              items = [];
                              nextPage = 1;
                              fetchItems();
                            });
                          }
                        },
                      ),
                      (items.isEmpty && _isLoading)
                          ? const SizedBox(
                              width: 50,
                              height: 50,
                              child: CircularProgressIndicator(),
                            )
                          : Expanded(
                              child: ListView.separated(
                                controller: _scrollController,
                                itemCount: items.length,
                                itemBuilder: (context, index) {
                                  var item = items[index];
                                  return widget.listItemBuilder(item);
                                },
                                separatorBuilder: (context, index) =>
                                    const Divider(),
                              ),
                            )
                    ],
                  )
                : const Center(
                    child: Text(
                      "Vous n'avez pas la permission de consulter cette page",
                    ),
                  ),
          ),
        ),
      ),
    );
  }
}
说明
  • 泛型State类确保widget类型为ListPageBuilder<T>,此时listItemBuilder会被正确识别为Widget Function(T),和传入的(Job) => ListTile完全匹配。
  • 约束items为List<T>后,传给listItemBuilder的每个item都是明确的T类型(此处为Job),不会再触发类型转换错误。

内容的提问来源于stack exchange,提问作者Parfait_D

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:17:05