猫哥课堂 ducafecat.com
开通 VIP 会员, 观看所有视频、附件、猫哥密友、猫哥 VIP 群

Flutter signal get_it 状态管理快速指南

视频

https://youtu.be/xFcsaim1aPA

https://www.bilibili.com/video/BV14j421R79L/

前言

原文 https://ducafecat.com/blog/flutter-signal-state-management-get-it-quickstart

学习如何利用 Flutter signal 状态管理插件实现懒加载、全平台支持以及小颗粒度局部刷新,配合 get_it 深入掌握移动应用开发技巧。

代码

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_signals

参考

https://dartsignals.dev/

https://pub.dev/packages/signals

https://pub.dev/packages/get_it

例子: 计数器

安装 signals package

pubspec.yaml

dependencies: flutter: sdk: flutter ... dio: ^5.4.1 get_it: ^7.6.7 signals_flutter: ^4.5.1

编写计数器

lib/pages/counter.dart

定义响应对象

final counter = signal(0);

点击后更新数据

Widget _buildView(context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ // 按钮 ElevatedButton( onPressed: () { counter.value++; }, child: const Text('加法计数'), ), // 显示 Text(counter.watch(context).toString()), ], ), ); }

通过 counter.value++; 访问修改响应变量

通过 counter.watch( 方式监听变化

Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('计数器'), ), body: _buildView(context), ); }

菜单页

lib/pages/index.dart

