Bläddra i källkod

refactor-重构

ditclear 7 år sedan
förälder
incheckning
dd142fe0bb

+ 19 - 0
lib/di/app_module.dart

@@ -0,0 +1,19 @@
+import 'package:mvvm_flutter/model/remote.dart';
+import 'package:mvvm_flutter/model/repository.dart';
+import 'package:mvvm_flutter/viewmodel/home_provide.dart';
+
+import 'dartin.dart';
+
+final viewModelModule = Module([
+  factory<HomeProvide>(({params}) => HomeProvide(params.get(0), get<GithubRepo>())),
+]);
+
+final repoModule = Module([
+  lazy<GithubRepo>(({params}) => GithubRepo(get<GithubService>())),
+]);
+
+final remoteModule = Module([
+  lazy<GithubService>(({params}) => GithubService()),
+]);
+
+final appModule = [viewModelModule, repoModule, remoteModule];

+ 225 - 0
lib/di/dartin.dart

@@ -0,0 +1,225 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+class DartInScope {
+  final String _name;
+
+  /// Constructor
+  const DartInScope(this._name);
+
+  @override
+  String toString() {
+    return "Scope ('$_name')";
+  }
+}
+
+/// DartIns are the values passed to the [DartInNodes].
+///
+/// DartIns can be added to using either convenience functions such as
+/// [provideValue] or by passing in DartIns.
+class DartIns {
+  // The DartIn for each given [Type] should return that type, but we can't
+  // enforce that here directly. We can use APIs to make sure it's type-safe.
+  final Map<DartInScope, Map<Type, DartIn<dynamic>>> _providers = {};
+
+  /// Creates a new empty provider.
+  DartIns();
+
+  /// The default scope in which any type not with a defined scope resides.
+  static const DartInScope defaultScope = DartInScope('_default');
+
+  /// Creates a provider with the included providers.
+  ///
+  /// If a scope is provided, the values will be under that scope.
+  factory DartIns.withDartIns(Map<Type, DartIn<dynamic>> providers, {DartInScope scope}) => DartIns()..provideAll(providers, scope: scope);
+
+  /// Add a provider for a single type.
+  ///
+  /// Will override any existing provider of that type in this node with the
+  /// given scope. If no [scope] is passed in, the default one will be used.
+  void provide<T>(DartIn<T> provider, {DartInScope scope}) {
+    // This should never happen.
+//    assert(provider.type == T);
+
+    _providersForScope(scope)[provider.type] = provider;
+  }
+
+  /// Provide many providers at once.
+  ///
+  /// Prefer using [provide] and [provideFrom] because that catches type
+  /// errors at compile-time.
+  void provideAll(Map<Type, DartIn> providers, {DartInScope scope}) {
+    for (var entry in providers.entries) {
+      if (entry.key != entry.value.type) {
+        if (entry.value.type == dynamic) {
+          throw ArgumentError('Not able to infer the type of provider for'
+              ' ${entry.key} automatically. Add type argument to provider.');
+        }
+        throw ArgumentError('Type mismatch between ${entry.key} and provider '
+            'of ${entry.value.type}.');
+      }
+    }
+
+    _providersForScope(scope).addAll(providers);
+  }
+
+  /// Add in all the providers from another DartIns.
+  void provideFrom(DartIns other) {
+    for (final scope in other._providers.keys) {
+      provideAll(other._providersForScope(scope), scope: scope);
+    }
+  }
+
+  /// Syntactic sugar around adding a value based provider.
+  ///
+  /// If this value is [Listenable], widgets that use this value can be rebuilt
+  /// on change. If no [scope] is passed in, the default one will be used.
+  void provideValue<T>(T value, {DartInScope scope}) {
+    provide(DartIn._value(value), scope: scope);
+  }
+
+  DartIn<T> getFromType<T>({DartInScope scope}) {
+    return _providersForScope(scope)[T];
+  }
+
+  T value<T>({DartInScope scope, List values}) {
+    return getFromType<T>(scope: scope)?.get(values: values);
+  }
+
+  Map<Type, DartIn<dynamic>> _providersForScope(scope) => _providers[scope ?? defaultScope] ??= {};
+}
+
+/// A DartIn provides a value on request.
+///
+/// If a provider implements [Listenable], it will be listened to by the
+/// [Provide] widget to rebuild on change. Other than the built in providers,
+/// one can implement DartIn to provide caching or linkages.
+///
+/// When a DartIn is instantiated within a [providers.provide] call, the type
+/// can be inferred and therefore the type can be ommited, but otherwise,
+/// [T] is required.
+///
+/// DartIn should be implemented and not extended.
+abstract class DartIn<T> {
+  /// Returns the value provided by the provider.
+  ///
+  /// Because providers could potentially initialize the value each time [get]
+  /// is called, this should be called as infrequently as possible.
+  T get({List values});
+
+  /// The type that is provided by the provider.
+  Type get type;
+
+  /// Creates a provider with the value provided to it.
+  factory DartIn._value(T value) => _ValueDartIn(value);
+
+  /// Creates a provider which will initialize using the [_DartInFunction]
+  /// the first time the value is requested.
+  ///
+  /// The context can be used to obtain other values from the provider. However,
+  /// care should be taken with this to not have circular dependencies.
+  factory DartIn._lazy(_DartInFunction<T> function) => _LazyDartIn<T>(function);
+
+  /// Creates a provider that provides a new value for each
+  /// requestor of the value.
+  factory DartIn._withFactory(_DartInFunction<T> function) => _FactoryDartIn<T>(function);
+}
+
+/// Base mixin for providers.
+abstract class _TypedDartIn<T> implements DartIn<T> {
+  /// The type of the provider
+  @override
+  Type get type => T;
+}
+
+/// Contains a value which will never be disposed.
+class _ValueDartIn<T> extends _TypedDartIn<T> {
+  final T _value;
+
+  @override
+  T get({List values}) => _value;
+
+  _ValueDartIn(this._value);
+}
+
+/// Function that returns an instance of T when called.
+typedef _DartInFunction<T> = T Function({_ParameterList params});
+
+/// Is initialized on demand, and disposed when no longer needed
+/// if [dispose] is set to true.
+/// When obtained statically, the value will never be disposed.
+class _LazyDartIn<T> with _TypedDartIn<T> {
+  final _DartInFunction<T> _initalizer;
+
+  T _value;
+
+  _LazyDartIn(this._initalizer);
+
+  @override
+  T get({List values}) {
+    // Need to have a local copy for casting because
+    // dart requires it.
+    T value;
+    if (_value == null) {
+      value = _value ??= _initalizer(params: _ParameterList.parametersOf(values));
+    }
+    return _value;
+  }
+}
+
+/// A provider who's value is obtained from providerFunction for each time the
+/// value is requested.
+///
+/// This provider doesn't keep any values itself, so those values are disposed
+/// when the containing widget is disposed.
+class _FactoryDartIn<T> with _TypedDartIn<T> {
+  final _DartInFunction<T> providerFunction;
+
+  _FactoryDartIn(this.providerFunction);
+
+  @override
+  T get({List values}) => providerFunction(params: _ParameterList.parametersOf(values));
+}
+
+class Module {
+  final List<DartIn> providerIns;
+  Module(this.providerIns);
+}
+
+class _ParameterList {
+  final List<Object> values;
+
+  get(int i) {
+    if (values == null || i > values.length - 1 || i < 0) {
+      return null;
+    }
+    return values[i];
+  }
+
+  _ParameterList.parametersOf(this.values);
+}
+
+DartIn<T> factory<T>(_DartInFunction<T> value,{String scope }) => DartIn<T>._withFactory(value);
+
+DartIn<T> single<T>(T value) => DartIn<T>._value(value);
+
+DartIn<T> lazy<T>(_DartInFunction<T> value) => DartIn<T>._lazy(value);
+
+T get<T>({DartInScope scope, List params}) {
+  assert(_dartIns != null);
+  return _dartIns.value<T>(scope: scope, values: params);
+}
+
+T inject<T>({DartInScope scope, List params}) => get<T>(scope: scope, params: params);
+
+DartIns _dartIns;
+
+startDartIn(List<Module> modules) {
+  _dartIns = DartIns();
+  for (var module in modules) {
+    for (var providerIn in module.providerIns) {
+      _dartIns.provide(providerIn);
+    }
+  }
+}

