如何使用Mobx的Flutter状态管理

922 阅读5分钟

使用Mobx的Flutter状态管理

在处理大型Flutter应用程序时,以干净和优化的方式管理应用程序状态是非常重要的。状态管理是指在不同的屏幕和组件之间处理应用程序的数据。

前提条件

  1. 安装[Flutter SDK]。
  2. 安装了首选的IDE或代码编辑器。
  3. 具有[Flutter]方面的知识。

简介

这篇文章将讨论如何从API中获取数据并将其传递给UI的服务类。UI将监听来自API的数据。当数据可用时,应用程序将显示一个项目的列表。

此外,当应用程序处于加载状态时,它将显示一个进度指示器。MobX有四个原则性的概念,我们将学习并理解它们的工作原理。observables,computed values,reactionsactions

观察者

MobX中的可观察项允许我们为我们的数据结构--如类、对象、数组--添加可观察的功能,并使我们的属性成为可观察项。这意味着我们的属性存储一个单一的值,每当该属性的值发生变化时,MobX将为我们跟踪该属性的值。

例如,让我们想象一下我们有一个叫做counter的变量。我们可以通过使用@observable 装饰器来轻松地创建一个可观察变量,像这样。

class counter {
  @observable
  int value = 0;
}

通过使用@observable 装饰器,我们告诉MobX我们要跟踪计数器的值,每次计数器变化时,我们都会得到更新的值。

计算值

在MobX中,我们可以将计算值理解为可以从我们的状态中派生出来的值,所以computed values 这个名字是完全合理的。它们是由我们的状态派生出来的函数,所以只要我们的状态发生变化,它们的返回值就会改变。

你必须记住,关于计算值,获取语法是必需的,而且它从你的状态衍生出来是自动的,你不需要做任何事情来获取更新的值。

class counter {
  @observable
  int value = 0;

  @computed
  int get doubleValue => value * 2;
}

行动

MobX的动作是一个非常重要的概念,因为它们负责修改我们的状态。它们负责改变和修改我们的观察变量。

class counter {
  @observable
  int value = 0;

  @action
  void increment() {
    value++;
  }

  @action
  void decrement() {
    value--;
  }
}

反应

MobX中的反应与计算值非常相似,但不同的是,反应会触发副作用,并在我们的观察变量改变时发生。MobX中的反应可以是UI变化或后台变化--例如,网络请求、控制台的打印等。

我们有自定义的反应:自动运行、反应、当。

自动运行

autorun将在每次特定的可观察到的变化时运行。例如,如果我们想在计数器每次变化时打印它的值,我们可以这样做。

autorun((_) {
  print(counter.value);
});
Reaction

Reaction 与 autorun 相似,但它在跟踪观察变量的值时给了我们更多的控制。它接收两个参数:第一个是一个简单的函数,用来返回第二个参数中使用的数据。

第二个参数将是效果函数;这个效果函数将只对第一个函数参数中传递的数据做出反应。只有当你在第一个参数中传递的数据发生变化时,这个效果函数才会被触发。

reaction((_) {
  return counter.value;
}, (_) {
  print('Counter changed to ${counter.value}');
});

when 与 reaction 非常相似,但它更具体。它是一个只会对与你在第一个参数中传递的数据相匹配的数据做出反应的函数。

when((_) {
  return counter.value == 0;
}, (_) {
  print('Counter is zero');
});

项目设置

在继续之前,运行下面的命令来验证你已经在你的机器上正确安装了Flutter。

$ flutter doctor

Command window

现在您已经验证了所有的设置都是正确的,执行下面的命令来创建一个新的flutter项目。

$ flutter create news_app # news_app is the name of the application

在你的代码编辑器中打开生成的项目,在pubspec.yml 文件中添加以下依赖项。

dependencies:
  flutter:
    sdk: flutter
  mobx:
  flutter_mobx:
  http:
  url_launcher:
  cupertino_icons: ^1.0.2

dev_dependencies:
  build_runner:
  mobx_codegen:
  flutter_test:
    sdk: flutter
  • mobx- 我们将使用的状态管理器。
  • http- 我们将使用的互联网连接库,从API中获取新闻数据。
  • url_launcher- 用提供的URL打开一个浏览器。在我们的案例中,每个新闻项目都有一个指向新闻发布网站的URL。
  • mobx_codegen-mobx 状态管理器的代码生成工具。

领域

在项目的lib文件夹中,创建一个名为article.dart 的新dart文件,并添加以下代码片段。

class Articles {
  // Default constructor
  Articles({
    String? author,
    String? title,
    String? description,
    String? url,
    String? urlToImage,
    String? publishedAt,
    String? content,
  }) {
    _author = author;
    _title = title;
    _description = description;
    _url = url;
    _urlToImage = urlToImage;
    _publishedAt = publishedAt;
    _content = content;
  }
  //Converts a news JSON item to Article model 
  Articles.fromJson(dynamic json) {
    _author = json['author'];
    _title = json['title'];
    _description = json['description'];
    _url = json['url'];
    _urlToImage = json['urlToImage'];
    _publishedAt = json['publishedAt'];
    _content = json['content'];
  }

  String? _author;
  String? _title;
  String? _description;
  String? _url;
  String? _urlToImage;
  String? _publishedAt;
  String? _content;

  String? get author => _author;

  String? get title => _title;

  String? get description => _description;

  String? get url => _url;

  String? get urlToImage => _urlToImage;

  String? get publishedAt => _publishedAt;

  String? get content => _content;

