Сначала я попытаюсь объяснить, что компоненты BLOC должны делать как можно короче (и настолько просто, насколько это возможно).
- Экран пользовательского интерфейса - очевидно, показывает данные пользователю
- BLOC (или ViewModel) - решает, КАК отображать данные пользователю, делаем ли мы текст жирным, показываем ли мы ошибку, идем ли мы на следующий экран.
- Repo - решает, ЧЕМУ данные отображатьпользователь (показываем ли мы содержимое из базы данных, получаем ли мы его из API, показываем ли мы продукты красного цвета?)
У вас могут быть и другие компоненты, в зависимости от того, что делает ваше приложение,например:
- Работа в сети - выполняет запросы API и преобразовывает ответ в модель, это должно быть доступно только из репозиториев, и единственное, что этот компонент должен делать, это получать данные из репо (заголовки, тело,url) и вернуть данные в репо в виде модели (вы можете проверить код ниже).
- База данных - выполнить операцию CRUD для базы данных, которая доступна только для репо.
- Датчики -читать данные с собственных датчиков, которые доступны только из репозитория.
Теперь я бы предложил использовать паттерн BLOC и с Dependency Injection, но он бесполезен.С DI вы можете макетировать все компоненты до UI, и было бы очень легко выполнить модульное тестирование всего вашего кода.
Кроме того, я думаю, что нет смысла смешивать RxDart (библиотеку) с Streams / Future (эквивалент дротикаRxDart lib).Для начала я бы предложил использовать только один из них, и, основываясь на вашем фрагменте кода, я бы предложил лучше посмотреть, как использовать 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;
}