provider_mvvm 0.4.1

Flutter MVVM框架介绍 #

A Flutter MVVM plugin for iOS and Android,It makes Make development easier。

代码说明: #

  • BaseRepository:负责统一封装get和post请求,供子类调用,获取服务器数据
  • BaseViewModel:负责管理widget加载状态,提供一个抽象方法创建Repository对象,通过这个对象从服务器获取数据,提供一个统一供子类调用的方法,方便统一处理显示loading和处理错误状态,提供widget状态设置和刷新widget状态的方法。
  • ViewState:widget状态枚举类
  • ProviderWidget:负责给widget创建viewmodel对象,关联viewmodel,提供一个初始化加载数据方法。
  • CommonViewStateHelper:针对widget状态为加载中,数据为空,数据加载失败做统一处理,并提供数据为空,数据加载失败的点击事件处理。

代码中用到的插件:

dio: ^3.0.8
provider: ^3.2.0
fluttertoast: ^3.0.1

使用步骤 #

1. 创建ViewModel #

class MovieListViewModel extends BaseViewModel<MovieRepository> {
  /// 获取即将上映电影
  Future<dynamic> getComingList({int start, int count}) async {
    var result = await requestData(
        mRepository.getComingList(start: start, count: count));
    if (result != null && result.data != null) {
      return result.data['subjects'];
    } else {
      return null;
    }
  }

  @override
  MovieRepository createRepository() {
    return new MovieRepository();
  }
}

2. 创建Repository #

/// 电影
class MovieRepository extends BaseRepository {
  /// 获取即将上映电影
  Future<dynamic> getComingList({int start, int count}) async {
    var result = await get(ApiService.getComingSoon(),
        params: {'start': start, 'count': count});
    return result;
  }
}

3. widget使用,使用ListView实现下拉刷新,上拉加载更多 #

3.1. 通过ProviderWidget创建ViewModel对象 #

 /// 是否加载更多
  bool _loadMore = false;
  /// 是否是刷新
  bool _refresh = false;
  int start = 0;
  int count = 20;
  List<MovieItem> movieData = [];
  ScrollController _scrollController = new ScrollController();
  @override
  Widget build(BuildContext context) {
    return ProviderWidget<MovieListViewModel, MovieRepository>(
        model: MovieListViewModel(),
        initData: (model) {
          addListener(model);
          loadData(model: model);
        },
        builder: (context, model, child) {
          if (!model.isSuccess()) {
            return CommonViewStateHelper(
                model: model,
                onEmptyPressed: () {
                  refreshData(model: model);
                },
                onErrorPressed: () {
                  refreshData(model: model);
                });
          }
          if (model.isError()) {
            Toast.show(model.viewStateError.message);
          }
          if (movieData.length == 0) {
            return ViewStateEmptyWidget(onPressed: () {
              refreshData(model: model);
            });
          }
          return Scaffold(
              appBar: AppBar(
                  title: Text(
                widget.title,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(fontSize: 16, color: AppColor.white),
              )),
              body: RefreshIndicator(
                  child: _buildHomeList(),
                  onRefresh: () {
                    return refreshData(model: model);
                  }));
        });
  }

  /// 构建首页列表
  Widget _buildHomeList() {
    return Container(
        color: AppColor.white,
        child: ListView.builder(
            shrinkWrap: true,
            itemCount: movieData.length,
            controller: _scrollController,
            itemBuilder: (context, index) {
              if (_loadMore && (index + 1) == movieData.length) {
                return Container(
                  padding: EdgeInsets.only(top: 15, bottom: 15),
                  child: Center(
                    child: CupertinoActivityIndicator(),
                  ),
                );
              }
              MovieItem movieItem = movieData[index];
              return new MovieListItemView(movieItem, 'coming_soon');
            }));
  }

