Flutter MVVM架构插件fl_mvvm的使用

Flutter MVVM架构插件fl_mvvm的使用

特性 🎯

  • MVVM架构: 利用Model-View-ViewModel (MVVM) 架构模式确保清晰的职责分离,增强代码组织和可维护性。
  • FlView类: 通过扩展FlView类来渲染视图。可以重写构建不同状态的方法,包括数据状态、空状态、错误状态和加载状态。根据应用程序的当前状态自定义视图。
  • FlViewModel类: 通过扩展FlViewModel类来处理视图的状态。使用泛型类型和方法将自定义视图链接到视图模型。实现返回FlViewModel对象的createViewModel()方法。
  • 容器小部件: 指定一个容器小部件来包装整个视图。这允许用户根据需要自定义布局和外观。

开始使用 🚀

在使用该包之前,请确保将你的应用包装在一个ProviderScope中:

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

这可以确保在整个Flutter应用中可用必要的提供者和状态管理功能。

使用 🔨

视图模型

创建视图模型 🧩

要创建一个视图模型,只需创建一个继承自FlViewModel类的类:

class MyViewModel extends FlViewModel {}

状态化的视图模型 💾

每个视图模型的状态都可以通过getter value访问。

MyViewModel viewModel = MyViewModel();
dynamic state = viewModel.value;

你可以设置任何你想要的类型,使用泛型类型:

MyViewModel viewModel = MyViewModel<String>();
String state = viewModel.value;

注意:FlViewModel<T>的实际状态由AsyncValue<T>表示。例如,在上面的例子中,状态是一个AsyncValue<String>。然而,当访问getter value时,它返回的是AsyncValue的值而不是整个状态实例。

初始化视图模型的状态 🏁

当创建一个FlViewModel实例时,它会使用build()方法的返回值来构建其状态。默认情况下,FlViewModelbuild()方法返回null。

你可以覆盖它以返回视图模型的初始状态:

class UsersListViewModel extends FlViewModel<List<User>> {
  [@override](/user/override)
  FutureOr<List<User>?> build() {
    return UsersRepository.getAllUsers(); // 在这个例子中,初始状态将是从UsersRepository获取的所有用户
  }
}

onInit 🌱

onInit()是一个有用的回调方法,可以在视图初始化后立即运行一些初始化代码。

class MyViewModel extends FlViewModel {
  [@override](/user/override)
  void onInit() {
    // 当关联的视图被初始化时,这段代码只会运行一次
  }
}

注意:此方法只执行一次。

onDispose ♻️

onDispose()是一个有用的回调方法,可以在关联的视图被释放前运行一些清理代码。

class MyViewModel extends FlViewModel {
  [@override](/user/override)
  void onDispose() {
    // 当关联的视图正在被释放时,这段代码会被执行
    // 你可以在这里释放资源、处理可丢弃的状态、停止计时器等...
  }
}

刷新和刷新状态 🔄

refresh()方法会触发视图的重新绘制。另一方面,refreshState()方法通过调用build()方法重建视图模型的状态。

加载状态 ⏳

要显示加载状态,可以使用内置的setLoading()方法:

class MyViewModel extends FlViewModel {

  void loadUsers() async {
    setLoading(); // 这将导致视图呈现加载UI状态
    // 或者,你可以使用:
    // state = const AsyncValue.loading();
    // 或
    // state = const AsyncLoading();

    /*...继续加载用户...*/
  }
}

通过调用setLoading(),你会触发视图呈现加载UI状态,表示数据正在被获取或处理。或者,你可以直接给状态赋值AsyncValue.loading()AsyncLoading()来达到相同的效果。

数据状态 📊

要显示数据状态,可以使用内置的setData()方法:

class CounterViewModel extends FlViewModel<int> {
  [@override](/user/override)
  FutureOr<int?> build() {
    return 0;
  }