class IndexPage extends StatelessWidget { const IndexPage({super.key}); Widget _buildView(context) { return Column( children: [ // 计数器 ListTile( title: const Text("计数器"), onTap: () { Navigator.push(context, MaterialPageRoute(builder: (context) { return const CounterPage(); })); }, ), ], ); } Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Flutter signals 状态管理'), ), body: _buildView(context), ); } }

启动

lib/main.dart

class MyApp extends StatelessWidget { const MyApp({super.key}); Widget build(BuildContext context) { return MaterialApp( ... home: const IndexPage(), ); } }

例子: 拉取数据列表

定义商品 entity

lib/entity/product.dart

// To parse this JSON data, do // // final productModel = productModelFromJson(jsonString); import 'dart:convert'; ProductEntrty productModelFromJson(String str) => ProductEntrty.fromJson(json.decode(str)); String productModelToJson(ProductEntrty data) => json.encode(data.toJson()); class ProductEntrty { int? id; String? name; String? slug; String? permalink; DateTime? dateCreated; DateTime? dateCreatedGmt; DateTime? dateModified; DateTime? dateModifiedGmt; String? type; String? status; bool? featured; String? catalogVisibility; String? description; String? shortDescription; String? sku; String? price; String? regularPrice; String? salePrice; dynamic dateOnSaleFrom; dynamic dateOnSaleFromGmt; dynamic dateOnSaleTo; dynamic dateOnSaleToGmt; bool? onSale; bool? purchasable; int? totalSales; bool? virtual; bool? downloadable; List<dynamic>? downloads; int? downloadLimit; int? downloadExpiry; String? externalUrl; String? buttonText; String? taxStatus; String? taxClass; bool? manageStock; dynamic stockQuantity; String? backorders; bool? backordersAllowed; bool? backordered; dynamic lowStockAmount; bool? soldIndividually; String? weight; Dimensions? dimensions; bool? shippingRequired; bool? shippingTaxable; String? shippingClass; int? shippingClassId; bool? reviewsAllowed; String? averageRating; int? ratingCount; List<dynamic>? upsellIds; List<dynamic>? crossSellIds; int? parentId; String? purchaseNote; List<Category>? categories; List<Category>? tags; List<Image>? images; List<Attribute>? attributes; List<dynamic>? defaultAttributes; List<dynamic>? variations; List<dynamic>? groupedProducts; int? menuOrder; String? priceHtml; List<int>? relatedIds; List<MetaDatum>? metaData; String? stockStatus; bool? hasOptions; Links? links; ProductEntrty({ this.id, this.name, this.slug, this.permalink, this.dateCreated, this.dateCreatedGmt, this.dateModified, this.dateModifiedGmt, this.type, this.status, this.featured, this.catalogVisibility, this.description, this.shortDescription, this.sku, this.price, this.regularPrice, this.salePrice, this.dateOnSaleFrom, this.dateOnSaleFromGmt, this.dateOnSaleTo, this.dateOnSaleToGmt, this.onSale, this.purchasable, this.totalSales, this.virtual, this.downloadable, this.downloads, this.downloadLimit, this.downloadExpiry, this.externalUrl, this.buttonText, this.taxStatus, this.taxClass, this.manageStock, this.stockQuantity, this.backorders, this.backordersAllowed, this.backordered, this.lowStockAmount, this.soldIndividually, this.weight, this.dimensions, this.shippingRequired, this.shippingTaxable, this.shippingClass, this.shippingClassId, this.reviewsAllowed, this.averageRating, this.ratingCount, this.upsellIds, this.crossSellIds, this.parentId, this.purchaseNote, this.categories, this.tags, this.images, this.attributes, this.defaultAttributes, this.variations, this.groupedProducts, this.menuOrder, this.priceHtml, this.relatedIds, this.metaData, this.stockStatus, this.hasOptions, this.links, }); factory ProductEntrty.fromJson(Map<String, dynamic> json) => ProductEntrty( id: json["id"], name: json["name"], slug: json["slug"], permalink: json["permalink"], dateCreated: json["date_created"] == null ? null : DateTime.parse(json["date_created"]), dateCreatedGmt: json["date_created_gmt"] == null ? null : DateTime.parse(json["date_created_gmt"]), dateModified: json["date_modified"] == null ? null : DateTime.parse(json["date_modified"]), dateModifiedGmt: json["date_modified_gmt"] == null ? null : DateTime.parse(json["date_modified_gmt"]), type: json["type"], status: json["status"], featured: json["featured"], catalogVisibility: json["catalog_visibility"], description: json["description"], shortDescription: json["short_description"], sku: json["sku"], price: json["price"], regularPrice: json["regular_price"], salePrice: json["sale_price"], dateOnSaleFrom: json["date_on_sale_from"], dateOnSaleFromGmt: json["date_on_sale_from_gmt"], dateOnSaleTo: json["date_on_sale_to"], dateOnSaleToGmt: json["date_on_sale_to_gmt"], onSale: json["on_sale"], purchasable: json["purchasable"], totalSales: json["total_sales"], virtual: json["virtual"], downloadable: json["downloadable"], downloads: json["downloads"] == null ? [] : List<dynamic>.from(json["downloads"]!.map((x) => x)), downloadLimit: json["download_limit"], downloadExpiry: json["download_expiry"], externalUrl: json["external_url"], buttonText: json["button_text"], taxStatus: json["tax_status"], taxClass: json["tax_class"], manageStock: json["manage_stock"], stockQuantity: json["stock_quantity"], backorders: json["backorders"], backordersAllowed: json["backorders_allowed"], backordered: json["backordered"], lowStockAmount: json["low_stock_amount"], soldIndividually: json["sold_individually"], weight: json["weight"], dimensions: json["dimensions"] == null ? null : Dimensions.fromJson(json["dimensions"]), shippingRequired: json["shipping_required"], shippingTaxable: json["shipping_taxable"], shippingClass: json["shipping_class"], shippingClassId: json["shipping_class_id"], reviewsAllowed: json["reviews_allowed"], averageRating: json["average_rating"], ratingCount: json["rating_count"], upsellIds: json["upsell_ids"] == null ? [] : List<dynamic>.from(json["upsell_ids"]!.map((x) => x)), crossSellIds: json["cross_sell_ids"] == null ? [] : List<dynamic>.from(json["cross_sell_ids"]!.map((x) => x)), parentId: json["parent_id"], purchaseNote: json["purchase_note"], categories: json["categories"] == null ? [] : List<Category>.from( json["categories"]!.map((x) => Category.fromJson(x))), tags: json["tags"] == null ? [] : List<Category>.from( json["tags"]!.map((x) => Category.fromJson(x))), images: json["images"] == null ? [] : List<Image>.from(json["images"]!.map((x) => Image.fromJson(x))), attributes: json["attributes"] == null ? [] : List<Attribute>.from( json["attributes"]!.map((x) => Attribute.fromJson(x))), defaultAttributes: json["default_attributes"] == null ? [] : List<dynamic>.from(json["default_attributes"]!.map((x) => x)), variations: json["variations"] == null ? [] : List<dynamic>.from(json["variations"]!.map((x) => x)), groupedProducts: json["grouped_products"] == null ? [] : List<dynamic>.from(json["grouped_products"]!.map((x) => x)), menuOrder: json["menu_order"], priceHtml: json["price_html"], relatedIds: json["related_ids"] == null ? [] : List<int>.from(json["related_ids"]!.map((x) => x)), metaData: json["meta_data"] == null ? [] : List<MetaDatum>.from( json["meta_data"]!.map((x) => MetaDatum.fromJson(x))), stockStatus: json["stock_status"], hasOptions: json["has_options"], links: json["_links"] == null ? null : Links.fromJson(json["_links"]), ); Map<String, dynamic> toJson() => { "id": id, "name": name, "slug": slug, "permalink": permalink, "date_created": dateCreated?.toIso8601String(), "date_created_gmt": dateCreatedGmt?.toIso8601String(), "date_modified": dateModified?.toIso8601String(), "date_modified_gmt": dateModifiedGmt?.toIso8601String(), "type": type, "status": status, "featured": featured, "catalog_visibility": catalogVisibility, "description": description, "short_description": shortDescription, "sku": sku, "price": price, "regular_price": regularPrice, "sale_price": salePrice, "date_on_sale_from": dateOnSaleFrom, "date_on_sale_from_gmt": dateOnSaleFromGmt, "date_on_sale_to": dateOnSaleTo, "date_on_sale_to_gmt": dateOnSaleToGmt, "on_sale": onSale, "purchasable": purchasable, "total_sales": totalSales, "virtual": virtual, "downloadable": downloadable, "downloads": downloads == null ? [] : List<dynamic>.from(downloads!.map((x) => x)), "download_limit": downloadLimit, "download_expiry": downloadExpiry, "external_url": externalUrl, "button_text": buttonText, "tax_status": taxStatus, "tax_class": taxClass, "manage_stock": manageStock, "stock_quantity": stockQuantity, "backorders": backorders, "backorders_allowed": backordersAllowed, "backordered": backordered, "low_stock_amount": lowStockAmount, "sold_individually": soldIndividually, "weight": weight, "dimensions": dimensions?.toJson(), "shipping_required": shippingRequired, "shipping_taxable": shippingTaxable, "shipping_class": shippingClass, "shipping_class_id": shippingClassId, "reviews_allowed": reviewsAllowed, "average_rating": averageRating, "rating_count": ratingCount, "upsell_ids": upsellIds == null ? [] : List<dynamic>.from(upsellIds!.map((x) => x)), "cross_sell_ids": crossSellIds == null ? [] : List<dynamic>.from(crossSellIds!.map((x) => x)), "parent_id": parentId, "purchase_note": purchaseNote, "categories": categories == null ? [] : List<dynamic>.from(categories!.map((x) => x.toJson())), "tags": tags == null ? [] : List<dynamic>.from(tags!.map((x) => x.toJson())), "images": images == null ? [] : List<dynamic>.from(images!.map((x) => x.toJson())), "attributes": attributes == null ? [] : List<dynamic>.from(attributes!.map((x) => x.toJson())), "default_attributes": defaultAttributes == null ? [] : List<dynamic>.from(defaultAttributes!.map((x) => x)), "variations": variations == null ? [] : List<dynamic>.from(variations!.map((x) => x)), "grouped_products": groupedProducts == null ? [] : List<dynamic>.from(groupedProducts!.map((x) => x)), "menu_order": menuOrder, "price_html": priceHtml, "related_ids": relatedIds == null ? [] : List<dynamic>.from(relatedIds!.map((x) => x)), "meta_data": metaData == null ? [] : List<dynamic>.from(metaData!.map((x) => x.toJson())), "stock_status": stockStatus, "has_options": hasOptions, "_links": links?.toJson(), }; } class Attribute { int? id; String? name; int? position; bool? visible; bool? variation; List<String>? options; Attribute({ this.id, this.name, this.position, this.visible, this.variation, this.options, }); factory Attribute.fromJson(Map<String, dynamic> json) => Attribute( id: json["id"], name: json["name"], position: json["position"], visible: json["visible"], variation: json["variation"], options: json["options"] == null ? [] : List<String>.from(json["options"]!.map((x) => x)), ); Map<String, dynamic> toJson() => { "id": id, "name": name, "position": position, "visible": visible, "variation": variation, "options": options == null ? [] : List<dynamic>.from(options!.map((x) => x)), }; } class Category { int? id; String? name; String? slug; Category({ this.id, this.name, this.slug, }); factory Category.fromJson(Map<String, dynamic> json) => Category( id: json["id"], name: json["name"], slug: json["slug"], ); Map<String, dynamic> toJson() => { "id": id, "name": name, "slug": slug, }; } class Dimensions { String? length; String? width; String? height; Dimensions({ this.length, this.width, this.height, }); factory Dimensions.fromJson(Map<String, dynamic> json) => Dimensions( length: json["length"], width: json["width"], height: json["height"], ); Map<String, dynamic> toJson() => { "length": length, "width": width, "height": height, }; } class Image { int? id; DateTime? dateCreated; DateTime? dateCreatedGmt; DateTime? dateModified; DateTime? dateModifiedGmt; String? src; String? name; String? alt; Image({ this.id, this.dateCreated, this.dateCreatedGmt, this.dateModified, this.dateModifiedGmt, this.src, this.name, this.alt, }); factory Image.fromJson(Map<String, dynamic> json) => Image( id: json["id"], dateCreated: json["date_created"] == null ? null : DateTime.parse(json["date_created"]), dateCreatedGmt: json["date_created_gmt"] == null ? null : DateTime.parse(json["date_created_gmt"]), dateModified: json["date_modified"] == null ? null : DateTime.parse(json["date_modified"]), dateModifiedGmt: json["date_modified_gmt"] == null ? null : DateTime.parse(json["date_modified_gmt"]), src: json["src"], name: json["name"], alt: json["alt"], ); Map<String, dynamic> toJson() => { "id": id, "date_created": dateCreated?.toIso8601String(), "date_created_gmt": dateCreatedGmt?.toIso8601String(), "date_modified": dateModified?.toIso8601String(), "date_modified_gmt": dateModifiedGmt?.toIso8601String(), "src": src, "name": name, "alt": alt, }; } class Links { List<Collection>? self; List<Collection>? collection; Links({ this.self, this.collection, }); factory Links.fromJson(Map<String, dynamic> json) => Links( self: json["self"] == null ? [] : List<Collection>.from( json["self"]!.map((x) => Collection.fromJson(x))), collection: json["collection"] == null ? [] : List<Collection>.from( json["collection"]!.map((x) => Collection.fromJson(x))), ); Map<String, dynamic> toJson() => { "self": self == null ? [] : List<dynamic>.from(self!.map((x) => x.toJson())), "collection": collection == null ? [] : List<dynamic>.from(collection!.map((x) => x.toJson())), }; } class Collection { String? href; Collection({ this.href, }); factory Collection.fromJson(Map<String, dynamic> json) => Collection( href: json["href"], ); Map<String, dynamic> toJson() => { "href": href, }; } class MetaDatum { int? id; String? key; String? value; MetaDatum({ this.id, this.key, this.value, }); factory MetaDatum.fromJson(Map<String, dynamic> json) => MetaDatum( id: json["id"], key: json["key"], value: json["value"], ); Map<String, dynamic> toJson() => { "id": id, "key": key, "value": value, }; }

商品列表页

lib/pages/products.dart

商品列表

class _ProductsPageState extends State<ProductsPage> { // 商品列表 final items = listSignal<ProductEntrty>([]);

拉取数据

// 拉取数据 Future fetchData() async { String url = "https://wpapi.ducafecat.tech/products"; Response response = await Dio().get(url); List<ProductEntrty> list = []; for (var item in response.data) { list.add(ProductEntrty.fromJson(item)); } items.value = list; }

初始

void initState() { super.initState(); fetchData(); }

列表

Widget _buildView() { return ListView.builder( itemCount: items.value.length, itemBuilder: (context, index) { return ListTile( title: Text(items.value[index].name ?? ""), subtitle: Text(items.value[index].description ?? ""), ); }, ); }
Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('拉取数据'), ), body: Watch.builder( builder: (context) => _buildView(), ), ); }

首页入口

lib/pages/index.dart

Widget _buildView(context) { return Column( children: [ ... ListTile( title: const Text("拉取数据"), onTap: () { Navigator.push(context, MaterialPageRoute(builder: (context) { return const ProductsPage(); })); }, ),

例子: 用户面板

用户 entity

lib/entity/user.dart

class UserEntrty { final String? id; final String? name; final String? email; UserEntrty({this.id, this.name, this.email}); factory UserEntrty.fromJson(Map<String, dynamic> json) { return UserEntrty( id: json['id'], name: json['name'], email: json['email'], ); } Map<String, dynamic> toJson() { return { 'id': id, 'name': name, 'email': email, }; } }

用户数据模型

lib/model/user.dart

class UserModel { final _user = signal<UserEntrty>(UserEntrty()); // 当前用户名 String get userName => _user.value.name ?? ""; // 当前用户Id String get userId => _user.value.id ?? ""; // 登录 1 Future login() async { // await Future.delayed(const Duration(seconds: 2)); _user.value = UserEntrty( id: "1000001", name: "张三", email: "zhangshan@gmail.com", ); } // 登录 2 Future login2() async { // await Future.delayed(const Duration(seconds: 2)); _user.value = UserEntrty( id: "1000002", name: "张三2", email: "zhangshan2@gmail.com", ); } }

Get_it 加载

lib/pages/dashboard.dart

class _DashboardPageState extends State<DashboardPage> { void initState() { super.initState(); // 注册单例 - 当前用户 GetIt.I.registerSingleton<UserModel>( UserModel(), ); }
void dispose() { // 释放 GetIt.I.unregister<UserModel>(); super.dispose(); }

导航组件

lib/widget/nav.dart

import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import '../model/user.dart'; class NavWidget extends StatelessWidget { const NavWidget({super.key}); Widget _buildView(context) { var g = GetIt.I; return Container( color: Colors.amber, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ // 按钮 ElevatedButton( onPressed: () { // 执行登录 g.get<UserModel>().login(); }, child: const Text('登录'), ), // 按钮2 ElevatedButton( onPressed: () { // 执行登录 g.get<UserModel>().login2(); }, child: const Text('登录2'), ), ], ), ); } Widget build(BuildContext context) { return _buildView(context); } }

用户组件

lib/widget/profile.dart

class ProfileWidget extends StatelessWidget { const ProfileWidget({super.key}); Widget _buildView(context) { var g = GetIt.I; // 监听 effect(() { print(g.get<UserModel>().userId); print(g.get<UserModel>().userName); }); return Watch( (context) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('用户ID:${g.get<UserModel>().userId}'), Text('用户称呼:${g.get<UserModel>().userName}'), ], ), ), ); } Widget build(BuildContext context) { return _buildView(context); } }

面板界面

lib/pages/dashboard.dart

Widget _buildView(context) { return const Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ // 顶栏 NavWidget(), // 用户信息 Expanded(child: ProfileWidget()), ], ); }
Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('用户登录界面'), ), body: _buildView(context), ); }

首页入口

lib/pages/index.dart

Widget _buildView(context) { return Column( children: [ ... ListTile( title: const Text("面板"), onTap: () { Navigator.push(context, MaterialPageRoute(builder: (context) { return const DashboardPage(); })); }, ),

代码

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_signals

小结

signal + get_it 套件方式我总结 2 点:

  • 比 Provider 少了嵌套的繁琐
  • 虽然和 getx 一样轻巧,但是 getx 自带了 状态+依赖+路由

在本文中,我们深入探讨了 Flutter signal 状态管理插件的快速上手指南。通过使用 signal 插件,我们可以实现轻量级的状态管理,支持懒加载、全平台应用以及精细颗粒度的局部刷新,极大地提升了移动应用开发的效率和灵活性。结合 get_it 这一强大的依赖注入框架,我们能够应对复杂的移动应用场景,提升开发效率和代码质量。希望本文能够帮助读者更好地掌握 Flutter signal 状态管理插件,为日后的移动应用开发提供有力支持。

感谢阅读本文

如果有什么建议,请在评论中让我知道。我很乐意改进。


flutter 学习路径


© 猫哥 ducafecat.com

end


Copyright 2024 ducafecat. All rights reserved.
微信: ducafecat, line: ducafecat,京ICP备2021009050号-3