每次仿制一份代码

看着重复的代码发愣

是时分拿起重构的武器了

前言

编写了一个PagingListWidget组件,提取了分页列表通用逻辑,其他事务相关的选用虚办法,由子类来完结。

通用逻辑包括:(1)下拉刷新。(2)上拉加载。(3)空数据展现。

其他事务逻辑:(1)导航栏信息。(2)获取列表信息。(3)列表项的视图。

运用办法,直接承继PagingListWidget并完结虚办法。完好代码和样例,请参看分页列表;

一、 起因

作为事务开发工程师,每次新需求来,看到都是列表展现,毫无技能难度。直接ctrl + c 然后ctrl + v,一顿接口修改,开发完结。

一直觉得这已经挺良心了。可忽然腻了。

想着是不是可以再懒一点,想多点自由时刻来研究怎么能够更懒。

二、 提取

暴力开启,直接复制了一个列表页面的代码,然后将事务相关代码删去,仅仅留下通用的逻辑代码。如下:

/// 分页列表的页面
/// navigationTitle回来标题,页面展现一个title,一个列表。
/// navigationTitle回来null。页面仅仅为一个列表。此时可以重载build添加其他widgets。
​
abstract class PagingListWidget extends StatefulWidget {
 const PagingListWidget({Key? key}) : super(key: key);
​
 @override
 PagingListWidgetState createState();
}
​
abstract class PagingListWidgetState<T extends PagingListWidget, S> extends State<T> {
 // 完结类 获取数据时运用
 final int pageSize = 20;
 int page = 1;
 final List<S> dataList = [];
 bool showLoadingMore = false;
 int total = 0;
 bool hasInitialed = false;
​
 bool _disposed = false;
 final ScrollController _scrollController = ScrollController();
 final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey();
​
 @override
 void initState() {
  super.initState();
​
  _scrollController.addListener(() {
   if (showLoadingMore) {
    return;
    }
   if (_scrollController.position.pixels > (_scrollController.position.maxScrollExtent - 20) && total > dataList.length) {
    setState(() {
     showLoadingMore = true;
     });
    _loadMore();
    }
   });
​
  Future.delayed(const Duration(seconds: 0), () {
   _onRefresh();
   });
  }
​
 @override
 void dispose() {
  _disposed = true;
    _scrollController.dispose();
​
  super.dispose();
  }
​
 @override
 Widget build(BuildContext context) {
  var title = navigationTitle();
  if (null == title) {
   return _contentWidget();
   }
​
  return Scaffold(
   appBar: AppBar(
    title: Text(title),
    ),
   body: _contentWidget(),
   );
  }
​
// Widget __START__
​
 Widget _contentWidget() {
  return SafeArea(
   child: !hasInitialed
     ? const MyCircularProgress()
      : RefreshIndicator(
       key: _refreshKey,
       onRefresh: _onRefresh,
       child: dataList.isEmpty
         ? const NoData()
          : ListView.builder(
           itemBuilder: (context, index) {
            if (index == dataList.length) {
             return showLoadingMore
               ? Container(
                 margin: const EdgeInsets.all(10),
                 child: const Align(
                  child: CircularProgressIndicator(
                   color: MyColors.mainColor,
                   ),
                  ),
                 )
                : total <= dataList.length
                 ? Container(
                   margin: const EdgeInsets.all(10),
                   alignment: Alignment.center,
                   child: const Text("没有更多数据"),
                   )
                  : const SizedBox(
                   height: 44,
                   );
             }
            return listItem(index);
            },
           physics: const AlwaysScrollableScrollPhysics(),
           itemCount: dataList.length + 1,
           controller: _scrollController,
           ),
       ),
   );
  }
​
// Widget __END__// Network __START__
 Future<void> _onRefresh() async {
  dataList.clear();
​
  page = 1;
​
  if (_disposed) {
   return;
   }
​
  fetchData();
  }
​
 Future<void> _loadMore() async {
  page++;
​
  if (_disposed) {
   return;
   }
​
  fetchData();
  }
​
// Network __END__// 虚办法 __START__// 标题
 // 回来为null 则说明此页面不要包括导航栏,仅仅回来列表页面。
 // 回来标题时,那么直接展现完好的页面
 String? navigationTitle();
​
 // 获取列表的数据。
 Future<void> fetchData();
​
 // 列表视图
 Widget listItem(int index);
​
// 虚办法 __END__
}

三、 思路

1. 为了运用尽量便利,直接承继StatefulWidgetConsumerStatefulWidget
2. 将一切的值都放在父类,子类经过super来访问。
3. 列表信息的data model,选用泛型传入。abstract class PagingListWidgetState<T extends PagingListWidget, S>避免类型强转。
4. 提高复用性。依据navigationTitle来判断是否是带导航栏。
5. 提供自定义的部分,选用虚办法,让子类来完结。

四、样例

将原来的页面选用PagingListWidget重写下,代码量减少了一半。而且今后编写分页列表的新页面,开发时刻减少90%吧。爽了。

举一个比如,如下:

class MyList extends PagingListWidget {
 const MyList({super.key});
​
 @override
 PagingListWidgetState<PagingListWidget, dynamic> createState() => _MyListState();
}
​
class _MyListState extends PagingListWidgetState<MyList, String> {
 var random = Random();
​
 @override
 Future<void> fetchData() async {
  return Future.delayed(const Duration(seconds: 3), () {
   super.total = 100000;
   super.dataList.addAll(List.generate(super.pageSize, (index) => "测试下 ${random.nextInt(10000000)}"));
​
   setState(() {
    super.showLoadingMore = false;
    super.hasInitialed = true;
    });
   });
  }
​
 @override
 Widget listItem(int index) {
  return Container(
   color: MyColors.randomColor(),
   height: 44,
   padding: const EdgeInsets.only(top: 8, left: 8),
   child: Text(super.dataList[index], style: TextStyle(color: MyColors.randomColor(), fontSize: 16),),
   );
  }
​
 @override
 String? navigationTitle() {
  return "测试分页列表";
  }
}
1. 创建子类。
2. 完结虚办法。
3. 完结

五、结束

思路忽然打开了。重复的代码今后就用这个方法重构下,代码量和开发量下降好多。

抛砖引玉。请赐教更好的思路。