ditclear vor 7 Jahren
Ursprung
Commit
5cb78cbecc

+ 1 - 0
ios/Flutter/Debug.xcconfig

@@ -1 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
 #include "Generated.xcconfig"

+ 1 - 0
ios/Flutter/Release.xcconfig

@@ -1 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
 #include "Generated.xcconfig"

+ 50 - 4
lib/di/app_module.dart

@@ -1,10 +1,15 @@
-import 'package:mvvm_flutter/model/remote.dart';
+import 'package:dartin/dartin.dart';
+import 'package:dio/dio.dart';
+import 'package:mvvm_flutter/helper/constants.dart';
+import 'package:mvvm_flutter/helper/shared_preferences.dart';
 import 'package:mvvm_flutter/model/repository.dart';
 import 'package:mvvm_flutter/viewmodel/home_provide.dart';
-import 'package:dartin/dartin.dart';
 
 const testScope = DartInScope('test');
 
+/// ViewModel 模块
+///
+/// 定义ViewModel的构造方式
 final viewModelModule = Module([
   factory<HomeProvide>(({params}) => HomeProvide(params.get(0), get<GithubRepo>())),
 ])
@@ -13,12 +18,53 @@ final viewModelModule = Module([
 //  factory<HomeProvide>(({params}) => HomeProvide(params.get(0), get<GithubRepo>())),
   ]);
 
+/// Repository 模块
+///
+/// 定义Repository 的构造方式
 final repoModule = Module([
-  lazy<GithubRepo>(({params}) => GithubRepo(get<GithubService>())),
+  lazy<GithubRepo>(({params}) => GithubRepo(get<GithubService>(), get<SpUtil>())),
 ]);
 
+/// Remote 模块
+///
+/// 定义各网络接口服务的构造方式
 final remoteModule = Module([
   single<GithubService>(GithubService()),
 ]);
 
-final appModule = [viewModelModule, repoModule, remoteModule];
+/// Local 模块
+///
+/// 定义数据库层及SharedPreference/KV等等本地存储的构造方式
+final localModule = Module([
+  single<SpUtil>(spUtil),
+]);
+
+final appModule = [viewModelModule, repoModule, remoteModule, localModule];
+
+/// AuthInterceptor
+///
+/// 添加header认证
+class AuthInterceptor extends Interceptor {
+  @override
+  onRequest(RequestOptions options) {
+    final token = spUtil.getString(KEY_TOKEN);
+    options.headers.update("Authorization", (_) => token, ifAbsent: () => token);
+    return super.onRequest(options);
+  }
+}
+
+final dio = Dio()
+  ..options = BaseOptions(baseUrl: 'https://api.github.com/', connectTimeout: 30, receiveTimeout: 30)
+  ..interceptors.add(AuthInterceptor())
+  ..interceptors.add(LogInterceptor(responseBody: true, requestBody: true));
+
+SpUtil spUtil;
+
+/// init
+///
+/// 初始化 [spUtil] 并启动[DartIn]
+init() async {
+  spUtil = await SpUtil.getInstance();
+  // DartIn start
+  startDartIn(appModule);
+}

+ 0 - 17
lib/di/modules.dart

@@ -1,17 +0,0 @@
-import 'package:dio/dio.dart';
-
-String token = "";
-
-final Dio dio = Dio()
-  ..options = BaseOptions(baseUrl: 'https://api.github.com/', connectTimeout: 30, receiveTimeout: 30)
-  ..interceptors.add(AuthInterceptor())
-  ..interceptors.add(LogInterceptor(responseBody: true, requestBody: true));
-
-
-class AuthInterceptor extends Interceptor {
-  @override
-  onRequest(RequestOptions options) {
-    options.headers.update("Authorization", (_) => token, ifAbsent: () => token);
-    return super.onRequest(options);
-  }
-}

+ 2 - 0
lib/helper/constants.dart