+ 13 - 21
lib/di/modules.dart

@@ -1,38 +1,30 @@
-import 'dart:convert';
-
 import 'package:dio/dio.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
+import 'package:mvvm_flutter/di/dartin.dart';
 import 'package:mvvm_flutter/model/remote.dart';
 import 'package:mvvm_flutter/model/repository.dart';
-import 'package:mvvm_flutter/view/home.dart';
-import 'package:provide/provide.dart';
-
+import 'package:mvvm_flutter/viewmodel/home_provide.dart';
 
 final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
 
-String token="";
+String token = "";
 
-final Dio dio=Dio()
-  ..options = BaseOptions(
-      baseUrl: 'https://api.github.com/',
-      connectTimeout: 30,
-      receiveTimeout: 30)
+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));
 
-GithubService _provideGithubService()=>GithubService();
-GithubRepo _provideGithubRepo()=>GithubRepo(_provideGithubService());
-HomeViewModel provideHomeViewModel() => HomeViewModel(_provideGithubRepo());
-
-
-final providers = Providers();
-
-class AuthInterceptor extends Interceptor{
+//GithubService _provideGithubService() => GithubService();
+//GithubRepo _provideGithubRepo() => GithubRepo(_provideGithubService());
+//HomeProvide provideHomeViewModel() => HomeProvide("1", _provideGithubRepo());
 
+class AuthInterceptor extends Interceptor {
   @override
   onRequest(RequestOptions options) {
-    options.headers.update("Authorization",(_)=>token,ifAbsent: ()=>token);
+    options.headers.update("Authorization", (_) => token, ifAbsent: () => token);
     return super.onRequest(options);
   }
-}
+}
+
+

