flutter - bloc - как я могу использовать FutureBuilder в своем пользовательском интерфейсе для правильной реализации архитектуры Bloc - PullRequest
1 голос
/ 01 апреля 2019

Я новичок в трепетании и архитектуре Bloc, и я пытаюсь использовать Bloc для входа в систему. Я пытаюсь вызвать функцию в моем файле Bloc, но я не знаю, как это сделать. Также я был бы рад, если бы вы помогли мне увидеть, есть ли какие-либо другие проблемы в моем использовании Блока. вот код моего пользовательского интерфейса:

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 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 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 Repo {
    final API api = new API();
  login(String user, String pass) async => await api.login(user, pass);}

1 Ответ

1 голос
/ 01 апреля 2019

Сначала я попытаюсь объяснить, что компоненты 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;
}
...