flutter - bloc - how can I use FutureBuilder in my

2020-07-23 08:57发布

I am newbie to flutter and Bloc architecture and I am trying to use Bloc for my login functionality. I am trying to make a call to a function in my Bloc file, but I do not know how to do that. also I would be glad if you could help me to see if there is any other problems in my use of Bloc. here is the code of my 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


    }
  }

here is my bloc 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();

and here is my 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));
  }}

here is my repo class:

 class Repo {
    final API api = new API();
  login(String user, String pass) async => await api.login(user, pass);}

1条回答
老娘就宠你
2楼-- · 2020-07-23 09:21

I will try to explain first what BLOC components should do as short as possible (and as trivial as possible).

  • UI screen - obviously shows data to the user
  • BLOC (or the ViewModel) - decides HOW to display data to the user, do we make the text bold, do we show the error, do we go to next screen.
  • Repo - decides WHAT data to display to the user (do we show the content from db, do we fetch it from API, do we show products that are red ?)

You can have other components too, based on what your app does, like:

  • Networking - performs API requests and transforms response to a model, this should be accessible only from repos and the only thing that this component should do is receive data from the repo (headers, body, url) and return data to repo in the form of a model (you can check the code below).
  • Database - execute CRUD operation on database, same accessible only from repo.
  • Sensors - read data from native sensors, same accessible only from repo.

Now, I would suggest to use the BLOC pattern with Dependency Injection too, without it is kind of useless. With DI, you can mock all components till UI and it would be very easy to unit test all your code.

Also, I think there's no point to mix RxDart(library) with Streams/Future(dart equivalent of RxDart lib). For start, I would suggest to use only one of it and based on your code snippet, I would suggest to have better look on how to use Rx overall.

So, below you have a small code snippet on how I would use the block patter to do a login(it would be nice to check the code comments too :) ).

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;
}
查看更多
登录 后发表回答