+ 122 - 0
lib/helper/dialog.dart

@@ -0,0 +1,122 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+Future<T> _showAlert<T>({BuildContext context, Widget child}) => showDialog<T>(
+      context: context,
+      barrierDismissible: false,
+      builder: (BuildContext context) => child,
+    );
+/**
+ * onlyPositive  : 只有确定按钮
+ */
+Future<bool> showAlert(BuildContext context,
+        {String title,
+        String negativeText = "取消",
+        String positiveText = "确定",
+        bool onlyPositive = false}) =>
+    _showAlert<bool>(
+      context: context,
+      child: CupertinoAlertDialog(
+        title: Text(title),
+        actions: _buildAlertActions(
+            context, onlyPositive, negativeText, positiveText),
+      ),
+    );
+
+List<Widget> _buildAlertActions(BuildContext context, bool onlyPositive,
+    String negativeText, String positiveText) {
+  if (onlyPositive) {
+    return [
+      CupertinoDialogAction(
+        child: Text(
+          positiveText,
+          style: TextStyle(fontSize: 18.0),
+        ),
+        isDefaultAction: true,
+        onPressed: () {
+          Navigator.pop(context, true);
+        },
+      ),
+    ];
+  } else {
+    return [
+      CupertinoDialogAction(
+        child: Text(
+          negativeText,
+          style: TextStyle(color: Color(0xFF71747E), fontSize: 18.0),
+        ),
+        isDestructiveAction: true,
+        onPressed: () {
+          Navigator.pop(context, false);
+        },
+      ),
+      CupertinoDialogAction(
+        child: Text(
+          positiveText,
+          style: TextStyle(fontSize: 18.0),
+        ),
+        isDefaultAction: true,
+        onPressed: () {
+          Navigator.pop(context, true);
+        },
+      ),
+    ];
+  }
+}
+
+/**
+ * 显示loading框  , 隐藏调用 Navigator.pop(context)
+ */
+Future _showLoadingDialog(BuildContext c, LoadingDialog loading,
+        {bool cancelable = true}) =>
+    showDialog(
+        context: c,
+        barrierDismissible: cancelable,
+        builder: (BuildContext c) => loading);
+
+class LoadingDialog extends CupertinoAlertDialog {
+  BuildContext parentContext;
+  BuildContext currentContext;
+  bool showing;
+  show(BuildContext context) {
+    parentContext = context;
+    showing = true;
+    _showLoadingDialog(context, this).then((_){
+        showing=false;
+    });
+  }
+
+  hide() {
+    if(showing) {
+      Navigator.removeRoute(parentContext, ModalRoute.of(currentContext));
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    currentContext= context;
+    return WillPopScope(
+      onWillPop: () => Future.value(true),
+      child: LayoutBuilder(
+        builder: (BuildContext context, BoxConstraints constraints) {
+          return Center(
+            child: Container(
+              width: 120,
+              height: 120,
+              child: CupertinoPopupSurface(
+                child: Semantics(
+                  namesRoute: true,
+                  scopesRoute: true,
+                  explicitChildNodes: true,
+                  child: const Center(
+                    child: CupertinoActivityIndicator(),
+                  ),
+                ),
+              ),
+            ),
+          );
+        },
+      ),
+    );
+  }
+}

+ 3 - 1
lib/helper/widgetutils.dart

@@ -26,5 +26,7 @@ dispatchFailure(BuildContext context, dynamic e) {
     }
   }
   print("出错了:"+message);