3.2. 刷新方法和加载更多方法实现 #

   /// 刷新数据
  Future<void> refreshData({@required MovieListViewModel model}) async {
    start = 0;
    count = 20;
    loadData(model: model, isRefresh: true);
  }

  /// 加载更多数据
  Future<void> loadMoreData({@required MovieListViewModel model}) async {
    loadData(model: model, isLoadMore: true);
  }

  Future<dynamic> loadData(
      {MovieListViewModel model,
      bool isRefresh = false,
      bool isLoadMore = false}) async {
    model.isRefresh = isRefresh;
    var list = await model.getComingList(start: start, count: count);
    var movieList = MovieDataUtil.getMovieList(list);
    if (isRefresh) {
      this.movieData.clear();
    }
    if (movieList == null || movieList.isEmpty) {
      model.isLoadMore = false;
      _loadMore = false;
    } else {
      model.isLoadMore = true;
      _loadMore = true;
      start += count;
      this.movieData.addAll(movieList);
    }
    refreshState(model: model);
    return null;
  }

3.3. 添加列表监听 #

/// 添加监听
  void addListener(MovieListViewModel model) {
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        if (_loadMore) {
          _loadMore = false;
          model.isLoadMore = true;
          loadMoreData(model: model);
        }
      }
    });
  }

3.4. 刷新状态 #

 /// 刷新状态
  void refreshState({@required MovieListViewModel model}) {
    if (movieData.isEmpty) {
      model.setEmpty();
    } else {
      model.setSuccess();
    }
  }

0.0.1 #

  • 初始化mvvm项目

0.0.2 #

  • 修改readme文件

0.0.3 #

  • 修复bug

0.0.4 #

  • 修复版本不统一问题

0.4.0 #

  • 修复版本不统一问题

0.4.1 #

  • 移除HttpProvider和ApiManager类,改为用户自己实现,添加动态修改baseUrl拦截器

example/README.md

example #

使用步骤 #

1. 创建ViewModel #

class MovieListViewModel extends BaseViewModel<MovieRepository> {
  /// 获取即将上映电影
  Future<dynamic> getComingList({int start, int count}) async {
    var result = await requestData(
        mRepository.getComingList(start: start, count: count));
    if (result != null && result.data != null) {
      return result.data['subjects'];
    } else {
      return null;
    }
  }

  @override
  MovieRepository createRepository() {
    return new MovieRepository();
  }
}

2. 创建Repository #

/// 电影
class MovieRepository extends BaseRepository {
  /// 获取即将上映电影
  Future<dynamic> getComingList({int start, int count}) async {
    var result = await get(ApiService.getComingSoon(),
        params: {'start': start, 'count': count});
    return result;
  }
}

3. widget使用,使用ListView实现下拉刷新,上拉加载更多 #

3.1. 通过ProviderWidget创建ViewModel对象 #

 /// 是否加载更多
  bool _loadMore = false;
  /// 是否是刷新
  bool _refresh = false;
  int start = 0;
  int count = 20;
  List<MovieItem> movieData = [];
  ScrollController _scrollController = new ScrollController();
  @override
  Widget build(BuildContext context) {
    return ProviderWidget<MovieListViewModel, MovieRepository>(
        model: MovieListViewModel(),
        initData: (model) {
          addListener(model);
          loadData(model: model);
        },
        builder: (context, model, child) {
          if (!model.isSuccess()) {
            return CommonViewStateHelper(
                model: model,
                onEmptyPressed: () {
                  refreshData(model: model);
                },
                onErrorPressed: () {
                  refreshData(model: model);
                });
          }
          if (model.isError()) {
            Toast.show(model.viewStateError.message);
          }
          if (movieData.length == 0) {
            return ViewStateEmptyWidget(onPressed: () {
              refreshData(model: model);
            });
          }
          return Scaffold(
              appBar: AppBar(
                  title: Text(
                widget.title,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(fontSize: 16, color: AppColor.white),
              )),
              body: RefreshIndicator(
                  child: _buildHomeList(),
                  onRefresh: () {
                    return refreshData(model: model);
                  }));
        });
  }

  /// 构建首页列表
  Widget _buildHomeList() {
    return Container(
        color: AppColor.white,
        child: ListView.builder(
            shrinkWrap: true,
            itemCount: movieData.length,
            controller: _scrollController,
            itemBuilder: (context, index) {
              if (_loadMore && (index + 1) == movieData.length) {
                return Container(
                  padding: EdgeInsets.only(top: 15, bottom: 15),
                  child: Center(
                    child: CupertinoActivityIndicator(),
                  ),
                );
              }
              MovieItem movieItem = movieData[index];
              return new MovieListItemView(movieItem, 'coming_soon');
            }));
  }

