初入flutter,状态库到底用哪个!
我之前没写过flutter,进了新公司要求使用flutter。由于对flutetr的生态不熟悉,常常感到疑惑,我到底该用哪个库。其中最有争议的就是状态库的选择,在查找了诸多资料和群里询问交流后,写下了这篇文章,对我所接触到的一些flutter库进行调研。
状态库到底用getx还是provider,令刚开始接触flutter的我甚为纠结,provider是官方推荐,但感觉不是很好用,getx网友褒贬不一,还说什么初学者用了getx就废了(笑哭),让我不知该如何是好,因为我都不熟悉,后来又了解到provider的作者还写了一个更好用的库riverpod,去了解了一下,然后选择就变成了getx or riverpod。
由于一时之间不能决定,于是我决定分开两个分支来写,一个使用riverpod + flutter_hooks,一个用getx。
使用riverpod + flutter_hooks
需要安装的依赖:flutter pub add hooks_riverpod flutter_hooks
这个写法使用的hook对于react和vue3开发者非常的友好,符合使用习惯,最重要的是很方便。
写法如下:
使用HookConsumerWidget
,定义一个响应式更新到变量使用useState: final isButtonDisabled = useState<bool>(false);
,这样就可以愉快的使用定义的变量了,页面会响应式更新的。
dart
class EmailVerificationScreen extends HookConsumerWidget {
final Map<String, dynamic>? arguments;
const EmailVerificationScreen(this.arguments, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(() => GlobalKey<FormState>());
final emailCodeController = useTextEditingController();
final isButtonDisabled = useState<bool>(false);
Future<void> nextStep(BuildContext context) async {
try {
SRouter(context).pushNamed("SetPassword", queryParameters: {
"account": arguments?['account'],
});
} catch (e) {
print(e);
}
}
useEffect(() {
listener() {
isButtonDisabled.value = emailCodeController.text.length < 6;
}
emailCodeController.addListener(listener);
return () {
emailCodeController.removeListener(listener);
};
}, [emailCodeController]);
return Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).emailVerification,
style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.w400),
),
SizedBox(height: 8.w),
Text(
S.current
.enterThe6DigitVerificationCodeReceivedInYourEmailTheCodeIsValidFor30Minutes,
style: TextStyle(
color: const Color.fromRGBO(143, 143, 143, 1),
fontSize: 12.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 39.w),
SStyledInput(
controller: emailCodeController,
labelText: S.of(context).emailVerificationCode,
validator: (value) => Validators.validateEmail(value, context),
),
SizedBox(height: 10.w),
SizedBox(
height: 50.w,
width: double.infinity,
child: ElevatedButton(
onPressed:
isButtonDisabled.value ? null : () => nextStep(context),
child: Text(S.of(context).nextStep),
),
),
SizedBox(
height: 26.w,
),
GestureDetector(
onTap: () {
SRouter(context).pushNamed("Register");
},
child: Text(
S.of(context).didNotReceiveTheCode,
style: const TextStyle(
color: Color.fromRGBO(79, 160, 255, 1.0),
fontSize: 12,
),
),
)
],
),
);
}
}
- 全局状态管理:
大体如下,使用一个全局的NotifierProvider
scala
final userProvider = NotifierProvider<UserProvide, UserState>(UserProvide.new);
class UserState {
/// 当前活跃用户
User? activeUser;
/// 多用户会话管理
List<User> userList = [];
String? get getToken {
return activeUser?.accessToken;
}
}
class UserProvide extends Notifier<UserState> {
@override
UserState build() => UserState();
/// 登录
Future<void> login(User user) async {
final existingUser =
state.userList.firstWhereOrNull((u) => u.userId == user.userId);
getx
getx是使用的false.obs
的形式
dart
var isButtonDisabled = false.obs;
路由对比
由于getx还有路由的相关功能,所以这里再加一个getx 路由和go_router的对比,go_router是官方推荐的。
getx router
- router.dart getx路由提供了一些转场动画,挺方便的
php
const Transition transition = Transition.rightToLeft;
List<GetPage> routes = [
GetPage(
name: '/',
page: () => const HomeScreen(username: ''),
transition: transition),
GetPage(
name: '/login',
page: () => const DefaultLayout(
title: LocaleKeys.login,
child: LoginScreen(),
),
transition: transition),
GetPage(
name: '/welcomeBack',
page: () => DefaultLayout(
title: LocaleKeys.welcomeBack,
child: WelcomeBackScreen(),
),
transition: transition),
GetPage(
name: '/notFound',
page: () => const NotFoundScreen(),
transition: Transition.fade),
];
- 跳转:
可以看到getx并没有依赖context
dart
Get.toNamed(
'/welcomeBack',
arguments: {'email': email},
);
go_router
这里要吐槽一句go_router的官方文档容易让人误解。上来就让人context.go('/users/123')
,也没说清楚一点,这个操作数据是不进入路由栈的。所以路由回退不了,让我疑惑半天,差点就换库了,和群友交流了一下才晓得,不少哥们都被坑过。
- index.dart
php
final goRouter = GoRouter(
initialLocation: FlutterConfig.initialLocation,
redirect: routeBeforeHook,
// debugLogDiagnostics: true,
routes: baseRoutes + whiteList,
);
- routes.dart
php
List<GoRoute> baseRoutes = [ GoRoute(
name: "Home",
path: '/',
builder: (context, state) => const HomeScreen(),
),];
- 跳转:
ini
context.pushNamed(name);
- 这里需要说一下的就是go_router配合riverpod的使用,如何在go_router里面拿到riverpod的状态:
javascript
String? routeBeforeHook(BuildContext context, GoRouterState state) {
final userState = ProviderScope.containerOf(context).read(userProvider);
if (userState.getToken == null &&
!whiteList.map((e) => e.path).contains(state.uri.path)) {
return '/login';
} else {
return null;
}
}
国际化对比
getx
如果使用getx的国际化功能,建议配合getx_cli使用,因为这个可以根据json文件生成,json是通用文件,可以方便替换和多项目使用,而且生成之后就有提示了。
dart
import 'package:get/get.dart';
class Messages extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'zh_CN': {
'hello': '你好 世界',
},
'de_DE': {
'hello': 'Hallo Welt',
}
};
}
- 使用:
这个使用方式虽然也方便,但是没有提示
bash
Text('title'.tr);
flutter_localizations
这是官方推荐的,最好配合编辑器插件Flutter Intl
使用。在lib目录下创建I10n文件夹,使用的是.arb
后缀的文件,这个文件的格式和json一模一样,插件就会自动帮助生成相应代码了。
总结
总结下来,于我来说大概就是两条路线,一条是getx全家桶一把梭,一条就是hooks_riverpod + fluter_hooks + go_route + flutter_localizations。
接下来还要写react的项目,我对react的生态也不太熟,还要去继续去调研一下react的状态库以及相关的生态。