-  Toast.show(message, context, type: Toast.ERROR);
+  if(context!=null) {
+    Toast.show(message, context, type: Toast.ERROR);
+  }
 }

+ 11 - 23
lib/main.dart

@@ -1,14 +1,12 @@
 import 'package:flutter/material.dart';
-import 'package:mvvm_flutter/di/modules.dart';
-import 'package:mvvm_flutter/model/remote.dart';
-import 'package:mvvm_flutter/model/repository.dart';
-import 'package:mvvm_flutter/view/home.dart';
-import 'package:provide/provide.dart';
+import 'package:mvvm_flutter/view/home_page.dart';
 
-void main() {
-
-  runApp(ProviderNode(child: MyApp(), providers: providers));
+import 'di/app_module.dart';
+import 'di/dartin.dart';
 
+void main() {
+  startDartIn(appModule);
+  runApp(MyApp());
 }
 
 class MyApp extends StatelessWidget {
@@ -17,20 +15,10 @@ class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
-      title: 'MVVM-Flutter',
-      theme: ThemeData(
-        // This is the theme of your application.
-        //
-        // Try running your application with "flutter run". You'll see the
-        // application has a blue toolbar. Then, without quitting the app, try
-        // changing the primarySwatch below to Colors.green and then invoke
-        // "hot reload" (press "r" in the console where you ran "flutter run",
-        // or simply save your changes to "hot reload" in a Flutter IDE).
-        // Notice that the counter didn't reset back to zero; the application
-        // is not restarted.
-        primarySwatch: Colors.blue,
-      ),
-      home: HomeWidget(),
-    );
+        title: 'MVVM-Flutter',
+        theme: ThemeData(
+          primarySwatch: Colors.blue,
+        ),
+        home: HomePage('MVVM-Flutter'));
   }
 }

+ 34 - 0
lib/view/base.dart

@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+import 'package:provide/provide.dart';
+
+/**
+ * 普通widget点击事件处理
+ */
+abstract class Presenter {
+  void onClick(String action);
+}
+
+/**
+ * 列表Item点击事件处理
+ */
+abstract class ItemPresenter<T> {
+  void onItemClick(String action, T item);
+}
+
+class BaseViewModel with ChangeNotifier {}
+
+abstract class PageProvideNode extends StatelessWidget {
+  /// The values made available to the [child].
+  final Providers mProviders = Providers();
+
+  Widget buildContent(BuildContext context);
+
+  @override
+  Widget build(BuildContext context) {
+    return ProviderNode(
+      providers: mProviders,
+      child: buildContent(context),
+    );
+  }
+
+}

+ 0 - 221
lib/view/home.dart