@@ -0,0 +1,2 @@
+
+const KEY_TOKEN="token";

+ 1 - 0
lib/helper/dialog.dart

@@ -71,6 +71,7 @@ Future _showLoadingDialog(BuildContext c, LoadingDialog loading,
         barrierDismissible: cancelable,
         builder: (BuildContext c) => loading);
 
+/// 加载框
 class LoadingDialog extends CupertinoAlertDialog {
   BuildContext parentContext;
   BuildContext currentContext;

+ 1 - 1
lib/helper/netutils.dart → lib/helper/net_utils.dart

@@ -1,7 +1,7 @@
 
 import 'dart:async';
 
-import 'package:mvvm_flutter/di/modules.dart';
+import 'package:mvvm_flutter/di/app_module.dart';
 import 'package:rxdart/rxdart.dart';
 
 Future _get(String url, {Map<String, dynamic> params}) async {

+ 117 - 0
lib/helper/shared_preferences.dart

@@ -0,0 +1,117 @@
+
+import 'dart:async';
+import 'package:shared_preferences/shared_preferences.dart';
+
+/// 用来做shared_preferences的存储
+class SpUtil {
+  static SpUtil _instance;
+  static Future<SpUtil> get instance async {
+    return await getInstance();
+  }
+
+  static SharedPreferences _spf;
+
+
+  SpUtil._();
+
+  Future _init() async {
+    _spf = await SharedPreferences.getInstance();
+  }
+
+  static Future<SpUtil> getInstance() async  {
+    if (_instance == null) {
+      _instance = new SpUtil._();
+      await _instance._init();
+
+    }
+    return _instance;
+  }
+
+  static bool _beforCheck() {
+    if (_spf == null) {
+      return true;
+    }
+    return false;
+  }
+  // 判断是否存在数据
+  bool hasKey(String key) {
+    Set keys = getKeys();
+    return keys.contains(key);
+  }
+
+  Set<String> getKeys() {
+    if (_beforCheck()) return null;
+    return _spf.getKeys();
+  }
+
+  get(String key) {
+    if (_beforCheck()) return null;
+    return _spf.get(key);
+  }
+
+  getString(String key) {
+    if (_beforCheck()) return null;
+    return _spf.getString(key);
+  }
+
+  Future<bool> putString(String key, String value) {
+    if (_beforCheck()) return null;
+    return _spf.setString(key, value);
+  }
+
+  bool getBool(String key) {
+    if (_beforCheck()) return null;
+    return _spf.getBool(key);
+  }
+
+  Future<bool> putBool(String key, bool value) {
+    if (_beforCheck()) return null;
+    return _spf.setBool(key, value);
+  }
+
+  int getInt(String key) {
+    if (_beforCheck()) return null;
+    return _spf.getInt(key);
+  }
+
+  Future<bool> putInt(String key, int value) {
+    if (_beforCheck()) return null;
+    return _spf.setInt(key, value);
+  }
+
+  double getDouble(String key) {
+    if (_beforCheck()) return null;
+    return _spf.getDouble(key);
+  }
+
+  Future<bool> putDouble(String key, double value) {
+    if (_beforCheck()) return null;
+    return _spf.setDouble(key, value);
+  }
+
+  List<String> getStringList(String key) {
+    return _spf.getStringList(key);
+  }
+
+  Future<bool> putStringList(String key, List<String> value) {
+    if (_beforCheck()) return null;
+    return _spf.setStringList(key, value);
+  }
+
+  dynamic getDynamic(String key) {
+    if (_beforCheck()) return null;
+    return _spf.get(key);
+  }
+
+
+
+  Future<bool> remove(String key) {
+    if (_beforCheck()) return null;
+    return _spf.remove(key);
+  }
+
+  Future<bool> clear() {
+    if (_beforCheck()) return null;
+    return _spf.clear();
+  }
+}

+ 1 - 0
lib/helper/toast.dart

@@ -1,6 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 
+/// toast
 class Toast {
   static const int LENGTH_SHORT = 1;
   static const int LENGTH_LONG = 2;

+ 1 - 0
lib/helper/widgetutils.dart → lib/helper/widget_utils.dart

@@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
 import 'package:flutter/material.dart';
 import 'package:mvvm_flutter/helper/toast.dart';
 
+/// 对错误 [e] 进行分发处理
 dispatchFailure(BuildContext context, dynamic e) {
   var message = e.toString();
   if (e is DioError) {

+ 3 - 5
lib/main.dart

@@ -1,13 +1,11 @@
 import 'package:flutter/material.dart';
 import 'package:mvvm_flutter/view/home_page.dart';
-
 import 'di/app_module.dart';
-import 'package:dartin/dartin.dart';
 
 
-void main() {
-  /// DartIn start
-  startDartIn(appModule);
+void main() async{
+  // wait init
+  await init();
 
   runApp(MyApp());
 }

+ 0 - 8
lib/model/remote.dart

@@ -1,8 +0,0 @@
-
-import 'package:rxdart/rxdart.dart';
-import 'package:mvvm_flutter/helper/netutils.dart';
-class GithubService{
-
-  Observable<dynamic> login()=> get("user");
-
-}

+ 27 - 4
lib/model/repository.dart

@@ -1,16 +1,39 @@
 import 'dart:convert';
 
-import 'package:mvvm_flutter/di/modules.dart';
-import 'package:mvvm_flutter/model/remote.dart';
+import 'package:mvvm_flutter/helper/constants.dart';
+import 'package:mvvm_flutter/helper/net_utils.dart';
+import 'package:mvvm_flutter/helper/shared_preferences.dart';
 import 'package:rxdart/rxdart.dart';
 
+/// 网络层
+///
+/// 提供网络层接口
+class GithubService {
+  /// 登录
+  Observable login() => get("user");
+}
+
+/// 仓库层
+///
+/// 当请求开始时:处理ViewModel层传递过来的参数
+/// 当返回数据时:将网络和本地的数据组装成ViewModel层需要的数据类型
 class GithubRepo {
   final GithubService _remote;
 
-  GithubRepo(this._remote);
+  /// sharedPreference
+  ///
+  /// 也应该算在Model层,在这里面处理数据的读取
+  final SpUtil _sp;
+
+  GithubRepo(this._remote, this._sp);
 
+  /// 登录
+  ///
+  /// - 将ViewModel层 传递过来的[username] 和 [password] 处理为 token 并用[_sp]进行缓存
+  /// - 调用 [_remote] 的 [login] 方法进行网络访问
+  /// - 返回 [Observable] 类型的数据给ViewModel层
   Observable login(String username, String password) {
-    token = "basic " + base64Encode(utf8.encode('$username:$password'));
+    _sp.putString(KEY_TOKEN, "basic " + base64Encode(utf8.encode('$username:$password')));
     return _remote.login();
   }
 }

+ 33 - 7
lib/view/base.dart

@@ -1,22 +1,48 @@
+import 'dart:async';
+
 import 'package:flutter/material.dart';
 import 'package:provide/provide.dart';
+import 'package:rxdart/rxdart.dart';
 
-/**
- * normal click event
- */
+/// normal click event
 abstract class Presenter {
+  /// 处理点击事件
+  ///
+  /// 可根据 [action] 进行区分 ,[action] 应是不可变的量
   void onClick(String action);
 }
 
-/**
- * ListView Item Click
- */
+/// ListView Item Click
 abstract class ItemPresenter<T> {
+  /// 处理列表点击事件
+  ///
+  /// 可根据 [action] 进行区分 ,[action] 应是不可变的量
   void onItemClick(String action, T item);
 }
 
-class BaseViewModel with ChangeNotifier {}
+/// BaseProvide
+class BaseProvide with ChangeNotifier {
+
+  CompositeSubscription compositeSubscription = CompositeSubscription();
+
+
+  /// add [StreamSubscription] to [compositeSubscription]
+  ///
+  /// 在 [dispose]的时候能进行取消
+  addSubscription(StreamSubscription subscription){
+    compositeSubscription.add(subscription);
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    compositeSubscription.dispose();
+  }
+}
 
+/// page的基类 [PageProvideNode]
+///
+/// 隐藏了 [ProviderNode] 的调用
 abstract class PageProvideNode extends StatelessWidget {
   /// The values made available to the [child].
   final Providers mProviders = Providers();

+ 82 - 45
lib/view/home_page.dart

@@ -3,16 +3,26 @@ import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:mvvm_flutter/helper/dialog.dart';
 import 'package:mvvm_flutter/helper/toast.dart';
-import 'package:mvvm_flutter/helper/widgetutils.dart';
+import 'package:mvvm_flutter/helper/widget_utils.dart';
 import 'package:mvvm_flutter/view/base.dart';
 import 'package:mvvm_flutter/viewmodel/home_provide.dart';
 import 'package:provide/provide.dart';
 
+/// Page :HomePage
+///
+/// 获取其它页面传递来的参数
+/// 构造出各个 Provide 对象,放入到 [mProviders]里
 class HomePage extends PageProvideNode {
+  /// 页面标题
   final String title;
 
+  /// 提供
+  ///
+  /// 获取参数 [title] 并生成一个[HomeProvide]对象
+  /// 然后放入 [mProviders]中
   HomePage(this.title) {
-    mProviders.provideValue(inject<HomeProvide>(params: [title]));
+    final provide = inject<HomeProvide>(params: [title]);
+    mProviders.provideValue(provide);
   }
 
   @override
@@ -21,6 +31,10 @@ class HomePage extends PageProvideNode {
   }
 }
 
+/// View : 登录页面
+///
+/// 展示UI (ps:如果有UI地址,最好附上相应的链接)
+/// 与用户进行交互
 class _HomeContentPage extends StatefulWidget {
   @override
   State<StatefulWidget> createState() {
@@ -28,15 +42,16 @@ class _HomeContentPage extends StatefulWidget {
   }
 }
 
-/**
- * View
- */
-class _HomeContentState extends State<_HomeContentPage> with SingleTickerProviderStateMixin<_HomeContentPage> implements Presenter {
-  HomeProvide _viewModel;
+class _HomeContentState extends State<_HomeContentPage> with SingleTickerProviderStateMixin<_HomeContentPage>
+    implements Presenter {
 
+  HomeProvide mProvide;
+
+  /// 处理动画
   AnimationController _controller;
   Animation<double> _animation;
-  final _ACTION_LOGIN = "login";
+
+  static const ACTION_LOGIN = "login";
 
   final LoadingDialog loadingDialog = LoadingDialog();
 
@@ -46,7 +61,7 @@ class _HomeContentState extends State<_HomeContentPage> with SingleTickerProvide
     _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
     _animation = Tween(begin: 295.0, end: 48.0).animate(_controller)
       ..addListener(() {
-        _viewModel.btnWidth = _animation.value;
+        mProvide.btnWidth = _animation.value;
       });
   }
 
@@ -54,19 +69,25 @@ class _HomeContentState extends State<_HomeContentPage> with SingleTickerProvide
   void dispose() {
     print('-------dispose-------');
     _controller.dispose();
-    _viewModel.disposeBag();
     super.dispose();
   }
 
   @override
   void onClick(String action) {
-    if (action == _ACTION_LOGIN) {
-      _login();
+    if (action == ACTION_LOGIN) {
+      login();
     }
   }
 
-  _login() {
-    final s = _viewModel.login().doOnListen(() {
+  /// 登录
+  ///
+  /// 调用 [mProvide] 的 login 方法并进行订阅
+  /// 请求开始时:启动动画 [AnimationStatus.forward]
+  /// 请求结束时:反转动画 [AnimationStatus.reverse]
+  /// 成功 :弹出 'login success'
+  /// 失败 :[dispatchFailure] 显示错误原因
+  void login() {
+    final s = mProvide.login().doOnListen(() {
       _controller.forward();
     }).doOnDone(() {
       _controller.reverse();
@@ -79,20 +100,20 @@ class _HomeContentState extends State<_HomeContentPage> with SingleTickerProvide
       //error
       dispatchFailure(context, e);
     });
-    _viewModel.plus(s);
+    mProvide.addSubscription(s);
   }
 
   @override
   Widget build(BuildContext context) {
-    _viewModel = Provide.value<HomeProvide>(context);
+    mProvide = Provide.value<HomeProvide>(context);
     print("--------build--------");
-    return Scaffold(
-      appBar: AppBar(
-        title: Text(_viewModel.title),
-      ),
-      body: DefaultTextStyle(
-        style: TextStyle(),
-        child: Material(
+    return Material(
+      child: Scaffold(
+        appBar: AppBar(
+          title: Text(mProvide.title),
+        ),
+        body: DefaultTextStyle(
+          style: TextStyle(color: Colors.black),
           child: Column(
             children: <Widget>[
               TextField(
@@ -103,7 +124,7 @@ class _HomeContentState extends State<_HomeContentPage> with SingleTickerProvide
                   labelText: 'Account',
                 ),
                 autofocus: false,
-                onChanged: (str) => _viewModel.username = str,
+                onChanged: (str) => mProvide.username = str,
               ),
               TextField(
                 obscureText: true,
@@ -114,30 +135,12 @@ class _HomeContentState extends State<_HomeContentPage> with SingleTickerProvide
                   labelText: 'Password',
                 ),
                 autofocus: false,
-                onChanged: (str) => _viewModel.password = str,
+                onChanged: (str) => mProvide.password = str,
               ),
               const Padding(
                 padding: EdgeInsets.only(top: 30.0),
               ),
-              Provide<HomeProvide>(
-                builder: (BuildContext context, Widget child, HomeProvide value) => CupertinoButton(
-                      onPressed: value.loading ? null : () => onClick(_ACTION_LOGIN),
-                      pressedOpacity: 0.8,
-                      child: Container(
-                        alignment: Alignment.center,
-                        width: value.btnWidth,
-                        height: 48,
-                        decoration: BoxDecoration(
-                            borderRadius: BorderRadius.all(Radius.circular(30.0)),
-                            gradient: LinearGradient(colors: [
-                              Color(0xFF686CF2),
-                              Color(0xFF0E5CFF),
-                            ]),
-                            boxShadow: [BoxShadow(color: Color(0x4D5E56FF), offset: Offset(0.0, 4.0), blurRadius: 13.0)]),
-                        child: _buildChild(value),
-                      ),
-                    ),
-              ),
+              buildLoginBtnProvide(),
               const Text(
                 "Response:",
                 style: TextStyle(fontSize: 18),
@@ -161,7 +164,41 @@ class _HomeContentState extends State<_HomeContentPage> with SingleTickerProvide
     );
   }
 
-  Widget _buildChild(HomeProvide value) {
+  /// 登录按钮
+  ///
+  /// 按钮宽度根据是否进行请求由[_controller]控制
+  /// 当 [mProvide.loading] 为true 时 ,点击事件不生效
+  Provide<HomeProvide> buildLoginBtnProvide() {
+    return Provide<HomeProvide>(
+              builder: (BuildContext context, Widget child, HomeProvide value) {
+                return CupertinoButton(
+                    onPressed: value.loading ? null : () => onClick(ACTION_LOGIN),
+                    pressedOpacity: 0.8,
+                    child: Container(
+                      alignment: Alignment.center,
+                      width: value.btnWidth,
+                      height: 48,
+                      decoration: BoxDecoration(
+                          borderRadius: BorderRadius.all(Radius.circular(30.0)),
+                          gradient: LinearGradient(colors: [
+                            Color(0xFF686CF2),
+                            Color(0xFF0E5CFF),
+                          ]),
+                          boxShadow: [
+                            BoxShadow(color: Color(0x4D5E56FF), offset: Offset(0.0, 4.0), blurRadius: 13.0)
+                          ]),
+                      child: buildLoginChild(value),
+                    ),
+                  );
+              },
+            );
+  }
+
+  /// 登录按钮内部的 child
+  ///
+  /// 当请求进行时 [value.loading] 为 true 时,显示 [CircularProgressIndicator]
+  /// 否则显示普通文本
+  Widget buildLoginChild(HomeProvide value) {
     if (value.loading) {
       return const CircularProgressIndicator();
     } else {

+ 15 - 27
lib/viewmodel/home_provide.dart

@@ -3,17 +3,19 @@ import 'dart:async';
 import 'package:dio/dio.dart';
 import 'package:flutter/material.dart';
 import 'package:mvvm_flutter/model/repository.dart';
+import 'package:mvvm_flutter/view/base.dart';
 import 'package:rxdart/rxdart.dart';
 
-/**
- * ViewModel
- */
-class HomeProvide extends ChangeNotifier {
-  final CompositeSubscription _subscriptions = CompositeSubscription();
+/// ViewModel 层
+///
+/// 将 Model层 [GithubRepo] 返回的数据转换成 View 层 [HomePage] 需要展示的数据
+/// 通过 [notifyListeners] 通知UI层更新
+class HomeProvide extends BaseProvide {
   final GithubRepo _repo;
   String username = "";
   String password = "";
   bool _loading = false;
+  /// 结果
   String _response = "";
 
   final String title;
@@ -43,13 +45,14 @@ class HomeProvide extends ChangeNotifier {
 
   HomeProvide(this.title,this._repo);
 
-  /**
-   * call the model layer 's method to login
-   * doOnData : handle response when success
-   * doOnError : handle error when failure
-   * doOnListen : show loading when listen start
-   * doOnDone : hide loading when complete
-   */
+  /// 登录
+  ///
+  /// 调用 [_repo] 的 [login] 方法进行登录
+  /// doOnData : handle response when success
+  /// doOnError : handle error when failure
+  /// doOnListen : show loading when listen start
+  /// doOnDone : hide loading when complete
+  /// return [Observable] 给 View 层
   Observable login() => _repo
       .login(username, password)
       .doOnData((r) => response = r.toString())
@@ -61,19 +64,4 @@ class HomeProvide extends ChangeNotifier {
       .doOnListen(() => loading = true)
       .doOnDone(() => loading = false);
 
-  @override
-  void dispose() {
-    super.dispose();
-  }
-
-  void disposeBag(){
-    _subscriptions.dispose();
-
-  }
-
-  /// add [StreamSubscription] to [_subscriptions]
-  void plus(StreamSubscription s) {
-    _subscriptions.add(s);
-  }
-
 }

+ 1 - 0
pubspec.yaml

@@ -21,6 +21,7 @@ dependencies:
   cupertino_icons: ^0.1.2
   provide: ^1.0.2
   dio: 2.0.7
+  shared_preferences: ^0.5.1+2
   rxdart: ^0.21.0
   dartin: ^0.0.1
 

+ 0 - 19
test/rx_test.dart

@@ -1,19 +0,0 @@
-
-import 'package:rxdart/rxdart.dart';
-
-main(){
-  final s = ReplaySubject<int>();
-  s.listen((s){
-    print("S");
-  });
-
-  s.sink.add(1);
-  s.sink.add(3);
-  s.sink.add(3);
-  s.sink.add(3);
-  s.sink.add(3);
-  s.sink.add(5);
-
-
-
-}