Flutter依赖注入与状态管理插件args_riverpod的使用

Flutter依赖注入与状态管理插件args_riverpod的使用

args_riverpod 是一个扩展了 args 包并引入了 Riverpod 能力的库。它提供了一些类来帮助在命令行工具中处理依赖注入和状态管理。

Args Riverpod

免责声明: 此包不是官方 Riverpod 包的一部分。

args 包的扩展版本,结合了 Riverpod 的能力。args_riverpod 引入了以下类:

  • ProviderCommandRunner: 提供一个带有根 ProviderContainer(与运行器一起实例化)的 CommandRunner
  • ProviderCommand: 提供一个可以访问根 ProviderContainerCommand

使用方式与 args 包相同,但有以下不同点:

  • 分支命令 可以重写 processArgs 函数以处理它们声明的参数。
  • 叶命令 可以重写 processArgs 函数以处理它们声明的参数,并且必须重写 execute 函数(代替 run 函数)。
  • Provider 命令运行器 提供了一个 setGlobalArgsProcessor,可以注册一个处理器来处理全局参数。

注意: run 已被 execute 替换,这是由于 args 包实现的约束。

根 Provider 容器

在创建 ProviderCommandRunner 时,你可以传递根 ProviderContaineroverridesobservers

ProviderCommandRunner("cli", "A super CLI.", overrides: [], observers: []);

全局选项处理

ProviderCommandRunner 实例化了一个 Riverpod ProviderContainer,该容器会传递给所有命令/子命令和参数处理器。此容器作为 refprocessArgsexecute 函数中可用。

为了声明一个全局选项处理器,可以在 ProviderCommandRunner 上调用 setGlobalArgProcessor

void main(List<String> arguments) {
  ProviderCommandRunner("cli", "A super CLI.")
    ..setGlobalArgProcessor(GlobalOptionParser())
    ..addCommand(BranchCommand())
    ..argParser.addOption('host', help: 'Server host URL')
    ..argParser.addOption('token', help: 'Server access token')
    ..run(arguments);
}

class GlobalOptionParser extends ProviderArgsProcessor {
  [@override](/user/override)
  Future<void> processArgs() async {
    print('GlobalOptionParser.processArgs called');
    ref.read(hostArgProvider.notifier).state = argResults?['host'];
    ref.read(tokenArgProvider.notifier).state = argResults?['token'];
  }
}

分支命令选项处理

具有子命令的命令称为“分支命令”,不能直接执行。它应该调用 addSubcommand(通常从构造函数中调用)来注册子命令。分支命令可以重写 processArgs 来处理相关的参数。

class BranchCommand extends ProviderCommand {
  [@override](/user/override)
  final name = "branch";
  [@override](/user/override)
  final description = "branch command";

  BranchCommand() {
    argParser.addOption('input', help: 'input file path');
    addSubcommand(LeafCommand());
  }

  [@override](/user/override)
  void processArgs() {
    print('BranchCommand.processArgs called');
    ref.read(inputArgProvider.notifier).state = argResults?['input'];
  }
}

叶命令选项处理

如果命令没有子命令并且打算运行,则称为“叶命令”。叶命令必须重写 execute 并且可以选择重写 processArgs

class LeafCommand extends ProviderCommand {
  [@override](/user/override)
  final name = "leaf";
  [@override](/user/override)
  final description = "leaf command";

  LeafCommand() {
    argParser.addOption('output', help: 'output file path');
  }

  [@override](/user/override)
  void processArgs() {
    print('LeafCommand.processArgs called');
    ref.read(outputArgProvider.notifier).state = argResults?['output'];
  }

  [@override](/user/override)
  void execute() {
    print('LeafCommand.execute called');
    ref.read(fooServiceProvider).doSomething();
  }
}

完整使用示例

完整的使用示例可参见 这里

你可以按如下方式运行它:

git clone git@gitlab.com:noan-public/dart-and-flutter/args_riverpod.git && cd args_riverpod

dart "$PWD/args_riverpod/example/args_riverpod_example.dart" --host server.com  --token my_token branch --input path/in-file.txt leaf --output path/out-file.txt

示例代码

import 'dart:io';

import 'package:args_riverpod/args_riverpod.dart';

import 'package:args/command_runner.dart';
import 'package:riverpod/riverpod.dart';

// CLI sample call:
// dart "./args_riverpod/example/args_riverpod_example.dart" --host server.com  --token my_token branch --input path/in-file.txt leaf --output path/out-file.txt

// CLI main & global options handling
//
void main(List<String> arguments) {
  ProviderCommandRunner("cli", "A super CLI.")
    ..setGlobalArgProcessor(GlobalOptionParser())
    ..addCommand(BranchCommand())
    ..argParser.addOption('host', help: 'Server host URL')
    ..argParser.addOption('token', help: 'Server access token')
    ..run(arguments);
}