@@ -1,221 +0,0 @@
-import 'package:dio/dio.dart';
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/material.dart';
-import 'package:mvvm_flutter/di/modules.dart';
-import 'package:mvvm_flutter/helper/toast.dart';
-import 'package:mvvm_flutter/helper/widgetutils.dart';
-import 'package:mvvm_flutter/model/repository.dart';
-import 'package:provide/provide.dart';
-import 'package:rxdart/rxdart.dart';
-
-class HomeWidget extends StatefulWidget {
-  @override
-  State<StatefulWidget> createState() {
-    return _HomeState(provideHomeViewModel());
-  }
-}
-
-/**
- * View
- */
-class _HomeState extends State<HomeWidget>
-    with SingleTickerProviderStateMixin<HomeWidget> {
-  final HomeViewModel _viewModel;
-  final CompositeSubscription _subscriptions = CompositeSubscription();
-
-  AnimationController _controller;
-  Animation<double> _animation;
-
-  _HomeState(this._viewModel) {
-    providers.provideValue(_viewModel);
-  }
-
-  @override
-  void initState() {
-    super.initState();
-    _controller = AnimationController(
-        vsync: this, duration: const Duration(milliseconds: 300));
-    _animation = Tween(begin: _viewModel.btnWidth, end: 48.0).animate(_controller)
-      ..addListener(() {
-        _viewModel.btnWidth = _animation.value;
-      });
-  }
-
-  _login() {
-    final s = _viewModel.login().doOnListen(() {
-      _controller.forward();
-    }).doOnDone(() {
-      _controller.reverse();
-    }).listen((_) {
-      //success
-      Toast.show("login success",context,type: Toast.SUCCESS);
-    }, onError: (e) {
-      //error
-      dispatchFailure(context, e);
-    });
-    _subscriptions.add(s);
-  }
-
-  @override
-  void dispose() {
-    _controller.dispose();
-    _subscriptions.dispose();
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: const Text("MVVM-Flutter"),
-      ),
-      body: Material(
-        child: Column(
-          children: <Widget>[
-            TextField(
-              keyboardType: TextInputType.text,
-              decoration: InputDecoration(
-                contentPadding: EdgeInsets.all(10.0),
-                icon: Icon(Icons.person),
-                labelText: '账号',
-              ),
-              autofocus: false,
-              onChanged: (str) => _viewModel.username = str,
-            ),
-            TextField(
-              obscureText: true,
-              keyboardType: TextInputType.number,
-              decoration: InputDecoration(
-                contentPadding: EdgeInsets.all(10.0),
-                icon: Icon(Icons.lock),
-                labelText: '密码',
-              ),
-              autofocus: false,
-              onChanged: (str) => _viewModel.password = str,
-            ),
-            const Padding(
-              padding: EdgeInsets.only(top: 30.0),
-            ),
-            Provide<HomeViewModel>(
-              builder: (BuildContext context, Widget child,
-                      HomeViewModel value) =>
-                  CupertinoButton(
-                    onPressed: value.loading?null:_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),
-                    ),
-                  ),
-            ),
-            const Text(
-              "Response:",
-              style: TextStyle(fontSize: 18),
-              textAlign: TextAlign.start,
-            ),
-            Expanded(
-              child: Container(
-                constraints: BoxConstraints(minWidth: double.infinity),
-                margin: EdgeInsets.fromLTRB(12, 12, 12, 0),
-                padding: EdgeInsets.all(5.0),
-                decoration:
-                    BoxDecoration(border: Border.all(color: Colors.blue)),
-                child: Provide<HomeViewModel>(
-                  builder: (BuildContext context, Widget child,
-                          HomeViewModel value) =>
-                      Text(value.response),
-                ),
-              ),
-            )
-          ],
-        ),
-      ),
-    );
-  }
-
-  Widget _buildChild(HomeViewModel value) {
-    if (value.loading) {
-      return const CircularProgressIndicator();
-    } else {
-      return const FittedBox(
-        fit: BoxFit.scaleDown,
-        child: const Text(
-          '使用GitHub账号登录',
-          maxLines: 1,
-          textAlign: TextAlign.center,
-          overflow: TextOverflow.fade,
-          style: const TextStyle(
-              fontWeight: FontWeight.bold, fontSize: 16.0, color: Colors.white),
-        ),
-      );
-    }
-  }
-}
-
-/**
- * ViewModel
- */
-class HomeViewModel extends ChangeNotifier {
-  final GithubRepo _repo; //数据仓库
-  String username = ""; //账号
-  String password = ""; //密码
-  bool _loading = false; // 加载中
-  String _response = ""; //响应数据
-
-  String get response => _response;
-
-  set response(String response) {
-    _response = response;
-    notifyListeners();
-  }
-
-  bool get loading => _loading;
-
-  double _btnWidth = 295.0;
-
-  double get btnWidth => _btnWidth;
-
-  set btnWidth(double btnWidth) {
-    _btnWidth = btnWidth;
-    notifyListeners();
-  }
-
-  set loading(bool loading) {
-    _loading = loading;
-    notifyListeners();
-  }
-
-  HomeViewModel(this._repo);
-
-  /**
-   * 调用model层的方法进行登录
-   * doOnData : 请求成功时,处理响应数据
-   * doOnError : 请求失败时,处理错误
-   * doOnListen : 开始时loading为true,通知ui更新
-   * doOnDone : 结束时loading为false,通知ui更新
-   */
-  Observable login() => _repo
-      .login(username, password)
-      .doOnData((r) => response = r.toString())
-      .doOnError((e, stacktrace) {
-        if (e is DioError) {
-          response = e.response.data.toString();
-        }
-      })
-      .doOnListen(() => loading = true)
-      .doOnDone(() => loading = false);
-}