3.2. 刷新方法和加载更多方法实现 #

   /// 刷新数据
  Future<void> refreshData({@required MovieListViewModel model}) async {
    start = 0;
    count = 20;
    loadData(model: model, isRefresh: true);
  }

  /// 加载更多数据
  Future<void> loadMoreData({@required MovieListViewModel model}) async {
    loadData(model: model, isLoadMore: true);
  }

  Future<dynamic> loadData(
      {MovieListViewModel model,
      bool isRefresh = false,
      bool isLoadMore = false}) async {
    model.isRefresh = isRefresh;
    var list = await model.getComingList(start: start, count: count);
    var movieList = MovieDataUtil.getMovieList(list);
    if (isRefresh) {
      this.movieData.clear();
    }
    if (movieList == null || movieList.isEmpty) {
      model.isLoadMore = false;
      _loadMore = false;
    } else {
      model.isLoadMore = true;
      _loadMore = true;
      start += count;
      this.movieData.addAll(movieList);
    }
    refreshState(model: model);
    return null;
  }

3.3. 添加列表监听 #

/// 添加监听
  void addListener(MovieListViewModel model) {
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        if (_loadMore) {
          _loadMore = false;
          model.isLoadMore = true;
          loadMoreData(model: model);
        }
      }
    });
  }

3.4. 刷新状态 #

 /// 刷新状态
  void refreshState({@required MovieListViewModel model}) {
    if (movieData.isEmpty) {
      model.setEmpty();
    } else {
      model.setSuccess();
    }
  }

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  provider_mvvm: ^0.4.1

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:provider_mvvm/main.dart';
import 'package:provider_mvvm/base/base_repository.dart';
import 'package:provider_mvvm/base/base_result.dart';
import 'package:provider_mvvm/base/base_view_model.dart';
import 'package:provider_mvvm/base/provider_widget.dart';
import 'package:provider_mvvm/base/view_state.dart';
import 'package:provider_mvvm/base/view_state_widget.dart';
import 'package:provider_mvvm/common/app_color.dart';
import 'package:provider_mvvm/interceptor/common_dynamic_url_interceptor.dart';
import 'package:provider_mvvm/interceptor/common_header_interceptor.dart';
import 'package:provider_mvvm/interceptor/common_log_interceptor.dart';
import 'package:provider_mvvm/interceptor/common_param_interceptor.dart';
import 'package:provider_mvvm/utils/screen_util.dart';
import 'package:provider_mvvm/utils/toast_util.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
3
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
50
Learn more about scoring.

We analyzed this package on Feb 12, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.5
  • Flutter: 1.12.13+hotfix.7

Health suggestions

Fix lib/common/app_color.dart. (-0.50 points)

Analysis of lib/common/app_color.dart reported 1 hint:

line 20 col 16: Name non-constant identifiers using lowerCamelCase.

Format lib/base/base_repository.dart.

Run flutter format to format lib/base/base_repository.dart.

Format lib/base/base_result.dart.

Run flutter format to format lib/base/base_result.dart.

Format lib/interceptor/common_log_interceptor.dart.

Run flutter format to format lib/interceptor/common_log_interceptor.dart.

Format lib/utils/toast_util.dart.

Run flutter format to format lib/utils/toast_util.dart.

Maintenance issues and suggestions

Support latest dependencies. (-10 points)

The version constraint in pubspec.yaml does not support the latest published versions for 1 dependency (provider).

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.6.0 <3.0.0
dio ^3.0.8 3.0.8
flutter 0.0.0
fluttertoast ^3.0.1 3.1.3
http 0.12.0+4 0.12.0+4
provider ^3.2.0 3.2.0 4.0.4
Transitive dependencies
async 2.4.0
charcode 1.1.3
collection 1.14.11 1.14.12
http_parser 3.1.3
meta 1.1.8
path 1.6.4
pedantic 1.9.0
sky_engine 0.0.99
source_span 1.6.0
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test