Behind Developing the Minimal Poetry App: Flutter Mobile Rapid Development Practice — State Management, Internationalization, Data Persistence, and Performance Optimization (Part 1)
Preface A note before we start: Although Flutter has Google behind it, it is still a young technology. The project is still in its early stages, updates very quickly, and there are more than a few problems. Using Flutter means you have to put up with all kinds of weird bugs and the headache of scarce Chinese documentation. This article is not trying to talk anyone into jumping in; it is simply a record of the development process of the “Minimal Poetry” app. The app is already on the store. If you’re interested, you can download it and give it a try: https://www.coolapk.com/apk/
目录7 个章节
Preface
A note before we start: Although Flutter has Google behind it, it is still a young technology. The project is still in its early stages, updates very quickly, and there are more than a few problems. Using Flutter means you have to put up with all kinds of weird bugs and the headache of scarce Chinese documentation. This article is not trying to talk anyone into jumping in; it is simply a record of the development process of the “Minimal Poetry” app.
The app is already on the store. If you’re interested, you can download it and give it a try: https://www.coolapk.com/apk/251155
Main UI screenshots:
| Home | Dark Mode Home | Poetry Collection | Browse Collection |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
| Collection Details | Author List | Author Details | Font Selection |
![]() | ![]() | ![]() | ![]() |
As with the Django rapid development practice article, this piece skips the fluff and goes straight to the steps.
Project File Structure
Design the project file structure first. Different projects have different needs, so design the structure according to your actual requirements. The following is my project structure, for reference only:
lib
├── common
├── i10n
├── models
├── routes
├── states
└── widgets
| Folder | Purpose |
|---|---|
| common | Some utility classes, such as general helper classes, network API classes, static classes that store global variables, etc. |
| i10n | Holds internationalization-related code |
| models | Model class files generated via json to models are all stored here |
| routes | Holds all page code for the project |
| states | Stores state classes that need to be shared across components in the app |
| widgets | Holds custom widgets |
Define the models
In this project, I use json to models to auto-generate model classes. Why this? The reason is simple: it reduces the workload. Define the models used in the app with JSON; after generating the model classes you can conveniently serialize them to JSON for persistence, or read JSON data from config files and deserialize it into model objects. You can also generate model classes directly from JSON returned by backend APIs, which is very convenient.
Define models using JSON, as in the following example:
Create a json folder in the project root and add the JSON files to be converted. The content looks roughly like this. poem.json
{
"strains": [
"平平平仄仄,平仄仄平平。",
"仄仄平平仄,平平仄仄平。",
"平平平仄仄,平仄仄平平。",
"平仄仄平仄,平平仄仄平。"
],
"author": "作者名称",
"authorObj": "$author",
"paragraphs": [
"秦川雄帝宅,函谷壯皇居。",
"綺殿千尋起,離宮百雉餘。",
"連甍遙接漢,飛觀迥凌虛。",
"雲日隱層闕,風煙出綺疎。"
],
"tags": [
"战争",
"生活",
"冬天",
"爱国",
"边塞"
],
"chapter": "国风",
"section": "周南",
"rhythmic": "玉楼春",
"title": "帝京篇十首 一",
"content": "经传宜独坐读,史鉴宜与友共读。",
"comment": [
"孙恺似曰:深得此中真趣,固难为不知者道。",
"王景州曰:如无好友,即红友亦可。"
],
"notes": [
"1.小山--写女子的隔夜残妆。小山:女子画眉的式样之一。小山重叠:眉晕褪色。金:额黄,在额上涂黄色。金明灭:褪色的额黄明暗不匀。",
"2.鬓云欲度--鬓发撩乱如云,低垂下来。香腮雪:洁白如雪的香腮。",
"3.照花--对镜簪花。用前镜、后镜对照以瞻顾后影。",
"4.双双--罗襦上用金线绣的成双的鹧鸪鸟。反衬自身孤独。"
],
"anthology": "所属诗集名称",
"id": "08e41396-2809-423d-9bbc-1e6fb24c0ca1"
}
Add the dependencies:
dev_dependencies:
json_model: ^0.0.2
build_runner: ^1.0.0
json_serializable: ^2.0.0
After that, run:
flutter packages pub run json_model
This will automatically generate the model classes under the lib/models folder.
One frustrating thing is that this json_model library only supports very old versions of build_runner and json_serializable, which then conflicts with the intl I need later. Every time I use these two libraries I have to keep commenting things out and switching dependency versions. Really annoying = =....
State Management
State management is the most important part of an app, and also the foundation for theme switching and internationalization later on. This article is a rapid development practice, so it won’t go too deep into Flutter state management. If you want to learn more, you can check out this Flutter tutorial written by an expert: https://book.flutterchina.club/chapter7/provider.html
I use the Provider component to manage app state. It is implemented based on InheritedWidget and is quite convenient to use.
First, add the dependency:
dependencies:
provider: ^3.2.0
Under the lib/states folder, add models for shared state, for example:
import 'package:flutter/material.dart';
import 'package:minimal_poem/common/global.dart';
import 'package:minimal_poem/models/index.dart';
import 'notifier.dart';
class ProfileChangeNotifier extends ChangeNotifier {
Profile get profile => Global.profile;
@override
void notifyListeners() {
// 保存Profile变更
Global.saveProfile();
Global.saveAllUsers();
super.notifyListeners(); // 通知依赖的Widget更新
}
}
class ThemeModel extends ProfileChangeNotifier {
// 获取当前主题,如果未设置主题,则默认使用蓝色主题
ColorSwatch get theme => Global.themes.firstWhere((e) => e.value == profile.theme, orElse: () => Colors.blue);
// 主题改变后,通知其依赖项,新主题会立即生效
set theme(ColorSwatch color) {
if (color != theme) {
profile.theme = color[500].value;
notifyListeners();
}
}
bool get darkMode => Global.profile.darkMode;
set darkMode(bool value) {
Global.profile.darkMode = value;
notifyListeners();
}
}
These models inherit from ProfileChangeNotifier and can provide data or manage modifications and persistence of data.
In ordinary widgets you can use them directly to get or save data. Combined with the provider component, when the model data changes it can trigger widget updates~
For example, my MyApp class definition uses MultiProvider and Consumer2:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: <SingleChildCloneableWidget>[
ChangeNotifierProvider.value(value: ThemeModel()),
ChangeNotifierProvider.value(value: UserModel()),
ChangeNotifierProvider.value(value: LocaleModel()),
],
child: Consumer2<ThemeModel, LocaleModel>(
builder: (BuildContext context, themeModel, localeModel, Widget child) {
return MaterialApp(
theme: ThemeData(
brightness: Global.profile.darkMode ? Brightness.dark : Brightness.light,
primarySwatch: themeModel.theme,
),
onGenerateTitle: (context) {
return DaLocalizations.of(context).title;
},
home: HomeRoute(),
//应用主页
locale: localeModel.getLocale(),
//我们只支持美国英语和中文简体
supportedLocales: [
const Locale('zh', 'CN'), // 中文简体
const Locale('en', 'US'), // 美国英语
//其它Locales
],
localizationsDelegates: [
// 本地化的代理类
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
// EasyRefresh的多语言支持
GlobalEasyRefreshLocalizations.delegate,
// 注册我们的Delegate
DaLocalizationsDelegate()
],
localeResolutionCallback: (Locale _locale, Iterable<Locale> supportedLocales) {
if (localeModel.getLocale() != null) {
//如果已经选定语言,则不跟随系统
return localeModel.getLocale();
} else {
Locale locale;
// APP语言跟随系统语言,如果系统语言不是中文简体或美国英语,
// 则默认使用美国英语
if (supportedLocales.contains(_locale)) {
locale = _locale;
} else {
locale = Locale('en', 'US');
}
return locale;
}
},
);
},
),
);
}
}
Internationalization Support
Internationalization means multi-language support, using the intl package.
Create a folder i10n-arb in the project root, and create localization_intl.dart in lib/i10n:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'messages_all.dart';
class DaLocalizations {
String get userNameOrPasswordWrong => null;
static Future<DaLocalizations> load(Locale locale) {
final String name = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
final String localeName = Intl.canonicalizedLocale(name);
//2
return initializeMessages(localeName).then((b) {
Intl.defaultLocale = localeName;
return new DaLocalizations();
});
}
static DaLocalizations of(BuildContext context) {
return Localizations.of<DaLocalizations>(context, DaLocalizations);
}
String get auto => Intl.message('auto', name: 'auto', desc: 'set theme mode auto');
}
//Locale代理类
class DaLocalizationsDelegate extends LocalizationsDelegate<DaLocalizations> {
const DaLocalizationsDelegate();
//是否支持某个Local
@override
bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);
// Flutter会调用此类加载相应的Locale资源类
@override
Future<DaLocalizations> load(Locale locale) {
//3
return DaLocalizations.load(locale);
}
// 当Localizations Widget重新build时,是否调用load重新加载Locale资源.
@override
bool shouldReload(DaLocalizationsDelegate old) => false;
}
Run the command to generate arb files:
flutter pub pub run intl_translation:extract_to_arb --output-dir=i10n-arb lib/i10n/localization_intl.dart
After that, an intl_messages.arb file will be generated under the i10n-arb folder. This is essentially a json file. We also need to create corresponding translations for different language versions. For example, this app supports Chinese and English, so we need to create two files: intl_zh.arb and intl_en.arb.
Copy the contents of the intl_messages.arb file into the translation files for the corresponding languages respectively, and modify them into the corresponding language versions.
{
"@@last_modified": "2019-12-17T17:04:43.001945",
"title": "title",
"@title": {
"type": "text",
"placeholders": {}
},
}
After finishing the above, run the command to generate the corresponding classes:
# 从arb文件生成dart代码
flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/i10n --no-use-deferred-loading lib/i10n/localization_intl.dart i10n-arb/intl_*.arb
To Be Continued
Turns out there's so much content. Limited by length, I will continue documenting in the following articles~
Welcome to Connect
For questions, please leave a message on the WeChat official account backend. I'll reply to every message~
- WeChat Official Account: 画星星高手
- Live coding room: https://live.bilibili.com/11883038
- Zhihu: https://www.zhihu.com/people/dealiaxy
- Jianshu: https://www.jianshu.com/u/965b95853b9f
_EOF_
Behind Developing the Minimal Poetry App: Flutter Mobile Rapid Development Practice — State Management, Internationalization, Data Persistence, and Performance Optimization (Part 1)
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
使用Flutter设计一个好看的_我_页面
近期遇到一些很烦的琐事,状态比较down,很多原本计划好的事情都耽搁了,实在是难顶…… 看到后台一直有朋友问怎么博客和公众号没有更新,所以我忙完得闲就来更了! 前言 起因是最近重拾以前的旧项目(业余做的,打算有机会自己运营起来)继续完善,然后发现有些界面设计太粗糙了,实在不满意~~(虽然是因为本来功能就不多)~~,然后就着手开始优化~ 应该会写成一系列文章,今天就从App的"我"页面开始吧~ 效果
2021-12-18 · Flutter · 约 1 分钟
Windows上怎么配置flutter开发环境?这篇文章手把手教你
前言 在Windows上做开发,一个字:坑。 但是有时候外出只能带着笔记本,Linux在一些笔记本上不太兼容,驱动支持不完美,而且电源管理不够好,所以无奈的情况下只能用Windows暂时顶一下,就像现在我要在Windows上搭建Flutter的开发环境一样,太坑了。 进入正题 先安装Android SDK 首先,你要有一种可以下载到Android SDK的方式,可以是梯子,或者是国内镜像,具体我就
2023-03-05 · Flutter · 约 1 分钟
Flutter开发:迁移Flutter应用到AndroidX
首先上官方文档:https://flutter.dev/docs/development/packages-and-plugins/androidx-compatibility 有两种方式,一种由Android studio自动完成,一种手动修改依赖配置。 官方推荐第一种,不过在我这不好使,于是我用了第二种,也成功跑起来了~ 自动方式 Use Android Studio to migrate y
2021-12-18 · Flutter · 约 1 分钟
Flutter开发:ListView手动滚动到底部
这个问题搜了很久都没找到,国内关于flutter的研究好像还没多少,不过stackoverflow真的是万能的,这里记录一下~ 原文 Use ScrollController.jumpTo() or ScrollController.animateTo() method to achieve this. Here is code snippet (After 1 second, the ListV
2021-12-18 · Flutter · 约 1 分钟
讨论
共 0 条加载评论中…