+ 183 - 0
lib/view/home_page.dart

@@ -0,0 +1,183 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:mvvm_flutter/di/dartin.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/view/base.dart';
+import 'package:mvvm_flutter/viewmodel/home_provide.dart';
+import 'package:provide/provide.dart';
+
+class HomePage extends PageProvideNode {
+  final String title;
+
+
+  HomePage(this.title){
+    mProviders.provideValue(inject<HomeProvide>(params: [title]));
+  }
+
+  @override
+  Widget buildContent(BuildContext context) {
+    return _HomeContentPage();
+  }
+
+}
+
+class _HomeContentPage extends StatefulWidget {
+
+  @override
+  State<StatefulWidget> createState() {
+    return _HomeContentState();
+  }
+}
+
+/**
+ * View
+ */
+class _HomeContentState extends State<_HomeContentPage> with SingleTickerProviderStateMixin<_HomeContentPage> implements Presenter {
+  HomeProvide _viewModel;
+
+  AnimationController _controller;
+  Animation<double> _animation;
+  final _ACTION_LOGIN = "login";
+
+  final LoadingDialog loadingDialog = LoadingDialog();
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
+    _animation = Tween(begin: 295.0, end: 48.0).animate(_controller)
+      ..addListener(() {
+        _viewModel.btnWidth = _animation.value;
+      });
+  }
+
+  @override
+  void dispose() {
+    print('-------dispose-------');
+    _controller.dispose();
+    _viewModel.disposeBag();
+    super.dispose();
+  }
+
+  @override
+  void onClick(String action) {
+    if (action == _ACTION_LOGIN) {
+      _login();
+    }
+  }
+
+  _login() {
+    final s = _viewModel.login().doOnListen(() {
+      _controller.forward();
+    }).doOnDone(() {
+      _controller.reverse();
+    }).doOnCancel(() {
+      print("======cancel======");
+    }).listen((_) {
+      //success
+      Toast.show("login success", context, type: Toast.SUCCESS);
+    }, onError: (e) {
+      //error
+      dispatchFailure(context, e);
+    });
+    _viewModel.plus(s);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    _viewModel = Provide.value<HomeProvide>(context);
+    print("--------build--------");
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(_viewModel.title),
+      ),
+      body: DefaultTextStyle(
+        style: TextStyle(),
+        child: Material(
+          child: Column(
+            children: <Widget>[
+              TextField(
+                keyboardType: TextInputType.text,
+                decoration: InputDecoration(
+                  contentPadding: EdgeInsets.all(10.0),
+                  icon: Icon(Icons.person),
+                  labelText: '账号',
+                ),
+                autofocus: false,
+                onChanged: (str) => _viewModel.username = str,
+              ),
+              TextField(
+                obscureText: true,
+                keyboardType: TextInputType.number,
+                decoration: InputDecoration(
+                  contentPadding: EdgeInsets.all(10.0),
+                  icon: Icon(Icons.lock),
+                  labelText: '密码',
+                ),
+                autofocus: false,
+                onChanged: (str) => _viewModel.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),
+                      ),
+                    ),
+              ),
+              const Text(
+                "Response:",
+                style: TextStyle(fontSize: 18),
+                textAlign: TextAlign.start,
+              ),
+              Expanded(
+                child: Container(
+                  constraints: BoxConstraints(minWidth: double.infinity),
+                  margin: EdgeInsets.fromLTRB(12, 12, 12, 0),
+                  padding: EdgeInsets.all(5.0),
+                  decoration: BoxDecoration(border: Border.all(color: Colors.blue)),
+                  child: Provide<HomeProvide>(
+                    builder: (BuildContext context, Widget child, HomeProvide value) => Text(value.response),
+                  ),
+                ),
+              )
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildChild(HomeProvide value) {
+    if (value.loading) {
+      return const CircularProgressIndicator();
+    } else {
+      return const FittedBox(
+        fit: BoxFit.scaleDown,
+        child: const Text(
+          '使用GitHub账号登录',
+          maxLines: 1,
+          textAlign: TextAlign.center,
+          overflow: TextOverflow.fade,
+          style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0, color: Colors.white),
+        ),
+      );
+    }
+  }
+}

