I'm trying to keep users logged in with nestjs backend, when I use Postman the process works very smoothly, but with Flutter I don't know how to do it. I don't think that I actually understand how sessions work for mobiles, I tried looking for some proper explaining but I couldn't find anything so far.
Nestjs Code
#Controller('users')
#Serialize(UserDto)
export class UsersController {
constructor(
private usersService: UsersService,
private authService: AuthService,
) {}
#Get('/whoami')
#UseGuards(AuthGuard)
whoAmI(#currentUser() user: User) {
return user;
}
#Get()
getUsers(#Query('email') email: string) {
return this.usersService.find(email);
}
#Post('/signup')
async sendUser(#Body() body: UserDto, #Session() session: any) {
console.log(body);
const user = await this.authService.signup(body.email, body.password);
session.userId = user.id;
console.log(session.userId);
return user;
}
#Post('/signin')
async signin(#Body() body: UserDto, #Session() session: any) {
const user = await this.authService.signin(body.email, body.password);
session.userId = user.id;
console.log(session.userId);
return user;
}
#Post('/signout')
async signout(#Session() session: any) {
console.log(session.userId);
if (!session.userId) {
throw new NotFoundException('user not found');
}
session.userId = null;
}
}
Flutter Code
Future<void> signin(
String username, String password, BuildContext context) async {
try {
var url = 'https://example-app.herokuapp.com/users/signin';
var dio = Dio();
var response =
await dio.post(url, data: {'email': username, 'password': password}, options: Options(headers: {'Accept': 'application/json'}));
print(response.headers);
// response;
Navigator.of(context).pushNamed(CategoryDetailScreen.routeName);
} catch (err) {
print(err);
throw err;
}
}
Future<void> signout() async {
try {
var url = 'https://example-app.herokuapp.com/users/signout';
var dio = Dio();
var response = await dio.post(url,
options: Options(headers: {
'cookie':
'key'
}
)
);
print(response.headers);
response;
// return response;
} catch (err) {
print(err);
throw err;
}
}
Thanks to #RichardHeap 's comment I managed to solve my issue.
Check out session management with cookies:
Flutter http Maintain PHP session
I used FlutterSecureStorage package to store the incoming cookie and then decide which screen to show as a home screen using FutureBuilder as seen below:
I used these functions to write cookies and delete them from the device:
Future<void> signin(
String username, String password, BuildContext context) async {
try {
// String cookie = '';
var url = 'https://daleel-app.herokuapp.com/users/signin';
var storage = FlutterSecureStorage();
var dio = Dio();
var response =
await dio.post(url, data: {'email': username, 'password': password});
List<String>? cookies = response.headers['set-cookie'];
for (int i = 0; i <= cookies!.length - 1; i++) {
var cokIndex = cookies[i].indexOf(';');
var subCookies = cookies[i].substring(0, cokIndex + 1);
cookie += subCookies + ' ';
}
var subbedCookie = cookie.substring(0, cookie.length - 2);
print(subbedCookie);
storage.write(key: 'cookie', value: subbedCookie);
loggedIn = true;
// print(response.headers['set-cookie']);
// [express:sess=eyJ1c2VySWQiOjU1fQ==; path=/; httponly, express:sess.sig=Zy_Lc7kXM1BqZKIZRRt7ygpCTrM; path=/; httponly]
Navigator.of(context).pushNamed(CategoryDetailScreen.routeName);
} catch (err) {
print(err);
throw err;
}
}
Future<void> signout(BuildContext context) async {
try {
var url = 'https://daleel-app.herokuapp.com/users/signout';
var dio = Dio();
var fStorage = FlutterSecureStorage();
var header = await fStorage.read(key: 'cookie');
await dio.post(url, options: Options(headers: {'cookie': header}));
fStorage.delete(key: 'cookie');
Navigator.of(context).pushReplacementNamed(LoginScreen.routeName);
print('you reached here');
} catch (err) {
print(err);
throw err;
}
}
And here I used the FutureBuilder to decide which screen to show:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var cookie = FlutterSecureStorage().read(key: 'cookie');
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => Places(),
),
ChangeNotifierProvider(
create: (ctx) => Offers(),
),
],
child: FutureBuilder(
future: cookie,
builder: (BuildContext context, AsyncSnapshot snapshot) => MaterialApp(
debugShowCheckedModeBanner: true,
title: 'Daleel',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: snapshot.hasData ? HomeScreen() : LoginScreen(),
routes: {
LoginScreen.routeName: (ctx) => LoginScreen(),
TestScreen3.routeName: (ctx) => TestScreen3(),
TestScreen2.routeName: (ctx) => TestScreen2(),
HomeScreen.routeName: (ctx) => HomeScreen(),
DetailsScreen.routeName: (ctx) => DetailsScreen(),
ExploreScreen.routeName: (ctx) => ExploreScreen(),
CategoryDetailScreen.routeName: (ctx) => CategoryDetailScreen(),
SearchDetailsScreen.routeName: (ctx) => SearchDetailsScreen(),
}),
),
);
}
}
Related
I have a simple controller like this
class UserController with ChangeNotifier {
UserData user = UserData();
UserData get userdata => user;
void setUser(UserData user) {
user = user;
print(user.sId);
notifyListeners();
}
login(data) async {
var response = await ApiService().login(data);
final databody = json.decode(response);
if (databody['success']) {
UserData authUser = UserData.fromJson(databody['data']);
setUser(authUser);
notifyListeners();
return true;
} else {
return false;
}
}
}
I am trying to just print it like this on both widget and in initstate function but values are showing null. I can see in set function value is not null.
print('id ${context.watch<UserController>().user.sId.toString()}');
print(
'id2 ${Provider.of<UserController>(context, listen: false).user.sId.toString()}');
I already have added
ChangeNotifierProvider(create: (_) => UserController()),
],
in main.dart in MultiProvider
Also on Tap of login button I am doing this
showLoader(context);
UserController auth = Provider.of<UserController>(
context,
listen: false);
var data = {
"userEmail":
emailController.text.trim().toLowerCase(),
"userPassword": passwordController.text.trim(),
};
auth.login(data).then((v) {
if (v) {
hideLoader(context);
context.go('/homeroot');
} else {
hideLoader(context);
Fluttertoast.showToast(
backgroundColor: green,
textColor: Colors.white,
msg:
'Please enter correct email and password');
}
});
Try to include this while naming is same,
void setUser(UserData user) {
this.user = user;
print(user.sId);
notifyListeners();
}
Follow this structure
class UserController with ChangeNotifier {
UserData user = UserData();
UserData get userdata => user;
void setUser(UserData user) {
this.user = user;
print(user.sId);
notifyListeners();
}
Future<bool> login(String data) async {
await Future.delayed(Duration(seconds: 1));
UserData authUser = UserData(sId: data);
setUser(authUser);
notifyListeners();
return true;
}
}
class HPTest extends StatelessWidget {
const HPTest({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<UserController>(
builder: (context, value, child) {
return Text(value.user.sId);
},
),
floatingActionButton: FloatingActionButton(onPressed: () async {
final result = await Provider.of<UserController>(context, listen: false)
.login("new ID");
print("login $result");
;
}),
);
}
}
I know there is many answer on flutter provider but I cannot find my answer after hours of debugging please help me
I have a widget which is SignUpIdols
Widget signupidolsUI(BuildContext context) {
final auth = Provider.of<AuthProvider>(context);
This is the issues final auth = Provider.of(context);
My AuthProvider Class
class AuthProvider with ChangeNotifier {
Status _loggedInStatus = Status.NotLoggedIn;
Status _registeredInStatus = Status.NotRegistered;
Status get loggedInStatus => _loggedInStatus;
Status get registeredInStatus => _registeredInStatus;
Future<Map<String, dynamic>> register(
String name, String email, String password) async {
_registeredInStatus = Status.Registering;
CollectionReference users = FirebaseFirestore.instance.collection('users');
var result;
users
.add({'name': name, 'email': email, 'password': password})
.then((value) => () {
result = {
'status': true,
'message': 'Successful',
'user': 'Register Successful'
};
_registeredInStatus = Status.Registered;
})
.catchError((error) => () {
result = {'status': true, 'message': 'Error', 'user': '$error'};
});
return result;
}
}
Error I face
Thanks.
I fix it after I inject multiprovider on my main app its works
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<AuthProvider>(create: (BuildContext context) {
return AuthProvider();
}),
],
child: MaterialApp(
I am trying to get state from one ChangeNotifier Auth.dart into another ChangeNotifier ProductsProvider.dart. But ChangeNotifierProxyProvider is providing incorrect state data for Auth.
main.dart
void main() => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<Auth>(create: (ctx) => Auth()),
ChangeNotifierProxyProvider<Auth, ProductsProvider>(
create: (context) => ProductsProvider(
Provider.of<Auth>(context, listen: false),
productList: [],
),
update: (ctx, auth, preProducts) {
print("ChangeNotifierProxyProvider Token ${auth.isAuth}");
print("ChangeNotifierProxyProvider Token ${auth.token}");
print("ChangeNotifierProxyProvider Test ${auth.test}");
return ProductsProvider(
auth,
productList:
preProducts == null ? [] : preProducts.getProductList,
);
},
),
ChangeNotifierProvider(create: (ctx) => Cart()),
ChangeNotifierProvider(create: (ctx) => Order()),
ChangeNotifierProvider(create: (ctx) => Auth()),
],
child: MyApp(),
),
);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<Auth>(
builder: (ctx, auth, _) {
print("Builder Token ${auth.isAuth}");
print("Builder Token ${auth.token}");
print("Builder Test ${auth.test}");
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.orange,
fontFamily: "Lato",
),
routes: {
"/": (ctx) => auth.isAuth ? ProductOverviewScreen() : AuthScreen(),
ProductDetailScreen.PRODUCT_DETAIL_ROUTE: (ctx) =>
ProductDetailScreen(),
CartScreen.CART_SCREEN_ROUTE: (ctx) => CartScreen(),
OrderScreen.ORDER_SCREEN_ROUTE: (ctx) => OrderScreen(),
UserProductScreen.USER_PRODUCT_ROUTE: (ctx) => UserProductScreen(),
EditProductScreen.EDIT_PRODUCT_ROUTE: (ctx) => EditProductScreen(),
},
);
},
// ),
);
}
}
Auth.dart
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
import 'package:shop_app/models/HttpException.dart';
class Auth with ChangeNotifier {
String _token;
String _userId;
DateTime _expiryDate;
String test;
bool get isAuth => token != null;
String get token {
if (_token == null
// && _expiryDate != null &&
// _expiryDate.isAfter(DateTime.now())
) {
return null;
}
return _token;
}
Future<void> _authenticate(String email, String password, String url) async {
final uri = Uri.parse(url);
final response = await http.post(
uri,
body: jsonEncode({
"email": email,
"password": password,
"returnSecureToken": true,
}),
);
Map<String, dynamic> responseData = jsonDecode(response.body);
if (responseData["error"] != null) {
throw HttpException(responseData["error"]["message"]);
}
_token = responseData["idToken"];
_userId = responseData["localId"];
test = "_token";
// _expiryDate = DateTime.now()
// .add(Duration(seconds: int.parse(responseData["expiresIn"])));
notifyListeners();
}
Future<void> signUp(String email, String password) async {
const url =
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[KEY]";
return await _authenticate(email, password, url);
}
Future<void> login(String email, String password) async {
const url =
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=[KEY]";
return await _authenticate(email, password, url);
}
}
ProductsProvider.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shop_app/models/HttpException.dart';
import 'package:shop_app/models/Product.dart';
import 'package:shop_app/providers/Auth.dart';
class ProductsProvider with ChangeNotifier {
List<Product> _productList = [];
Auth _auth;
ProductsProvider(this._auth, {List<Product> productList = const []})
: _productList = productList;
Future<void> fetchAndSetProducts() async {
var uri = Uri.parse(
"https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products.json?auth=${_auth.token}");
try {
final response = await http.get(uri);
if (response.body != "null") {
final Map<String, dynamic> decodedJSON = jsonDecode(response.body);
final List<Product> loadedProductList = [];
decodedJSON.forEach((prodId, prodData) {
loadedProductList.add(Product(
id: prodId,
title: prodData["title"],
description: prodData["description"],
price: prodData["price"],
imageUrl: prodData["imageUrl"],
isFavourite: prodData["isFavourite"],
));
});
_productList = loadedProductList;
} else {
_productList = [];
}
notifyListeners();
} catch (error) {
throw error;
}
}
List<Product> get getProductList {
return _productList;
}
Product findById(String id) =>
_productList.firstWhere((element) => element.id == id);
Future<void> addProduct(Product product) async {
var uri = Uri.parse(
"https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products.json");
try {
final responseProduct =
await http.post(uri, body: jsonEncode(product.toJSon()));
final finalProduct = Product(
id: jsonDecode(responseProduct.body)["name"],
title: product.title,
description: product.description,
price: product.price,
imageUrl: product.imageUrl);
_productList.add(finalProduct);
notifyListeners();
return Future.value();
} catch (error) {
throw error;
}
}
Future<void> updateProduct(String id, Product product) async {
var productIndex = _productList.indexWhere((element) => element.id == id);
if (productIndex >= 0) {
var uri = Uri.parse(
"https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products/$id.json");
try {
await http.patch(uri,
body: jsonEncode({
"title": product.title,
"description": product.description,
"price": product.price,
"imageUrl": product.imageUrl,
"isFavourite": product.isFavourite,
}));
_productList[productIndex] = product;
notifyListeners();
} catch (error) {
throw error;
}
}
}
Future<void> deleteProduct(String id) async {
final uri = Uri.parse(
"https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products/$id.json");
final existingProductIndex =
_productList.indexWhere((element) => element.id == id);
var existingProduct = _productList[existingProductIndex];
_productList.removeAt(existingProductIndex);
final response = await http.delete(uri);
if (response.statusCode >= 400) {
_productList.insert(existingProductIndex, existingProduct);
notifyListeners();
throw HttpException("Could not delete product");
}
existingProduct = null;
notifyListeners();
}
}
After I click the login button the login method from the Auth.dart is trigged. After fetching the token the from firebase the screen is updated to ProductOverviewScreen. But the ProductsProvider.dart is not able to fetch the items because the ChangeNotifierProxyProvider update is returning an incorrect state for Auth.
Output:
Builder Token true
Builder Token eyJhbGciOiJSUzI1NiIsImtpZCI6IjNkOWNmYWE4OGVmMDViNDI0YmU2MjA1ZjQ2YjE4OGQ3MzI1N2JjNDIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmx1dHRlci1zaG9wLWFwcC0zMDM1YSIsImF1ZCI6ImZsdXR0ZXItc2hvcC1hcHAtMzAzNWEiLCJhdXRoX3RpbWUiOjE2MjE3NzUxMDYsInVzZXJfaWQiOiJ5Y0dVVWhwYTFvY2EwMThlYUx4VGZkQnRNbmsyIiwic3ViIjoieWNHVVVocGExb2NhMDE4ZWFMeFRmZEJ0TW5rMiIsImlhdCI6MTYyMTc3NTEwNiwiZXhwIjoxNjIxNzc4NzA2LCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsidGVzdEB0ZXN0LmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.t1xosbzllt79NV6FQ79mTQ2J3VCR5fILKMxE5-ObOxMI2DtD_kMg2AP9NXm_f1IsLF9AT5xeXeVU36goVDLQuKSWmbejOANDn7hsF6VzZMyBV1P9qehXpWgSmCscjXT8FRlKViZzxOZwCWSHS1M94n92YYhwaZltiDcQ87hhv7ZdKyLrlDsUPfr7IjNBeSVDmzws_9uBoZwYKRYCW1veWPc7HPWtdP8QT7K_vkCEvGLHfxbmVHOgUkDMzLqhgusZl34GCPVKr_PSpQ9SgC7Mg95QeZyzYzPmhasGUptq5pQsjEoqTxgYHnmEuMRRjksZT5lbfsQQFOJMsXBTIC7RDQ
Builder Test _token
ChangeNotifierProxyProvider Token false
ChangeNotifierProxyProvider Token null
ChangeNotifierProxyProvider Test null
Error: Expected a value of type 'int', but got one of type 'String'
at Object.throw_ [as throw] (http://localhost:1344/dart_sdk.js:5333:11)
at ProductProvider.ProductsProvider.new.fetchAndSetProducts (http://localhost:1344/packages/shop_app/providers/ProductProvider.dart.lib.js:84:21)
at fetchAndSetProducts.next (<anonymous>)
at http://localhost:1344/dart_sdk.js:39031:33
at _RootZone.runUnary (http://localhost:1344/dart_sdk.js:38888:58)
at _FutureListener.thenAwait.handleValue (http://localhost:1344/dart_sdk.js:33874:29)
at handleValueCallback (http://localhost:1344/dart_sdk.js:34434:49)
at Function._propagateToListeners (http://localhost:1344/dart_sdk.js:34472:17)
at _Future.new.[_completeWithValue] (http://localhost:1344/dart_sdk.js:34314:23)
at async._AsyncCallbackEntry.new.callback (http://localhost:1344/dart_sdk.js:34337:35)
at Object._microtaskLoop (http://localhost:1344/dart_sdk.js:39175:13)
at _startMicrotaskLoop (http://localhost:1344/dart_sdk.js:39181:13)
at http://localhost:1344/dart_sdk.js:34688:9
I am learning dart and flutter. I am not sure what I am missing. Can someone could help me fix this issue.
I was able to find what was causing this issue.
It appears I was reinitializing the Auth ChangeNotifierProvider. If you see the last line in the MultiProvider constructor providers argument.
If anyone else comes across such an issue ensure that you have specified the providers in the correct order.
Product Id defined int/Integer, but the parser from firebase is String. You need change in Product Id to String or use int.parse(prodId)
I am using Riverpod's FutureProvider with family. The FutureProvider keeps on running again and again. It shows the loading dialog only. Also the hot reload stops working. FutureProvider is working fine without family. Please help in finding what's wrong.
final ephemerisProvider =
Provider((ref) => ApiService("https://localhost"));
final ephemerisFutureProvider = FutureProvider.family
.autoDispose<EpheModel, Map<String, dynamic>>((ref, data) async {
var response = await ref.read(ephemerisProvider).getData(data);
print(EpheModel.fromJSON(response));
return EpheModel.fromJSON(response);
});
class Kundlis extends ConsumerWidget {
static const routeName = "/kundlis";
#override
Widget build(BuildContext context, ScopedReader watch) {
final AsyncValue<EpheModel> kundlis = watch(ephemerisFutureProvider({}));
return Scaffold(
appBar: AppBar(
title: Text("Kundlis"),
),
drawer: AppDrawer(),
body: kundlis.when(
data: (kundli) => Center(child: Text(kundli.toString())),
loading: () => ProgressDialog(message: "Fetching Details..."),
error: (message, st) =>
CustomSnackBar.buildErrorSnackbar(context, '$message')));
}
}
class ApiService {
final String url;
ApiService(this.url);
Future<Map<String, dynamic>> getData(Map<String, dynamic> data) async {
try {
http.Response response = await http.post(url + "/ephe",
headers: <String, String>{'Content-Type': 'application/json'},
body: jsonEncode(data));
if (response.statusCode == 200) {
return data;
} else {
throw Exception("Error Fetching Details");
}
} on SocketException {
throw Exception("No Internet Connection");
} on HttpException {
throw Exception("Error Fetching Details");
}
}
}
{} != {}. Because of .family, you are creating a completely new provider every time you call watch(ephemerisFutureProvider({})). To select a previously-built provider via family, you must pass an identical value. And {} is never identical to {}, guaranteed. :)
I am new in BLOC and I am trying to read respond from api.. but whenever I call stream builder... my widget always stops in wait... here is my code
here is api provider file
class Provider {
final _url = '...';
Future<List<LoginRespon>> login(a, b) async {
List<LoginRespon> datalogin = [];
try {
bool trustSelfSigned = true;
HttpClient httpClient = new HttpClient()
..badCertificateCallback =
((X509Certificate cert, String host, int port) =>
trustSelfSigned);
IOClient http = new IOClient(httpClient);
final response = await http.post(_url,
headers: {
HttpHeaders.contentTypeHeader: 'application/json',
},
body: json.encode({
"aa": a,
"bb": b,
}));
Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson["status"] == "200") {
datalogin.add(LoginRespon(
status: responseJson['status'],
data: Data(
name: responseJson['data']['name'],
position: responseJson['data']['pos'])));
return datalogin;
} else {
print("ppp");
}
} on Exception {
rethrow;
}
return datalogin;
}
}
and here is for stream builder
isClick
? StreamBuilder(
stream: bloc.login(),
builder: (context, snapshot) {
if (snapshot.hasData) {
print(snapshot.data);
return Text("success");
} else if (snapshot.hasError) {
return Text(
snapshot.error.toString());
}
return Text("wait..");
},
)
: FlatButton(
child: Text("Login"),
onPressed: () {
setState(() {
isClick = true;
});
},
),
is there a way so that I can call print(snapshot.data) inside if (snapshot.hasData)
You need to pass argument which required in method otherwise it will not successfully responce (200) and it will throw error.