Flutter开发 - 使用GetX框架实现类似MVVM架构

语言: CN / TW / HK

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情

回顾原生开发

在Android原生开发中,通常是使用Databinding实现MVVM架构,只需要在gradle中开启databinding的选项,然后使用ObservableField或LiveData即可。 groovy buildFeatures { dataBinding true } xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="m" type="com.example.vm.LoginViewModel" /> </data> <EditText android:text="@{m.username}"/> </layout> 在ViewModel中可以定义ObservableField,这样界面就可以直接观察了。简单看一下ObservableField的源码,其实还有另外的几个类也是类似的功能,比如ObservableInt和ObservableBoolean。

```java package androidx.databinding;

import androidx.annotation.Nullable;

import java.io.Serializable;

/* * An object wrapper to make it observable. *

* Observable field classes may be used instead of creating an Observable object. It can also * create a calculated field, depending on other fields: *

public class MyDataObject {
 *     private Context context;
 *     public final ObservableField<String> first = new ObservableField<String>();
 *     public final ObservableField<String> last = new ObservableField<String>();
 *     public final ObservableField<String> display =
 *         new ObservableField<String>(first, last) {
 *             @Override
 *             public String get() {
 *                 return context.getResources().getString(R.string.name, first.get, last.get());
 *             }
 *         };
 *     public final ObservableInt age = new ObservableInt();
 * }
* Fields of this type should be declared final because bindings only detect changes in the * field's value, not of the field itself. * * @param The type parameter for the actual object. * @see ObservableParcelable / public class ObservableField extends BaseObservableField implements Serializable { static final long serialVersionUID = 1L; private T mValue;

/**
 * Wraps the given object and creates an observable object
 *
 * @param value The value to be wrapped as an observable.
 */
public ObservableField(T value) {
    mValue = value;
}

/**
 * Creates an empty observable object
 */
public ObservableField() {
}

/**
 * Creates an ObservableField that depends on {@code dependencies}. Typically,
 * ObservableFields are passed as dependencies. When any dependency
 * notifies changes, this ObservableField also notifies a change.
 *
 * @param dependencies The Observables that this ObservableField depends on.
 */
public ObservableField(Observable... dependencies) {
    super(dependencies);
}

/**
 * @return the stored value.
 */
@Nullable
public T get() {
    return mValue;
}

/**
 * Set the stored value.
 *
 * @param value The new value
 */
public void set(T value) {
    if (value != mValue) {
        mValue = value;
        notifyChange();
    }
}

} ``` databinding里面的ObservableField类可以帮助我们很好的实现数据的双向绑定,通过调用它 的get()方法就可以拿到值了,在xml中也可以很方便地使用其值。 这样一种优雅的写法,在Flutter中怎么玩呢?还是那句话,巧妇难为无米之炊,首先你得有Flutter的开发环境,要不然你也就只能看看了。如果不了解Flutter环境怎么搭建的可以看http://juejin.cn/post/7185571569048125477 这篇文章。那么我们开向幼儿园的车马上就要发车了。不好意思,说错了,是开往大前端大佬的车。

框架搭建

我们先大概了解下Flutter项目的项目结构,由于这是一个演示Demo,我这里就不分包了,你们可以写的更优雅。集成get框架就一个命令,flutter就是这么简单。

flutter pub add get

项目结构

内行人一眼就看出我们肯定是先看main.dart文件。

```dart import 'package:flutter/material.dart'; import 'package:flutter_mvvm_demo/home_binding.dart'; import 'package:flutter_mvvm_demo/main_binding.dart'; import 'package:flutter_mvvm_demo/home_view.dart'; import 'package:flutter_mvvm_demo/main_view.dart'; import 'package:get/get.dart';

void main() { runApp(const MyApp()); }

class MyApp extends StatelessWidget { const MyApp({super.key});

@override Widget build(BuildContext context) { return GetMaterialApp( initialRoute: '/home', getPages: [ GetPage( name: '/home', page: () => const HomeView(), binding: HomeBinding(), ), GetPage( name: '/main', page: () => const MainView(), binding: MainBinding(), ), ], ); } } ``` 这个MyApp就是我们程序的入口,相当于Application类,其实最主要是因为在main()方法中调了一个runApp()方法,然后传入了我们的第一个视图。在Flutter中,不是管View叫视图。Flutter中的视图有两种,一种是有状态的StatefulWidget,还有一种是无状态的StatelessWidget。我们这里返回了一个GetMaterialApp,里面指定了两个属性。initialRoute代表第一个页面的路由,getPages中指定所有的页面。GetPage里面的page指定了这个页面的视图Widget,然后使用binding指定依赖注入,简单说就是不用在使用的时候new对象,直接注入到属性中。

```dart import 'package:flutter_mvvm_demo/home_controller.dart'; import 'package:get/get.dart';

class HomeBinding extends Bindings {

@override void dependencies() { Get.put(HomeController()); } } dart import 'package:flutter_mvvm_demo/main_controller.dart'; import 'package:get/get.dart';

class MainBinding extends Bindings {

@override void dependencies() { Get.put(MainController()); } } 调用Get.put()方法我们就注入了controller,之后我们可以直接在视图层拿到这个controller。dart import 'package:flutter/material.dart'; import 'package:flutter_mvvm_demo/home_controller.dart'; import 'package:get/get.dart';

class HomeView extends GetView { const HomeView({super.key});

@override Widget build(BuildContext context) { return Center( child: ElevatedButton(onPressed: () { Get.toNamed('/main'); }, child: const Text('跳转主界面')) , ); } } dart import 'package:flutter/material.dart'; import 'package:flutter_mvvm_demo/main_controller.dart'; import 'package:get/get.dart';

class MainView extends GetView { const MainView({super.key});

@override Widget build(BuildContext context) { return Obx(() => Row(children: [ Text('${controller.count}'), ElevatedButton(onPressed: () { controller.plus(); }, child: const Text('+')) ])); } } 视图层我们直接继承GetView比较方便,这样可以直接拿到之前注入的controller。使用Obx括起来的内容,当被观察的属性值被修改时,直接更新Obx里面的界面。我们再来看一看怎么定义可以被观察的属性,类似于原生开发中ObservableField&lt;String&gt;这样的。dart import 'package:get/get.dart';

class MainController extends GetxController {

var count = 0.obs;

plus() => count++; } ``` 你没有看错,直接在属性值的地方加一个.obs就可以了。这样count的值一旦被修改,就会通知刷新Obs括起来的界面。

效果演示

mvvmdemo.gif