Flutter管理Flutter应用程序中的状态插件jetpack的使用

Flutter管理Flutter应用程序中的状态插件jetpack的使用

Jetpack for Flutter

Jetpack for Flutter是一组从Android Jetpack中汲取灵感的抽象和工具,旨在帮助管理Flutter应用程序中的状态。

功能

LiveData

作为状态持有者和更改通知器,还允许读取当前值。如果你完全依赖于Stream和响应式编程,可能不需要这个特性。但如果你想通过命令式代码更新状态,这将有所帮助。

EventQueue

用于推送短暂的状态到UI并在处理后清除。对于从ViewModel内部触发吐司/弹出窗口非常有用。

ViewModel

业务逻辑容器,它暴露状态和事件方法给UI,并与应用程序的其余部分通信。

安装

$ flutter pub add jetpack

使用

创建你的ViewModel并使用LiveData公开状态:

import 'package:jetpack/jetpack.dart';

class CounterViewModel extends ViewModel {
  final MutableLiveData<int> _counter = MutableLiveData(0);

  LiveData<int> get counter => _counter;

  void increment() {
    _counter.value++;
  }
}

你可以通过BuildContext访问你的CounterViewModel

@override
Widget build(BuildContext context) {
  final CounterViewModel viewModel = context.viewModel();
}

并且你可以使用LiveDataBuilder消费LiveData

LiveDataBuilder<int>(
  liveData: viewModel.counter,
  builder: (BuildContext buildContext, int count) =>
      Text('$count'),
)

你还可以通过调用ViewModel上的方法传递UI事件:

FloatingActionButton(
  onPressed: viewModel.increment,
  //...
)

在Flutter项目中的一次性设置

创建一个ViewModelFactory为你的应用

class MyAppViewModelFactory extends ViewModelFactory {
  const MyAppViewModelFactory();

  @override
  T create<T extends ViewModel>() {
    if (T == HomeViewModel) {
      return HomeViewModel() as T;
    }
    throw Exception("Unknown ViewModel type");
  }
}

如果你使用依赖注入框架如get_it,你的工厂可以像这样:

class MyAppViewModelFactory extends ViewModelFactory {
  const MyAppViewModelFactory();

  @override
  T create<T extends ViewModel>() {
    return GetIt.I.get<T>();
  }
}

在你的App根部提供ViewModelFactory

void main() {
  const MyAppViewModelFactory viewModelFactory = MyAppViewModelFactory();
  runApp(const MyApp(viewModelFactory: viewModelFactory));
}

class MyApp extends StatelessWidget {
  final ViewModelFactory viewModelFactory;
  const MyApp({super.key, required this.viewModelFactory});

  @override
  Widget build(BuildContext context) {
    return ViewModelFactoryProvider(
        viewModelFactory: viewModelFactory,
        child: MaterialApp(
          title: 'Flutter App',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const HomePage(title: 'Home Page'),
        ));
  }
}

创建一个基础小部件Page来包装所有页面内容与ViewModelScope

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

  Widget buildContent(BuildContext context);

  @override
  Widget build(BuildContext context) {
    return ViewModelScope(builder: buildContent);
  }
}

如果你已经有所有页面的基础类,则按上述方式使用ViewModelScope包装内容。

为什么又一个状态管理库?

这些模式在Android生态系统中已经存在超过5年。即使在采用全新的UI框架——Jetpack Compose之后,它们仍然保持不变。这些抽象由于低耦合灵活性而具有强大的适应能力。

现有解决方案如blocprovider等默认情况下仅限于发出一个状态流,并且需要额外的样板代码来“选择”UI想要反应的状态片段。

有时候,我们希望暴露多个不同但相关联的状态流,这些流以不同的频率变化/发出。直接从ViewModel暴露它们,无需编写选择器等样板代码是非常方便的,没有任何成本。

这使我们能够组织和传播状态,以便按照其在UI中被消耗的方式进行最小化不必要的小部件重建。

你也可以使用FutureStream暴露状态给UI。你的选择

并且不需要为将UI事件传达给ViewModel创建额外的模型。只需直接调用方法即可。

示例代码

以下是一个完整的示例,展示了如何在一个简单的计数器应用中使用Jetpack插件:

import 'package:flutter/material.dart';
import 'package:jetpack/jetpack.dart';

// 创建ViewModel
class CounterViewModel extends ViewModel {
  final MutableLiveData<int> _counter = MutableLiveData(0);

  LiveData<int> get counter => _counter;

  void increment() {
    _counter.value++;
  }
}

// ViewModelFactory
class ExampleViewModelFactory extends ViewModelFactory {
  const ExampleViewModelFactory();

  @override
  T create<T extends ViewModel>() {
    if (T == CounterViewModel) {
      return CounterViewModel() as T;
    }
    throw Exception("Unknown ViewModel type");
  }
}

void main() {
  const ExampleViewModelFactory viewModelFactory = ExampleViewModelFactory();
  runApp(const MyApp(viewModelFactory: viewModelFactory));
}

class MyApp extends StatelessWidget {
  final ViewModelFactory viewModelFactory;
  const MyApp({super.key, required this.viewModelFactory});

  @override
  Widget build(BuildContext context) {
    return ViewModelFactoryProvider(
      viewModelFactory: viewModelFactory,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const HomePage(title: 'Simple Scope Demo'),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key, required this.title});
  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: ViewModelScope(
          builder: (_) => Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Padding(
                padding: EdgeInsets.all(16),
                child: Text(
                    'Here are two counter components in the same scope. They react to clicks on each other because they are talking to the same ViewModel'),
              ),
              const Counter(),
              const Counter(),
              ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).push(MaterialPageRoute(
                        builder: (context) => const MultiScopeDemo()));
                  },
                  child: const Text('NEXT'))
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final CounterViewModel viewModel = context.viewModel<CounterViewModel>();

    return Column(
      children: [
        LiveDataBuilder<int>(
          liveData: viewModel.counter,
          builder: (context, count) => Text('Count: $count'),
        ),
        FloatingActionButton(
          onPressed: viewModel.increment,
          child: const Icon(Icons.add),
        ),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Multi Scope Demo'),
      ),
      body: Center(
        child: ViewModelScope(
          builder: (_) => const Text('This is a new scope.'),
        ),
      ),
    );
  }
}

这段代码展示了一个包含两个计数器组件的应用程序,它们在同一作用域内共享同一个ViewModel,因此会相互响应点击事件。此外,还有一个导航按钮可以跳转到新的作用域页面。


更多关于Flutter管理Flutter应用程序中的状态插件jetpack的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

回到顶部