  //Converts Article model to a JSON
  Map<String, dynamic> toJson() {
    final map = <String, dynamic>{};
    map['author'] = _author;
    map['title'] = _title;
    map['description'] = _description;
    map['url'] = _url;
    map['urlToImage'] = _urlToImage;
    map['publishedAt'] = _publishedAt;
    map['content'] = _content;
    return map;
  }
}

上面的Article 类代表一个新闻项目。文章类将映射到由API返回的JSON对象。

服务

创建一个新的dart文件,命名为service.dart ,并在lib文件夹中添加下面的代码片段。

import 'dart:convert';

import 'package:http/http.dart' as http;

import 'articles.dart';

class NetworkService {
  //Creates an empty list of articles
  List<Articles> articles = [];

  //This method is asynchronous, hence the async keyword used, meaning the application can make the network request and continue performing other tasks. When there is a response, then the application acts on the data.
  Future<List<Articles>> getData(String url) async {
    final response = await http.get(Uri.parse(url));
    if (response.statusCode == 200) {
      //Retrieves the reponse from the http reponse body
      final data = jsonDecode(response.body); 
      //Loops through the news list JSON converting each news item to Article model and adding to articles list we defined above.
      articles = (data["articles"] as List)
          .map((json) => Articles.fromJson(json))
          .toList();
    } else {
      //This section of the code is excuted if the netwrok request fails, i.e. due to unavailable network or incorrect URL.
      print("Error in URL");
    }
    return articles;
  }
}
  • getData(String url) 方法接收URL并从提供的API URL中检索新闻项目列表。

状态管理器

在lib文件夹中,创建一个新的dart文件,名为news_store.dart 。这个文件将包含我们的状态管理代码。将下面的代码片段添加到上面创建的news_Store.dart 文件中。

import 'package:mobx/mobx.dart';
import 'package:structure/articles.dart';

import 'network_service.dart';

part 'news_store.g.dart';

class NewsStore = _NewsStore with _$NewsStore;

abstract class _NewsStore with Store {
  //Creates an instance of the network service class
  final NetworkService httpClient = NetworkService();
  
  @observable
  List<Articles> articles = [];

  @action
  Future<List<Articles>> fetchArticle() async {
    await httpClient
        .getData(
            'https://newsapi.org/v2/everything?q=bitcoin&apiKey=1bfba80a852a4a36b61239f758f97cb5')
        .then((articleList) {
      articles.addAll(articleList);
    });
    return articles;
  }

  void getTheArticles() {
    fetchArticle();
  }
}
  • @observable 注解表示应用程序可以监听标记的变量的任何变化。例如,我们的应用程序将监听文章列表中的变化。当文章被从API中检索并添加到文章列表中时,文章将被显示在用户界面上,而不是一个进度指示器。
  • @action 注解将 标记为可操作的,这意味着它将执行某些操作并改变标有 注解的变量中的数据状态。fetchArticle() @observable

UI

在lib文件夹中,创建一个名为home_screen.dart 的新dart文件,并添加以下代码片段。

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:structure/articles.dart';
import 'package:url_launcher/url_launcher.dart';

import 'news_store.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  //Initializes the NewsStore state manager class
  NewsStore newsStore = NewsStore();
  List<Articles> articles = [];

  @override
  void initState() {
    super.initState();
    getNews();
  }
  //Call the fetchArticle() method from the newsStore state manager class, the getData() method from the network service class is called to make network request.
  getNews() async {
    await newsStore.fetchArticle();
    //When the newStore articles list has data the the data is retrieved and add to the articles list in this screen.
    articles.addAll(newsStore.articles);
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: const Text('News'),
        ),
        //The Observer widgets checks if the state of the observable varibale changed and renders a widget conditionaly.
        body: Observer(
          builder: (context) => RefreshIndicator(
            //Whenever the page is refreshed a network call is made to retrive new news items.
            onRefresh: () async {
              await Future.delayed(const Duration(seconds: 4));
              await newsStore.fetchArticle();
              Scaffold.of(context).showSnackBar(snackBar);
            },
            child: Observer(
              //A list of news items shows if the observable variable has data or a progress indicator is shown.
                builder: (_) => (!articles.isEmpty)
                    ? ListView.builder(
                        itemCount: articles.length,
                        itemBuilder: (_, index) {
                          final newsArticles = articles[index];
                          return ArticleContainer(newsArticles);
                        },
                      )
                    : const Center(
                        child: CircularProgressIndicator(),
                      )),
          ),
        ),
      );

  Widget ArticleContainer(Articles newsArticle) {
    return Padding(
      key: Key(newsArticle.title!),
      padding: const EdgeInsets.all(16.0),
      child: ExpansionTile(
        title: Text(
          newsArticle.title!,
          style: const TextStyle(fontSize: 24.0),
        ),
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              Text("${newsArticle.author} comments"),
              IconButton(
                onPressed: () async {
                  if (await canLaunch(newsArticle.url!)) {
                    launch(newsArticle.url!);
                  }
                },
                icon: const Icon(Icons.launch),
              )
            ],
          ),
        ],
      ),
    );
  }
}

上面的代码片断代表了应用程序的用户界面。接下来,我们在ArticleContainer小组件上检索并渲染新闻项目。

main.dart 文件中,添加下面的代码片断。

import 'package:flutter/material.dart';
import 'package:structure/home_screen.dart';

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

class Application extends StatelessWidget {
  const Application({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomeScreen(),
    );
  }
}

测试

News List

总结

通过本文所涉及的所有内容,你现在可以尝试构建一个生产级的Flutter应用程序,使用Mobx管理应用程序的状态,并遵循推荐的模式,即模型-视图-ViewModel(MVVM)模式。