  void incrementCounter() {
    int currentValue = value ?? 0;
    int newValue = currentValue + 1;
    setData(newValue); // 这将导致视图呈现新的数据

    // 或者你可以使用:
    // state = AsyncValue.data(newValue);
    // 或
    // state = AsyncData(newValue);

  }
}

通过调用setData(newValue),你更新了视图模型的状态,这会触发视图呈现更新的数据状态。或者,你可以直接给状态赋值AsyncValue.data(newValue)AsyncData(newValue)来达到相同的效果。

未来数据 ⏭

如果你需要从Future中设置数据状态,你可以使用内置的setFutureData()方法,该方法接受你的Future作为参数:

// 假设我们有一个这样定义的产品仓库:
class ProductsRepository {
  static Future<List<Product>> getAllProducts() async {
    // 调用API
    List<Product> products = await Api().getProducts();
    return products;
  }
}

class ProductsViewModel extends FlViewModel<List<Product>> {
  void loadProducts() {
    setFutureData(
      ProductsRepository.getAllProducts(),
      withLoading: false,
    ); // 这将把状态设置为加载状态,异步加载产品列表,更新状态,并触发视图构建数据状态
  }
}

setFutureData()方法会在执行Future之前将状态设置为加载状态。如果你不想在获取数据时改变状态为加载状态,你可以将withLoading参数设置为false

void loadProducts() {
  setFutureData(
    ProductsRepository.getAllProducts(),
    withLoading: false,
  ); // 这将异步加载产品列表,更新状态,并触发视图构建数据状态
}

如果Future抛出错误,setFutureData()将会捕获该错误并将其状态更改为错误状态。

空状态 💨

状态被认为是空的,当没有值(state.value == null)或值是一个空的IterableMap

如果满足指定条件,它会触发视图呈现空UI状态。

你可以使用内置的setEmpty()方法来清除当前状态:

class MyViewModel extends FlViewModel<List<int>> {
  void clear() {
    setEmpty(); // 这将导致视图呈现空UI状态
  }
}

通过调用setEmpty(),你清除了视图模型的当前状态,这会触发视图呈现空UI状态。这在你想表示没有数据可显示时非常有用。

错误状态 ❗

要显示错误状态,可以使用内置的setError()方法:

class CounterViewModel extends FlViewModel<int> {
  [@override](/user/override)
  FutureOr<int?> build() {
    return 0;
  }

  void incrementCounter() {
    int currentValue = value ?? 0;
    int newValue = currentValue + 1;
    if (newValue > 10) {
      setError(Exception("You have reached the limits 👮"));

      // 或者你可以使用:
      // state = AsyncValue.error(Exception("You have reached the limits 👮"), StackTrace.current);
      // 或
      // state = AsyncError(Exception("You have reached the limits 👮"), StackTrace.current);
    }
    state = AsyncValue.data(newValue);
  }
}

通过调用setError(Exception("You have reached the limits 👮")),你将状态设置为错误状态,这会触发视图呈现相应的错误处理UI。

你也可以在catch块中设置错误状态:

class CounterViewModel extends FlViewModel<int> {
  [@override](/user/override)
  FutureOr<int?> build() {
    return 0;
  }

  void incrementCounter() {
    try {
      int currentValue = value ?? 0;
      int newValue = currentValue + 1;
      if (newValue > 10) {
        throw Exception("You have reached the limits 👮");
      }
      state = AsyncValue.data(newValue);
    } catch (error, stack) {
      setError(error, stack);
    }
  }
}

catch块中,你捕获错误并使用setError(error, stack)设置它,这会触发视图呈现错误UI状态。

视图 👀

创建视图 🖌️

要创建一个视图,只需创建一个新的继承自FlView类的类:

class MyView extends FlView {}

视图是一个小部件,可以像其他任何小部件一样成为你的Flutter布局的一部分:

class MyView extends FlView {
  const MyView({super.key}); // 建议为你的视图创建一个const构造函数

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Column(
        children: [
          Text("Hello world 🌍, what a wonderful view 👀!"),
          SizedBox(height: 24),
          MyView() // 渲染你的视图
        ],
      ),
    );
  }
}

将视图与视图模型链接 🔗

当你创建一个视图时,你需要重写createViewModel()方法,并返回一个FlViewModel类型的对象。

class MyViewModel extends FlViewModel {}

class MyView extends FlView {

  [@override](/user/override)
  FlViewModel createViewModel() {
    return MyViewModel();
  }

}

如果你想让你的视图链接到特定类型的视图模型,你可以使用泛型类型:

class MyViewModel extends FlViewModel {}

class MyView extends FlView<MyViewModel> {

  [@override](/user/override)
  MyViewModel createViewModel() {
    return MyViewModel();
  }

}

注意:返回的视图模型实例将在视图的生命周期内链接到该视图,并且不能更改。

使用参数初始化视图模型 🎛️

当创建视图模型时,你可以使用createViewModel()方法传递参数。

想象一下,你有一个视图显示用户列表,并且你想传递一个过滤器到视图中,只显示来自特定城市用户。

class UsersViewModel extends FlViewModel<List<User>> {
  final String cityFilter;

  UsersViewModel(this.cityFilter);

  [@override](/user/override)
  FutureOr<List<User>> build() {
    return UsersRepository.getUsers(city: cityFilter); // 当视图模型构建其状态时,它将使用cityFilter的值
  }
}

class UsersView extends FlView<UsersViewModel> {
  final String cityFilter;

  const UsersView({super.key, required this.cityFilter});

  [@override](/user/override)
  UsersViewModel createViewModel() {
    return UsersViewModel(cityFilter); // 创建一个UsersViewModel实例并传递cityFilter的值
  }
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Column(
        children: [
          Text("Hello world 🌍, what a wonderful view 👀!"),
          SizedBox(height: 24),
          UsersView(cityFilter: "Paris") // 向视图传递参数的方式与向任何自定义小部件传递参数相同
        ],
      ),
    );
  }
}

视图的UI状态 🎨

视图可以处理四种UI状态:

  1. 数据
  2. 加载
  3. 错误

默认情况下,视图初始化时处于数据UI状态。每种UI状态都由一个返回Widget的方法处理,你可以通过重写这些方法来自定义它们。

FlView类为所有状态提供了默认的Widget,除了数据状态,当你扩展FlView类时,你必须实现数据状态:

class Model {
  final String data;

  const Model(this.data);
}

class MyViewModel extends FlViewModel<Model> {
  [@override](/user/override)
  FutureOr<Model> build() {
    return const Model("abc");
  }
}

class MyView extends FlView<MyViewModel> {
  const MyView({super.key});

  [@override](/user/override)
  Widget buildDataState(BuildContext context, MyViewModel viewModel) {
    return Text("Hello 👋 I'm a friendly view, checkout my data => ${viewModel.value?.data}");
    // 输出: Hello 👋 I'm a friendly view, checkout my data => abc
  }

  [@override](/user/override)
  MyViewModel createViewModel() {
    return MyViewModel();
  }
}

你可以自由地重写其余的UI状态以满足你的需求:

class MyView extends FlView<MyViewModel> {
  const MyView({super.key});

  [@override](/user/override)
  Widget buildDataState(BuildContext context, MyViewModel viewModel) {
    return Text("Hello 👋 I'm a friendly view, checkout my data => ${viewModel.value?.data}");
    // 输出: Hello 👋 I'm a friendly view, checkout my data => abc
  }

  [@override](/user/override)
  Widget buildErrorState(BuildContext context, MyViewModel viewModel, String error) {
    return Text("🛑 $error");
  }

  [@override](/user/override)
  Widget buildEmptyState(BuildContext context, MyViewModel viewModel) {
    return Text("🍃 There's no data");
  }

