How do I return to the user stream in flutter - flutter

I'm having an issue return a Stream to a StreamBuilder widget in a flutter. I'm trying to access a custom class that is stored token.
class User {
String token;
User({this.token});
}
===============================
class AuthService {
String url = 'https://reqres.in/api/login';
String token = '';
// {
// "email": "eve.holt#reqres.in",
// "password": "cityslicka"
// }
Map data;
Future signIn(String email, String password) async {
final response =
await post(url, body: {'email': email, 'password': password});
data = jsonDecode(response.body);
print(data['token']);
token = data['token'];
_userFromDatabaseUser(data);
return data;
}
//create user obj based on the database user
User _userFromDatabaseUser(Map user) {
return user != null ? User(token: user['token']) : null;
}
//user stream for provider
Stream<User> get user {
return .................. ;
}

You could use a stream controller:
class AuthService {
final String url = 'https://reqres.in/api/login';
final controller = StreamController<User>();
Future<User> signIn(String email, String password) async {
final response = await post(url, body: {'email': email, 'password': password});
final data = jsonDecode(response.body);
final user = _userFromDatabaseUser(data);
controller.add(user);
return user;
}
//create user obj based on the database user
User _userFromDatabaseUser(Map user) {
return user != null ? User(token: user['token']) : null;
}
//user stream for provider
Stream<User> get user {
return controller.stream;
}
Please note that this approach is a simplistic example that has some flaws, you should read up on it in the documentation.
If you use this for the purpose you describe, you may want to look into the bloc pattern and it's implementation as flutter-bloc. It might seem easier to do the user in this way by hand, but once you reach the point where you have multiple of those streams, you may want a more structured approach.

You can use
Stream<User> get user async*{
yield .................. ;
}
you can use yield keyword when you want to return stream object.
2nd way you can use a stream controller. You can add value in controller and
listen wherever you want to listen in your app there is no need to return stream

Related

Pass the user id to an http parameter

Good morning, I have a login that returns me the UserID from the server, I store it in an instance of Shared Preferences, and I want to use it as a parameter so that in the main screen, it shows five recent records, also brought from the database.
I attach the code of the login, I emphasize that I was trying to pass some arguments by means of the routes, which are the UserID and the Role of the user, to show him a screen in special.
Future<void> login(email, password) async{
try{
var url = 'serverurl';
var response = await http.post(Uri.parse(url),
body:
{
'Email' : email,
'Password' : password
}).timeout(const Duration(seconds: 30));
var datos = jsonDecode(response.body);
print(datos);
if(response.body != '0'){
guardarDatos(datos['UserID'], datos['Role']);
if('Role' == 'admin'){
Navigator.pushNamed(context, '/AdminPage', arguments: {'UserID':UserId, 'Role': Role});
} else {
Navigator.pushNamed(context, '/UserPage', arguments: {'UserID': UserId, 'Role': Role});
}
} else{
//Cuadro de diálogo que indica que los datos son incorrectos.
showDialog(
context: context,
builder: (BuildContext context) {
return const AlertLogin();
});
print('Usuario Incorrecto');
}
} on TimeoutException catch(e){
print('Tiempo de proceso excedido.');
} on Error {
print('http error.');
}
}
The following, is the code for the main screen, where I plan to pass the user id as a parameter in the URL of the http.get, to get the user records, for example number 1.
//HTTP Request
Future<List<Record>> fetchRecord() async {
//final response = await http.get(Uri.parse('https://e5ac-45-65-15257.ngrok.io/get/fiverecords/1')); Este es estático.
final response = await http
.get(Uri.parse('https://e5ac-45-65-152-57.ngrok.io/get/fiverecords/'));
if (response.statusCode == 200) {
final parsed = json.decode(response.body).cast<Map<dynamic, dynamic>>();
return parsed.map<Record>((json) => Record.fromMap(json)).toList();
} else {
throw Exception('Failed to load records.');
}
}
So in your code for main screen, I am assuming you are asking how to retrieve your arguments passed through named routes. Here's what you need to do:
Define a variable:
final user = ModalRoute.of(context)!.settings.arguments;
You can use this user variable to access the UserID and Email just like this:
print('User Email: ${user.UserID}');
print('User Email: ${user.Role}');
Its like calling a map's value using its key.
So this is how your final code might look like:
Future<List<Record>> fetchRecord() async {
final user = ModalRoute.of(context)!.settings.arguments; // You can use this variable directly in your links
//final response = await http.get(Uri.parse('https://e5ac-45-65-15257.ngrok.io/get/fiverecords/1')); Este es estático.
final response = await http
.get(Uri.parse('https://e5ac-45-65-152-57.ngrok.io/get/fiverecords/'));
if (response.statusCode == 200) {
final parsed = json.decode(response.body).cast<Map<dynamic, dynamic>>();
return parsed.map<Record>((json) => Record.fromMap(json)).toList();
} else {
throw Exception('Failed to load records.');
}
}
Hope that solves your problem. Feel free to clear up any confusions.

show display name after signUp

I have a flutter firebase createUserWithEmailAndPassword function with the displayName update.
display name prints normally at the moment of fb user creation.
After signup MainPage loads with the users email and displayName. But displayName returns null value error. If I delete displayName from the MainPage - all works fine.
If I reload app, it works fine.
When I login, it works fine.
It can't pass the displayName at the moment of signup only.
Where I am wrong?
class AuthServiceProvider extends ChangeNotifier {
final auth.FirebaseAuth _firebaseAuth = auth.FirebaseAuth.instance;
final googleSingIn = GoogleSignIn();
UserModel? _userFromFirebase(auth.User? user) {
if (user == null) {
return null;
}
return UserModel(
user.displayName,
user.uid,
user.email,
);
}
Stream<UserModel?>? get user {
return _firebaseAuth.authStateChanges().map(_userFromFirebase);
}
Future<UserModel?> createUserWithEmailAndPassword(
String name,
String email,
String password,
) async {
try {
final userCred = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
auth.User? firebaseUser = _firebaseAuth.currentUser;
if (firebaseUser != null) {
await firebaseUser.updateDisplayName(name);
await firebaseUser.reload();
firebaseUser = _firebaseAuth.currentUser;
}
print('FIREBASE USER IS $firebaseUser');
return _userFromFirebase(firebaseUser);
} catch (e) {
print(e.toString());
return null;
}
}
}
If your class were to extend either StatelessWidget or StatefulWidget, then all you'd have to do is to pass the data (displayName) between the screens.
This is not an answer but a suggestion:
You should try changing the ChangeNotifier to a StatefulWidget
and pass the data between screens...
You could also setup an
Authentication class that will hold all these Future methods so that
these calls can be reusable in your code. With this method, all you have to do is to call the specific function and give its required parameters.
As usually the solution is very simple if you think a little bit.
As all this is through the firebase auth, at the main page loading I just grab the firebase user with its display name that is saved in FB both for GoogleSignIn and createUserWithEmailAndPassword (required at registration)
import 'package:firebase_auth/firebase_auth.dart' as auth;
final auth.FirebaseAuth _firebaseAuth = auth.FirebaseAuth.instance;
final String firebaseUser =
_firebaseAuth.currentUser!.displayName ?? 'Unknown user';

Flutter api login using riverpod

I'm trying to use riverpod for login with a laravel backend. Right now I'm just returning true or false from the repository. I've set a form that accepts email and password. The isLoading variable is just to show a circle indicator. I've run the code and it works but not sure if I'm using riverpod correctly. Is there a better way to do it ?
auth_provider.dart
class Auth{
final bool isLogin;
Auth(this.isLogin);
}
class AuthNotifier extends StateNotifier<Auth>{
AuthNotifier() : super(Auth(false));
void isLogin(bool data){
state = new Auth(data);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier());
auth_repository.dart
class AuthRepository{
static String url = "http://10.0.2.2:8000/api/";
final Dio _dio = Dio();
Future<bool> login(data) async {
try {
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
}
login_screen.dart
void login() async{
if(formKey.currentState.validate()){
setState((){this.isLoading = true;});
var data = {
'email':this.email,
'password':this.password,
'device_name':'mobile_phone'
};
var result = await AuthRepository().login(data);
if(result){
context.read(authProvider).isLogin(true);
setState((){this.isLoading = false;});
}else
setState((){this.isLoading = false;});
}
}
Since I'm not coming from mobile background and just recently use flutter+riverpod in my recent project, I cannot say this is the best practice. But there are some points I'd like to note:
Use interface such IAuthRepository for repository. Riverpod can act as a dependency injection.
final authRepository = Provider<IAuthRepository>((ref) => AuthRepository());
Build data to send in repository. You should separate presentation, business logic, and explicit implementation for external resource if possible.
Future<bool> login(String email, String password) async {
try {
var data = {
'email': email,
'password': password,
'device_name':'mobile_phone'
};
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
Do not call repository directly from presentation/screen. You can use the provider for your logic, which call the repository
class AuthNotifier extends StateNotifier<Auth>{
final ProviderReference ref;
IAuthRepository _authRepository;
AuthNotifier(this.ref) : super(Auth(false)) {
_authRepository = ref.watch(authRepository);
}
Future<void> login(String email, String password) async {
final loginResult = await_authRepository.login(email, password);
state = Auth(loginResult);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier(ref));
On screen, you can call provider's login method
login() {
context.read(authProvider).login(this.email, this.password);
}
Use Consumer or ConsumerWidget to watch the state and decide what to build.
It also helps that instead of Auth with isLogin for the state, you can create some other state. At the very least, I usually create an abstract BaseAuthState, which derives to AuthInitialState, AuthLoadingState, AuthLoginState, AuthErrorState, etc.
class AuthNotifier extends StateNotifier<BaseAuthState>{
...
AuthNotifier(this.ref) : super(AuthInitialState()) { ... }
...
}
Consumer(builder: (context, watch, child) {
final state = watch(authProvider.state);
if (state is AuthLoginState) ...
else if (state is AuthLoadingState) ...
...
})
Instead of using a bool, I like to use enums or class for auth state
enum AuthState { initialize, authenticated, unauthenticated }
and for login state
enum LoginStatus { initialize, loading, success, failed }

How do I print a value from an instance of 'User' in a flutter?

class User {
String token;
User({this.token});
}
class AuthService {
final String url = 'https://reqres.in/api/login';
final controller = StreamController<User>();
Future<User> signIn(String email, String password) async {
final response =
await post(url, body: {'email': email, 'password': password});
final data = jsonDecode(response.body);
final user = _userFromDatabaseUser(data);
// print(user.token);
controller.add(user);
return user;
}
//create user obj based on the database user
User _userFromDatabaseUser(Map user) {
return user != null ? User(token: user['token']) : null;
}
//user stream for provider
Stream<User> get user {
return controller.stream;
}
}
//in Sign in page
onPressed: () async {
if (_formKey.currentState.validate()) {
dynamic result = await _auth.signIn(email, password);
print(result); // Instance of 'User'
}
}
I am new to flutter and want to make an app that only authenticated users. I'm trying to read user token data from a stream. then check that token is not null if I got token then goto home page otherwise it will show error how do I print or store token value?
You can do is when you get the user after the sign In:
User result = await _auth.signIn(email, password);
Then to see the data you can do is
print(result.token);
which will give you the token, and then you can use the shared prefrences to store your token and access it.
Check out the docs for the it: https://pub.dev/packages/shared_preferences
You can override Object.toString method.
you can add this method in your User class to print the token instead of Instance of 'User'.
#override
String toString() {
// TODO: change the below return to your desired string
return "token: $token";
}
You can print using
print(userModel.toString());

Singleton class for http requests

Can you give some advice how to design class for api requests in flutter? I'm ios developer and I used singleton classes with alamofire. If you provide some code it would be great!
class Client: ApiBase {
static let shared = Client()
private override init() {}
func login(phoneNumber: String, password: String, completion: #escaping(_ error: String?) -> Void) {
let params: [String : String] = [
"userId" : phoneNumber,
"password" : password,
]
baseRequest(route: ApiRouter.login(), params: params) { (response) in
if let json = response.json {
Session.current.sessionId = json["sessionId"].string
}
completion(response.error)
}
}
}
How login method called:
#IBAction func singin(_ sender: TransitionButton) {
Client.shared.login(phoneNumber: "12312", password: "123") { (error) in
guard error == nil else {
// show error
return
}
// navigate to home page
}
}
In flutter you don't have to deal with the relative nastiness of IBActions, protocols as callback, or retain cycles, and you have async and await to help out.
There's a few ways you could do the API calls - one would be to simply put them right in the same code as your UI. That has downsides, but it is certainly readable.
class WhateverMyComponentIsState extends State<WateverMyComponentIs> {
Future<String> _doLogin({#required String phoneNumber, #required String password}) async {
final response = await http.post(LOGIN_URL, body: {'userId': phoneNumber, 'password': password})
if (response.statusCode == 200) {
final jsonResponse = jsonDecode(body);
return jsonResponse['sessionId'];
} else {
... error handling
}
}
String phoneNumber;
String password;
#override
Widget build(BuildContext context) {
return ...(
child: FlatButton(
onPressed: () async {
final sessionId = await _doLogin(phoneNumber: phoneNumber, password: password);
... do whatever - setState(() => loggedIn = true), or Navigator.push ...
}
),
)
}
}
If you wanted, you could extract all of the api calls into a different class - they could be static methods, but that makes it so that it's harder to write good tests if you ever decide to do that.
My personal recommendation is to use a form of more or less 'dependency injection', by utilizing InheritedWidget to provide an implementation of a class that actually performs the login (and could hold the sessionId). Rather than implementing all of that yourself, though, you could use the ScopedModel plugin which I personally like very much as it greatly reduces the amount of boilerplate needed.
If you use ScopedModel properly (which I'll leave as an exercise for you - I'm pretty sure there's other questions about that), you can use it or a class it provides to do the http request, and then have the sessionId stored in the ScopedModel.
The beauty of that is that if you were to ever get to writing tests (or have to deal with two slightly servers, etc), you could then replace the ScopedModel with a different ScopedModel which implemented the same interface but doesn't actually perform http requests or performs them differently.
In flutter you should create a class something like this
class User {
String name;
String pass;
User({
this.name,
this.pass,
});
User.fromJson(Map<String, dynamic> json) {
name = json['name'];
pass= json['pass'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['pass'] = this.pass;
return data;
}
}
Now create the list of type User class something like this
final List<User> user;
Now call the URL (API) for user Auth
Future<void> validateUsr() async {
var client = new http.Client();
try {
var response = await client.get(
'https://xxxxxxxx/wp-json/jwt-auth/v1/token?username=xxxxx2&password=xxxxxx');
if (response.statusCode == 200) {
var data = json.decode(response.body);
var list = data as List;
setState(() {
user=list.map<User>((i) => User.fromJson(i)).toList();
});
} else {
print('Somthing went wrong');
}
} catch (e) {
print(e);
} finally {
client.close();
}
}
Hope this helped you