+ 79 - 0
lib/viewmodel/home_provide.dart

@@ -0,0 +1,79 @@
+import 'dart:async';
+
+import 'package:dio/dio.dart';
+import 'package:flutter/material.dart';
+import 'package:mvvm_flutter/di/dartin.dart';
+import 'package:mvvm_flutter/model/repository.dart';
+import 'package:rxdart/rxdart.dart';
+
+/**
+ * ViewModel
+ */
+class HomeProvide extends ChangeNotifier {
+  final CompositeSubscription _subscriptions = CompositeSubscription();
+  final GithubRepo _repo; //数据仓库
+  String username = ""; //账号
+  String password = ""; //密码
+  bool _loading = false; // 加载中
+  String _response = ""; //响应数据
+
+  final String title;
+
+  String get response => _response;
+
+  set response(String response) {
+    _response = response;
+    notifyListeners();
+  }
+
+  bool get loading => _loading;
+
+  double _btnWidth = 295.0;
+
+  double get btnWidth => _btnWidth;
+
+  set btnWidth(double btnWidth) {
+    _btnWidth = btnWidth;
+    notifyListeners();
+  }
+
+  set loading(bool loading) {
+    _loading = loading;
+    notifyListeners();
+  }
+
+  HomeProvide(this.title,this._repo);
+
+  /**
+   * 调用model层的方法进行登录
+   * doOnData : 请求成功时,处理响应数据
+   * doOnError : 请求失败时,处理错误
+   * doOnListen : 开始时loading为true,通知ui更新
+   * doOnDone : 结束时loading为false,通知ui更新
+   */
+  Observable login() => _repo
+      .login(username, password)
+      .doOnData((r) => response = r.toString())
+      .doOnError((e, stacktrace) {
+        if (e is DioError) {
+          response = e.response.data.toString();
+        }
+      })
+      .doOnListen(() => loading = true)
+      .doOnDone(() => loading = false);
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void disposeBag(){
+    _subscriptions.dispose();
+
+  }
+
+  void plus(StreamSubscription s) {
+    _subscriptions.add(s);
+  }
+
+}

+ 19 - 0
test/rx_test.dart

@@ -0,0 +1,19 @@
+
+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);
+
+
+
+}