  [@override](/user/override)
  Widget buildLoadingState(BuildContext context, MyViewModel viewModel) {
    return Text("⏰ Please wait while the view is loading data...");
  }

  [@override](/user/override)
  MyViewModel createViewModel() {
    return MyViewModel();
  }
}

使用容器包裹视图 📦

有时,你可能需要将视图包裹在一个容器内,你可以通过将整个视图小部件包裹在一个作为容器的其他小部件中来实现传统方式:

Container(child: MyView())

或者,你可以覆盖buildContainer()方法,使用一个内部容器来包裹渲染的UI状态,同时可以访问FlViewModel实例:

class Model {
  final String data;

  const Model(this.data);
}

class MyViewModel extends FlViewModel<Model> {
  [@override](/user/override)
  FutureOr<Model> build() {
    return const Model("abc");
  }
}

class MyView extends FlView<MyViewModel> {
  const MyView({super.key});

  [@override](/user/override)
  Widget? buildContainer(BuildContext context, Widget child, MyViewModel viewModel) {
    // 使用Scaffold作为内部视图包装器
    return Scaffold(
      // 注意,视图模型可以从容器中访问,这使得你可以在容器中使用状态
      appBar: AppBar(
        title: Text("The data inside the model is ${viewModel.value?.data}"),
      ),
      body: child,
    );
  }

  [@override](/user/override)
  Widget buildDataState(BuildContext context, MyViewModel viewModel) {
    return Text("Hello 👋 I'm a friendly view, checkout my data => ${viewModel.value?.data}");
    // 输出: Hello 👋 I'm a friendly view, checkout my data => abc
  }

  [@override](/user/override)
  MyViewModel createViewModel() {
    return MyViewModel();
  }
}

更多关于Flutter MVVM架构插件fl_mvvm的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter MVVM架构插件fl_mvvm的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用fl_mvvm插件来实现MVVM架构的示例代码。fl_mvvm是一个帮助在Flutter中实现MVVM架构模式的插件,但需要注意的是,实际使用中你可能需要根据具体项目需求进行调整和扩展。

首先,确保你已经在pubspec.yaml文件中添加了fl_mvvm依赖:

dependencies:
  flutter:
    sdk: flutter
  fl_mvvm: ^最新版本号  # 请替换为实际的最新版本号

然后运行flutter pub get来安装依赖。

接下来,我们创建一个简单的Flutter项目,并使用fl_mvvm来实现MVVM架构。

1. 创建ViewModel

ViewModel负责处理业务逻辑和数据绑定。

import 'package:fl_mvvm/fl_mvvm.dart';

class CounterViewModel extends BaseViewModel {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();  // 通知视图更新
  }
}

2. 创建View(Widget)

View负责UI展示,并与ViewModel进行绑定。

import 'package:flutter/material.dart';
import 'package:fl_mvvm/fl_mvvm.dart';
import 'counter_view_model.dart';  // 导入ViewModel

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ViewModelProvider<CounterViewModel>.withConsumer(
      viewModel: CounterViewModel(),
      builder: (context, viewModel, child) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Flutter MVVM Example'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '${viewModel.count}',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              viewModel.increment();
            },
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      },
    );
  }
}

3. 主函数入口

main.dart中设置MaterialApp并导航到我们的CounterPage

import 'package:flutter/material.dart';
import 'counter_page.dart';  // 导入我们的页面

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter MVVM Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: CounterPage(),
    );
  }
}

总结

以上代码展示了一个简单的Flutter MVVM架构实现,其中包括了ViewModel来处理业务逻辑,并通过ViewModelProvider与View进行绑定。每当ViewModel中的数据发生变化时,视图会自动更新。

这个示例只是一个入门级的实现,实际项目中可能还需要处理更多复杂的场景,比如网络请求、状态管理等。你可以根据fl_mvvm的文档和API进一步扩展和优化你的项目。

回到顶部