mvvm 0.1.7

  • Readme
  • Changelog
  • Example
  • Installing
  • 81

pub package join chat

A Flutter MVVM (Model-View-ViewModel) implementation. It uses property-based data binding to establish a connection between the ViewModel and the View, and drives the View changes through the ViewModel.

一个 Flutter 的 MVVM(Model-View-ViewModel) 实现。 它使用基于属性 (property) 的数据绑定在视图模型 (ViewModel) 与视图 (View) 之间建立关联,并通过视图模型 (ViewModel) 驱动视图 (View) 变化。

Documentation & Example

import 'package:flutter/widgets.dart';
import 'package:mvvm/mvvm.dart';
import 'dart:async';

// ViewModel
class Demo1ViewModel extends ViewModel {
  Demo1ViewModel() {
    propertyValue<String>(#time, initial: "");
    start();
  }

  start() {
    Timer.periodic(const Duration(seconds: 1), (_) {
      var now = DateTime.now();
      setValue<String>(#time, "${now.hour}:${now.minute}:${now.second}");
    });
  }
}

// View
class Demo1View extends View<Demo1ViewModel> {
  Demo1View() : super(Demo1ViewModel());

  @override
  Widget build(BuildContext context) {
    return Container(
        margin: EdgeInsets.symmetric(vertical: 100),
        padding: EdgeInsets.all(40),
        // binding
        child: $.watchFor<String>(#time,
            builder:
                $.builder1((t) => Text(t, textDirection: TextDirection.ltr))));
  }
}

// run
void main() => runApp(Demo1View());

mvvm

APIs #

ViewContext ($.*) #

Properties #

Methods #

override

ViewModel #

Methods #

override

View #

Properties #

Methods #

override

Documentation

License #

MIT

0.1.7 #

  • 完善代码注释文档
  • 完善 APIs.md

0.1.6+1 #

  • 完善 APIs.md

0.1.6 #

  • 代码优化
  • 增加测试代码

0.1.5+1 #

  • 更新 README.md

0.1.5 #

  • 视图上下文 ViewContext 增加 disposeviewInitviewReady 方法
  • 视图模型 ViewModel 增加 disposeviewInitviewReady 方法
  • 视图 View 增加 disposeready 方法, 原有 buildCore 方法更名为 build, 原有 initView 方法更名为 init
  • 代码优化
  • 增加列表示例 example/lib/example_list.dart

0.1.4+1 #

  • 增加属性键 propertyKey 查找属性 Property 未找到时异常
  • 视图上下文 ViewContext 中增加任务功能
  • 视图 View 增加"流程管线"
  • 代码优化
  • 更新 example

0.1.4 #

  • 绑定属性 BindableProperty 增加 valueChanged 属性值变更后回调方法
  • 视图上下文 ViewContext 增加 adapt 辅助方法, 用于在 View 中动态创建适配到 Widget 的绑定属性
  • 视图上下文 ViewContext 增加 getValueForsetValueFor 辅助方法, 用于在 View 中手动获取或设置绑定属性值
  • 代码优化、完善文件头信息
  • 更新 example、README.md

0.1.3+4 #

  • 增加 AsyncViewModelPropertyresetOnBefore 参数功能

0.1.3+3 #

  • 代码优化

0.1.3+2 #

  • 代码优化
  • 更新 example

0.1.3+1 #

  • 代码优化

0.1.3 #

  • 完善代码注释文档
  • 增加 analysis_options.yaml
  • ViewModelproperty(..) 方法重命名为 propertyValue(..), propertyAdaptive 方法去除 TAdapteeValue 泛型参数
  • ValueNotifierAdapterTAdaptee 泛型参数约束调整
  • 更新 example
  • 更新 README.md

0.1.2+1 #

  • 增加代码注释文档

0.1.2 #

  • 拆分视图上下文 ViewContext 功能
  • 更新示例程序

0.1.1 #

  • 项目信息变更

0.1.0 #

  • 初始项目

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:mvvm/mvvm.dart';
import 'dart:async';

void main() => runApp(MaterialApp(home: LoginView()));

class User {
  String name;
  String displayName;
  User(this.name, this.displayName);
}

// Service
class RemoteService {
  Future<User> login(String userName, String password) async {
    return Future.delayed(Duration(seconds: 3), () {
      if (userName == "tom" && password == "123")
        return User(userName, "$userName cat~");
      throw "mock error.";
    });
  }
}

// ViewModel
class LoginViewModel extends ViewModel with AsyncViewModelMixin {
  final RemoteService _service;

  final TextEditingController userNameCtrl = TextEditingController();
  final TextEditingController passwordCtrl = TextEditingController();

  LoginViewModel(this._service) {
    propertyValue<DateTime>(#time, initial: DateTime.now());
    // timer
    start();

    propertyAdaptive<String, TextEditingController>(
        #userName, userNameCtrl, (v) => v.text, (a, v) => a.text = v,
        valueChanged: (v, k) => print("$k: $v"));

    propertyAsync<User>(
        #login, () => _service.login(userNameCtrl.text, passwordCtrl.text),
        valueChanged: (v, k) => print("$k: $v"));
  }

  bool get inputValid =>
      userNameCtrl.text.length > 2 && passwordCtrl.text.length > 2;

  final _pad = (int v) => "$v".padLeft(2, "0");
  String format(DateTime dt) =>
      "${_pad(dt.hour)}:${_pad(dt.minute)}:${_pad(dt.second)}";

  start() {
    Timer.periodic(const Duration(seconds: 1),
        (_) => setValue<DateTime>(#time, DateTime.now()));
  }
}

// View
class LoginView extends View<LoginViewModel> {
  LoginView() : super(LoginViewModel(RemoteService()));

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
            margin: EdgeInsets.only(top: 100, bottom: 30),
            padding: EdgeInsets.all(40),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                $.watchFor<DateTime>(#time,
                    builder: $.builder1((t) => Text($Model.format(t),
                        style:
                            TextStyle(color: Colors.redAccent, fontSize: 48)))),
                SizedBox(height: 30),
                TextField(
                  controller: $Model.userNameCtrl,
                  decoration: InputDecoration(
                    border: UnderlineInputBorder(),
                    labelText: 'UserName',
                  ),
                ),
                SizedBox(height: 10),
                $.adapt<String>(#password,
                    builder: (emit) => TextField(
                          onChanged: (v) => emit(),
                          obscureText: true,
                          controller: $Model.passwordCtrl,
                          decoration: InputDecoration(
                            border: UnderlineInputBorder(),
                            labelText: 'Password',
                          ),
                        ),
                    valueGetter: () => $Model.passwordCtrl.text,
                    valueSetter: (v) => $Model.passwordCtrl.text = v,
                    valueChanged: (v, k) => print("$k: $v")),
                SizedBox(height: 10),
                $.$ifFor(#login,
                    valueHandle: (AsyncSnapshot snapshot) => snapshot.hasError,
                    builder: $.builder1((AsyncSnapshot snapshot) {
                      return Text("${snapshot.error}",
                          style:
                              TextStyle(color: Colors.redAccent, fontSize: 16));
                    })),
                Container(
                    margin: EdgeInsets.only(top: 80),
                    width: double.infinity,
                    child: $.watchAnyFor<String>(const [#userName, #password],
                        builder: $.builder2((_, child) => RaisedButton(
                            onPressed: $Model.inputValid
                                ? $Model.link(#login, resetOnBefore: true)
                                : null,
                            child: child,
                            color: Colors.blueAccent,
                            textColor: Colors.white)),
                        child: $.watchFor(#login,
                            builder: $.builder2(
                                (AsyncSnapshot snapshot, child) =>
                                    snapshot.connectionState ==
                                            ConnectionState.waiting
                                        ? _buildWaitingWidget()
                                        : child),
                            child: Text("login")))),
                SizedBox(height: 20),
                $.$ifFor<AsyncSnapshot<User>>(#login,
                    valueHandle: (AsyncSnapshot snapshot) => snapshot.hasData,
                    builder: $.builder1((AsyncSnapshot<User> snapshot) => Text(
                        "${snapshot.data?.displayName}",
                        style:
                            TextStyle(color: Colors.blueAccent, fontSize: 20))))
              ],
            )));
  }

  Widget _buildWaitingWidget() => SizedBox(
      width: 20,
      height: 20,
      child: CircularProgressIndicator(
        backgroundColor: Colors.white,
        strokeWidth: 2,
      ));
}

Use this package as a library

1. Depend on it

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


dependencies:
  mvvm: ^0.1.7

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:mvvm/mvvm.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
62
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
81
Learn more about scoring.

We analyzed this package on Nov 15, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.6.0
  • pana: 0.12.21
  • Flutter: 1.9.1+hotfix.6

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.7 1.1.8
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test