class GlobalOptionParser extends ProviderArgsProcessor {
  [@override](/user/override)
  Future<void> processArgs() async {
    print('GlobalOptionParser.processArgs called');
    ref.read(hostArgProvider.notifier).state = argResults?['host'];
    ref.read(tokenArgProvider.notifier).state = argResults?['token'];
  }
}

// Branch command & related options handling
//
class BranchCommand extends ProviderCommand {
  [@override](/user/override)
  final name = "branch";
  [@override](/user/override)
  final description = "branch command";

  BranchCommand() {
    argParser.addOption('input', help: 'input file path');
    addSubcommand(LeafCommand());
  }

  [@override](/user/override)
  void processArgs() {
    print('BranchCommand.processArgs called');
    ref.read(inputArgProvider.notifier).state = argResults?['input'];
  }
}

// Leaf command, related options handling & command execution
//
class LeafCommand extends ProviderCommand {
  [@override](/user/override)
  final name = "leaf";
  [@override](/user/override)
  final description = "leaf command";

  LeafCommand() {
    argParser.addOption('output', help: 'output file path');
  }

  [@override](/user/override)
  void processArgs() {
    print('LeafCommand.processArgs called');
    ref.read(outputArgProvider.notifier).state = argResults?['output'];
  }

  [@override](/user/override)
  void execute() {
    print('LeafCommand.execute called');
    ref.read(fooServiceProvider).doSomething();
  }
}

// Options providers
//
final hostArgProvider = StateProvider<String?>((ref) => null);
final tokenArgProvider = StateProvider<String?>((ref) => null);
final inputArgProvider = StateProvider<String?>((ref) => null);
final outputArgProvider = StateProvider<String?>((ref) => null);
final fooServiceProvider = Provider<FooService>((ref) {
  return FooService(
    input: ref.watch(inputArgProvider),
    output: ref.watch(outputArgProvider),
  );
});

// Service
//
class FooService {
  FooService({
    required this.input,
    required this.output,
  });
  final String? input;
  final String? output;

  Future<void> doSomething() async {
    print('FooService.doSomething called');
    // Do the job
  }
}

更多关于Flutter依赖注入与状态管理插件args_riverpod的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter依赖注入与状态管理插件args_riverpod的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用args_riverpod进行依赖注入与状态管理的示例代码。args_riverpodriverpod状态管理库的一个扩展,它主要用于处理路由参数。不过,我们通常使用riverpodflutter_riverpod来管理全局状态,并通过args_riverpod来简化从路由参数中获取依赖的操作。

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

dependencies:
  flutter:
    sdk: flutter
  flutter_hooks: ^0.18.0  # 如果你打算使用Hooks API
  hooks_riverpod: ^1.0.0  # Riverpod核心库,包含provider和hooks支持
  flutter_riverpod: ^1.0.0  # Riverpod的Flutter绑定
  args_riverpod: ^0.1.0  # 用于处理路由参数的扩展

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

设置Riverpod

在你的应用入口文件(通常是main.dart)中,设置Riverpod:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:args_riverpod/args_riverpod.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: '/',
      routes: {
        '/': (context) => HomeScreen(),
        '/detail': (context) => DetailScreen(),
      },
      onGenerateRoute: (settings) => generateRouteWithArgs(context, settings),
    );
  }
}

Route<dynamic> generateRouteWithArgs(BuildContext context, RouteSettings settings) {
  // 这里是args_riverpod发挥作用的地方,它会根据路由参数创建ProviderScope
  return context.read(routerProvider.notifier).generateRoute(settings);
}

定义路由Provider

接下来,定义一个Provider来处理路由:

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:args_riverpod/args_riverpod.dart';

final routerProvider = RouterProvider<Map<String, dynamic>>(
  (ref) {
    // 这里可以定义你的路由生成逻辑,这里我们简单地返回null
    return null;
  },
);

使用依赖注入和状态管理

现在,让我们在屏幕组件中使用这些Provider。

HomeScreen

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

class HomeScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pushNamed(context, '/detail', arguments: {'id': 123});
          },
          child: Text('Go to Detail'),
        ),
      ),
    );
  }
}

DetailScreen

在DetailScreen中,我们将使用args_riverpod提供的功能来获取路由参数:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:args_riverpod/args_riverpod.dart';

class DetailScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final args = ref.watch(routeArgsProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('Detail Screen'),
      ),
      body: Center(
        child: Text('Detail ID: ${args?['id']}'),
      ),
    );
  }
}

在这个例子中,routeArgsProvider是由args_riverpod自动生成的,它允许你直接从Provider中获取路由参数。

总结

以上代码展示了如何在Flutter项目中使用args_riverpodriverpod进行依赖注入和状态管理。通过这种方式,你可以轻松地在不同屏幕之间传递数据,同时保持代码的整洁和可维护性。

回到顶部