flutter - bloc - 我如何在 Ui 中使用 FutureBuilder 来正确实现 Bloc Architecture
flutter - bloc - how can I use FutureBuilder in my Ui to properly implement Bloc Architecture
我是 Flutter 和 Bloc 架构的新手,我正在尝试使用 Bloc 来实现我的登录功能。我正在尝试调用我的 Bloc 文件中的函数,但我不知道该怎么做。如果您能帮我看看我在使用 Bloc 时是否还有其他问题,我也会很高兴。
这是我的代码 Ui:
MaterialButton(
color: Colors.deepPurple,
minWidth: screenAwareSize(500, context),
onPressed: () {
_submitForm(authBloc, user, pass);
},
void _submitForm(AuthBloc authBloc, String user, String pass) async {
formKey.currentState.save();
if (formKey.currentState.validate()) {
var response = await authBloc.login(user, pass);
//when I print(response) it shows null
}
}
这是我的集团 class:
class AuthBloc extends MainBloc {
final Repo _repo = Repo();
PublishSubject<Future<UserModel>> _authController = new PublishSubject<Future<UserModel>>();
Observable<Future<UserModel>> get auth => _authController.stream;
login(String user, String pass) async {
Future<UserModel> item = await _repo.login(user, pass);
_authController.sink.add(item);
}
dispose() {
_authController.close();
}
}
AuthBloc authBloc = new AuthBloc();
这是我的 API class:
class API{
Future<UserModel> login(String user, String pass) async {
var response =
await client.get(base_url + "login.php?user=${user}&pass=${pass}");
return UserModel.fromJSON(json.decode(response.body));
}}
这是我的回购 class:
class Repo {
final API api = new API();
login(String user, String pass) async => await api.login(user, pass);}
我将首先尝试解释 BLOC 组件应该做什么,尽可能简短(并且尽可能简单)。
- UI 屏幕 - 显然向用户显示数据
- BLOC(或 ViewModel)- 决定如何向用户显示数据,是否将文本设为粗体,是否显示错误,是否转到下一个屏幕。
- Repo - 决定向用户显示什么数据(我们是否显示来自数据库的内容,我们是否从 API 获取内容,我们是否显示红色产品?)
根据您的应用的功能,您也可以拥有其他组件,例如:
- 网络 - 执行 API 请求并转换对模型的响应,这应该只能从 repos 访问,并且该组件唯一应该做的就是从 repo 接收数据(headers,body,url) 和 return 数据以模型的形式回购(您可以查看下面的代码)。
- 数据库 - 对数据库执行 CRUD 操作,同样只能从 repo 访问。
- 传感器 - 从本地传感器读取数据,同样只能从 repo 访问。
现在,我建议也使用带有依赖注入的 BLOC 模式,没有它就有点没用了。使用 DI,您可以模拟所有组件直到 UI,并且可以很容易地对所有代码进行单元测试。
此外,我认为没有必要将 RxDart(库)与 Streams/Future(相当于 RxDart lib 的 dart)混合使用。首先,我建议只使用其中一个,并根据您的代码片段,我建议更好地了解如何整体使用 Rx。
所以,下面有一个小代码片段,说明我将如何使用块模式进行登录(最好也检查代码注释 :))。
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class TheUIScreen extends StatefulWidget {
@override
_TheUIScreenState createState() => _TheUIScreenState();
}
class _TheUIScreenState extends State<TheUIScreen> {
//TODO: for repo, block, networking, we used dependecy injection, here we have to create and init all the dependecies;
TheAuthBlock _block;
@override
void initState() {
super.initState();
TheAuthAPI api = TheAuthAPI();
TheAuthRepo repo =
TheAuthRepo(theAuthAPI: api); // we could also do repo = TheAuthRepo();
_block =
TheAuthBlock(repo: repo); // we could also do _block = TheAuthBlock();
}
@override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(onPressed: () {
_block.loginUser("test", "test").then((actualUser) {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return TestRoute(); // or do whatever action you need, the user is logged in
}));
}).catchError((error) {
//show error, something went wrong at login;
});
}),
);
}
}
class TheAuthBlock {
final TheAuthRepo repo;
TheAuthBlock({this.repo = const TheAuthRepo()});
Future<UserModel> loginUser(String email, String password) {
return repo.login(email, password).then((userModel) {
//TODO: here we decide HOW to display the user, you might want to transfor the UserModel into a model that's used only for UI.
//In any way, here you should do all the processing, the UI only should only display the data, not manipulate it.
});
}
}
class TheAuthRepo {
final TheAuthAPI theAuthAPI;
const TheAuthRepo(
{this.theAuthAPI =
const TheAuthAPI()}); // THIS would be the default constructor but it will alow us to test it using unit tests.
Future<UserModel> login(String email, String password) {
//TODO: here you could also check if the user is already logged in and send the current user as a response
if (email.isNotEmpty && password.isNotEmpty) {
return theAuthAPI.login(email, password).then((userModel) {
//TODO: you can do extra processing here before returning the data to the block;
});
} else {
return Future.error(
"Well you can't login with empty ddata"); // TODO: you can return differetn errors for email or pwd;
}
}
}
class TheAuthAPI {
final String url;
const TheAuthAPI({this.url = "https://my.cool.api/login"});
Future<UserModel> login(String email, String pwd) {
// TODO: note you return a future from this method since the login will return only once (like almost all http calls)
Map<String, String> headers = Map(); // TODO: set any headers you need here
Map<String, String> body = {
"email": email,
"pwd": pwd
}; // TODO: change the body acordingly
return http.post("THE URL", headers: headers, body: body).then((response) {
//TODO: parse response here and return it
return UserModel("test",
"test"); // this should be generated from the response not like this
});
}
}
class UserModel {
final String email;
UserModel(this.email, this.pass);
final String pass;
}
我是 Flutter 和 Bloc 架构的新手,我正在尝试使用 Bloc 来实现我的登录功能。我正在尝试调用我的 Bloc 文件中的函数,但我不知道该怎么做。如果您能帮我看看我在使用 Bloc 时是否还有其他问题,我也会很高兴。 这是我的代码 Ui:
MaterialButton(
color: Colors.deepPurple,
minWidth: screenAwareSize(500, context),
onPressed: () {
_submitForm(authBloc, user, pass);
},
void _submitForm(AuthBloc authBloc, String user, String pass) async {
formKey.currentState.save();
if (formKey.currentState.validate()) {
var response = await authBloc.login(user, pass);
//when I print(response) it shows null
}
}
这是我的集团 class:
class AuthBloc extends MainBloc {
final Repo _repo = Repo();
PublishSubject<Future<UserModel>> _authController = new PublishSubject<Future<UserModel>>();
Observable<Future<UserModel>> get auth => _authController.stream;
login(String user, String pass) async {
Future<UserModel> item = await _repo.login(user, pass);
_authController.sink.add(item);
}
dispose() {
_authController.close();
}
}
AuthBloc authBloc = new AuthBloc();
这是我的 API class:
class API{
Future<UserModel> login(String user, String pass) async {
var response =
await client.get(base_url + "login.php?user=${user}&pass=${pass}");
return UserModel.fromJSON(json.decode(response.body));
}}
这是我的回购 class:
class Repo {
final API api = new API();
login(String user, String pass) async => await api.login(user, pass);}
我将首先尝试解释 BLOC 组件应该做什么,尽可能简短(并且尽可能简单)。
- UI 屏幕 - 显然向用户显示数据
- BLOC(或 ViewModel)- 决定如何向用户显示数据,是否将文本设为粗体,是否显示错误,是否转到下一个屏幕。
- Repo - 决定向用户显示什么数据(我们是否显示来自数据库的内容,我们是否从 API 获取内容,我们是否显示红色产品?)
根据您的应用的功能,您也可以拥有其他组件,例如:
- 网络 - 执行 API 请求并转换对模型的响应,这应该只能从 repos 访问,并且该组件唯一应该做的就是从 repo 接收数据(headers,body,url) 和 return 数据以模型的形式回购(您可以查看下面的代码)。
- 数据库 - 对数据库执行 CRUD 操作,同样只能从 repo 访问。
- 传感器 - 从本地传感器读取数据,同样只能从 repo 访问。
现在,我建议也使用带有依赖注入的 BLOC 模式,没有它就有点没用了。使用 DI,您可以模拟所有组件直到 UI,并且可以很容易地对所有代码进行单元测试。
此外,我认为没有必要将 RxDart(库)与 Streams/Future(相当于 RxDart lib 的 dart)混合使用。首先,我建议只使用其中一个,并根据您的代码片段,我建议更好地了解如何整体使用 Rx。
所以,下面有一个小代码片段,说明我将如何使用块模式进行登录(最好也检查代码注释 :))。
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class TheUIScreen extends StatefulWidget {
@override
_TheUIScreenState createState() => _TheUIScreenState();
}
class _TheUIScreenState extends State<TheUIScreen> {
//TODO: for repo, block, networking, we used dependecy injection, here we have to create and init all the dependecies;
TheAuthBlock _block;
@override
void initState() {
super.initState();
TheAuthAPI api = TheAuthAPI();
TheAuthRepo repo =
TheAuthRepo(theAuthAPI: api); // we could also do repo = TheAuthRepo();
_block =
TheAuthBlock(repo: repo); // we could also do _block = TheAuthBlock();
}
@override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(onPressed: () {
_block.loginUser("test", "test").then((actualUser) {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return TestRoute(); // or do whatever action you need, the user is logged in
}));
}).catchError((error) {
//show error, something went wrong at login;
});
}),
);
}
}
class TheAuthBlock {
final TheAuthRepo repo;
TheAuthBlock({this.repo = const TheAuthRepo()});
Future<UserModel> loginUser(String email, String password) {
return repo.login(email, password).then((userModel) {
//TODO: here we decide HOW to display the user, you might want to transfor the UserModel into a model that's used only for UI.
//In any way, here you should do all the processing, the UI only should only display the data, not manipulate it.
});
}
}
class TheAuthRepo {
final TheAuthAPI theAuthAPI;
const TheAuthRepo(
{this.theAuthAPI =
const TheAuthAPI()}); // THIS would be the default constructor but it will alow us to test it using unit tests.
Future<UserModel> login(String email, String password) {
//TODO: here you could also check if the user is already logged in and send the current user as a response
if (email.isNotEmpty && password.isNotEmpty) {
return theAuthAPI.login(email, password).then((userModel) {
//TODO: you can do extra processing here before returning the data to the block;
});
} else {
return Future.error(
"Well you can't login with empty ddata"); // TODO: you can return differetn errors for email or pwd;
}
}
}
class TheAuthAPI {
final String url;
const TheAuthAPI({this.url = "https://my.cool.api/login"});
Future<UserModel> login(String email, String pwd) {
// TODO: note you return a future from this method since the login will return only once (like almost all http calls)
Map<String, String> headers = Map(); // TODO: set any headers you need here
Map<String, String> body = {
"email": email,
"pwd": pwd
}; // TODO: change the body acordingly
return http.post("THE URL", headers: headers, body: body).then((response) {
//TODO: parse response here and return it
return UserModel("test",
"test"); // this should be generated from the response not like this
});
}
}
class UserModel {
final String email;
UserModel(this.email, this.pass);
final String pass;
}