Hi,
I'm currently trying to use this login bloc for an app I'm developing and have added a fetchUser to the authenticate function in the UserRepository. Like so:
Future<String> authenticate({
@required String username,
@required String password,
}) async {
print("in authenticate");
var token = loginApiClient.fetchUser(username: username,password: password);
return token;
}
This is my loginApi:
import 'dart:convert';
import 'package:http/http.dart' as http;
class LoginApiClient {
//Api Provider
static const baseUrl = 'http://10.0.0.226:8000'; //localhost on computer
var responseString;
Future<String> fetchUser({String username, String password}) async {
final loginUrl = '$baseUrl/rest-auth/login/';
var jsonHeader = {"Content-Type": "application/json"};
String jsonBody = '{"username": "$username", "password": "$password"}';
try {
final loginResponse =
await http.post(loginUrl, body: jsonBody, headers: jsonHeader);
if (loginResponse.statusCode == 200) {
responseString = json.decode(loginResponse.body);
} else {
throw Exception('error getting user');
}
} catch (err) {
print("error $err");
}
return responseString.toString();
}
}
When the username and password is correct, I do receive a token and it is stored in:
Future<void> persistToken(String token) async {
// await Future.delayed(Duration(seconds: 2));
tokenStorage.write(key: "token", value: token);
}
But what I am stuck on is even when I submit an empty form or wrong credentials, my app still loads the LoggedIn event with the following log:
{"password":["This field may not be blank."]}
I/flutter ( 9888): error Exception: error getting user
I/flutter ( 9888): Delegrate Event: LoggedIn { token: null }
I/flutter ( 9888): Delegate Transition: Transition { currentState: LoginLoading, event: LoginButtonPressed { username: , password: }, nextState: InitialLoginState }
I/flutter ( 9888): Delegate Transition: Transition { currentState: AuthenticationUnauthenticated, event: LoggedIn { token: null }, nextState: AuthenticationLoading }
I/flutter ( 9888): Delegate Transition: Transition { currentState: AuthenticationLoading, event: LoggedIn { token: null }, nextState: AuthenticationAuthenticated }
I get this {"password":["This field may not be blank."]} from my api but the login still goes through.
I'm not sure what I'm doing wrong, please help. I can provide more code if necessary.
Thanks in advance!
It is perfectly fine to add events which you might think they shouldn't be dispatched(in order to not violate the bloc pattern you shouldn't have business logic in your UI deciding when to add an event, so no stuff like if (token != null) authBloc.add(LoggedIn(token)). It is the state resulting from that event what matters in the end. And the bloc is the one which decides what happens to a specific event, if it yields some new state or ignores the event all together.
When your app starts you might wanna add an event which checks if there's a persisted token already and if so, just yield an authenticated state or else an unauthenticated one. The user should really login only if there's no token present. In that case, you could add a login event, which will map to your authenticate method and yield an authenticated state if successful, but not before storing the token.
There's plenty of variations with handling authentication, it all depends on your requirements really.
It is perfectly fine to add events which you might think they shouldn't be dispatched(in order to not violate the bloc pattern you shouldn't have business logic in your UI deciding when to add an event, so no stuff like
if (token != null) authBloc.add(LoggedIn(token)). It is the state resulting from that event what matters in the end. And the bloc is the one which decides what happens to a specific event, if it yields some new state or ignores the event all together.When your app starts you might wanna add an event which checks if there's a persisted token already and if so, just yield an authenticated state or else an unauthenticated one. The user should really login only if there's no token present. In that case, you could add a login event, which will map to your authenticate method and yield an authenticated state if successful, but not before storing the token.
There's plenty of variations with handling authentication, it all depends on your requirements really.
Ah I see, this is how I setup my main.dart:
Future<void> main() async {
final _tokenStorage = FlutterSecureStorage();
final UsersRepository _usersRepository = UsersRepository(_tokenStorage);
BlocSupervisor.delegate = SimpleBlocDelegate();
runApp(
BlocProvider<AuthenticationBloc>(
builder: (context){
return AuthenticationBloc(userRepository: _usersRepository)..add(AppStarted());
},
child: App(
usersRepository: _usersRepository,
tokenStorage: _tokenStorage,
),
));
}
class App extends StatelessWidget {
final UsersRepository usersRepository;
final FlutterSecureStorage tokenStorage;
App(
{@required this.usersRepository,
@required this.tokenStorage});
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark.copyWith(
statusBarColor: BACKGROUND_WHITE, //or set color with: Color(0xFF0000FF)
));
return BlocProvider(
BlocProvider<LoginBloc>(
builder: (BuildContext context) => LoginBloc(authenticationBloc: BlocProvider.of<AuthenticationBloc>(context),
usersRepository: usersRepository),
)
],
child: MaterialApp(
title: 'App',
debugShowCheckedModeBanner: false,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
final Posts arg = settings.arguments;
switch (settings.name) {
case '/':
return CupertinoPageRoute(
builder: (context) => LoginScreen(usersRepository: usersRepository,), settings: settings);
case 'home':
print("in home");
return CupertinoPageRoute(
builder: (context) => HomeScreen(tokenStorage: tokenStorage,), settings: settings);
default:
return CupertinoPageRoute(
builder: (context) => SplashPage(), settings: settings);
}
},
home: BlocBuilder<AuthenticationBloc,AuthenticationState>(
builder: (context,state){
if(state is AuthenticationAuthenticated){
return HomeScreen();
}
if(state is AuthenticationUnauthenticated){
return LoginScreen(usersRepository: usersRepository,tokenStorage: tokenStorage,);
}
if(state is AuthenticationLoading){
return SplashPage();
}
return SplashPage();
},
),
),
);
}
}
And this is my AuthenticationBloc(which is the same as the one from this repo):
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
final UsersRepository userRepository;
AuthenticationBloc({@required this.userRepository}) : assert(userRepository != null);
@override
AuthenticationState get initialState => AuthenticationUninitialized();
@override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
// TODO: Add Logic
if(event is AppStarted){
final bool hasToken = await userRepository.hasToken();
if(hasToken){
print("in HasToken");
yield AuthenticationAuthenticated();
}else{
yield AuthenticationUnauthenticated();
}
}
if(event is LoggedIn){
yield AuthenticationLoading();
// final bool hasToken = await userRepository.hasToken();
await userRepository.persistToken(event.token);
yield AuthenticationAuthenticated();
}
if(event is LoggedOut){
yield AuthenticationLoading();
await userRepository.deleteToken();
yield AuthenticationUnauthenticated();
}
}
}
Can you please let me know if my main.dart setup is correct?
Hi @henryla92 馃憢
Thanks for opening an issue.
Your setup seems mostly fine. The one main thing I want to call out is if you provide a home and / route in onGenerateRoute I believe it will always render home. I would recommend either refactoring to use home or to use / in onGenerateRoute but not both.
Closing for now but feel free to comment with additional questions and I'm happy to continue the conversation 馃憤
Hi @henryla92 馃憢
Thanks for opening an issue.Your setup seems mostly fine. The one main thing I want to call out is if you provide a
homeand/route inonGenerateRouteI believe it will always renderhome. I would recommend either refactoring to usehomeor to use/inonGenerateRoutebut not both.Closing for now but feel free to comment with additional questions and I'm happy to continue the conversation 馃憤
Ahh I see, thank you for the advise!
Most helpful comment
Ahh I see, thank you for the advise!