Okay, good day everyone, here comes a question:
I've started using flutter for mobile front, it's quite nice.
But I cannot understand an idea of handling a jwt token and redirecting if it is exists.
Consider small app, let me simplify:
final storage = FlutterSecureStorage(); # init storage here?
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: LoginPage(), # want home page actually, smth like if storage is empty, then call loginpage, if storage is not empty - try to make http request, if request is okay and data received - show homepage, if request is rejected - login to obtain new jwt
);
}
}
http request:
Future<User> userLogin(String login, String password) async {
Response response = await post('$_apiUrl' + '/' + '$_loginUrl',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'login': login,
'password': password,
}));
if (response.statusCode == 200) {
return User.fromJson(json.decode(response.body));
} else {
throw HttpException(
'Error making login request. Status code: ${response.statusCode}');
}
}
User class:
class User {
final String token;
final String uuid;
final String login;
final String email;
final String firstName;
final String lastName;
User({this.token, this.uuid, this.login, this.email, this.firstName, this.lastName});
factory User.fromJson(Map<String, dynamic> json) {
return User(
token: json['token'],
uuid: json['user']['uuid'],
login: json['user']['login'],
email: json['user']['email'],
firstName: json['user']['firstName'],
lastName: json['user']['lastName'],
);
}
}
So how this works now:
In main file as for now I call login page, with a button:
FlatButton(
child: Text(
'Sign in',
style: TextStyle(
color: Colors.black,
),
),
color: Colors.white,
onPressed: () {
Future<User> user = ApiRequestController().userLogin('alex', 'alex123');
Navigator.push(context, MaterialPageRoute(builder: (context) {
return UserProfile(userData: user,);
}));
},
),
Then I get redirected to user profile, nothing interesting:
class UserProfile extends StatefulWidget {
UserProfile({this.userData});
final userData;
#override
_UserProfileState createState() => _UserProfileState();
}
class _UserProfileState extends State<UserProfile> {
#override
void initState() {
// TODO: implement initState
super.initState();
futureUser = widget.userData;
print(futureUser);
}
Future<User> futureUser;
#override
Widget build(BuildContext context) {
common stuff like scaffold etc
And then I build a profile:
FutureBuilder<User>(
future: futureUser,
builder: (context, snapshot) {
if (snapshot.hasData) {
dynamic userProfile = snapshot.data;
return Column(
children: [
Container(
child: ListTile(
title: Text(
'login: ' + '${userProfile.login}',
style: TextStyle(
color: Colors.black,
),
),
),
),
Container(
child: ListTile(
title: Text(
'uuid: ' + '${userProfile.uuid}',
style: TextStyle(
color: Colors.black,
),
),
),
),
Container(
child: ListTile(
title: Text(
'First name: ' + '${userProfile.firstName}',
style: TextStyle(
color: Colors.black,
),
),
),
),
Container(
child: ListTile(
title: Text(
'Last name: ' + '${userProfile.lastName}',
style: TextStyle(
color: Colors.black,
),
),
),
),
Container(
child: ListTile(
title: Text(
'Auth token: ' + '${userProfile.token}',
style: TextStyle(
color: Colors.black,
),
),
),
),
],
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return CircularProgressIndicator();
},
),
Interface is simplified for easier understanding.
So what I have now, I have main dart file, where I call login screen (not home screen, because I cannot get an idea how to use token), where I call async http request to an api, then I am redirected to the user profile.
Idea is very simple:
App opened -> send http request with existing auth header, if answer is 200, then show homepage
If answer was not 200 (token expired/corrupted/doesn't exist, checked on the server side), then navigate user to login screen to obtain new auth token
How to store damn jwt token correctly -_-? I've tried multiple times with futures, plain requests, I cannot get an idea of parsing token between multiple screens.
What should I do? Where to initialize a token? Where to store it? How can I call it from other widget?
I've read about InheritedWidget, okay. But i.e., my HomePage already extends StatefulWidget, and there is no multiple inheritance. Extend it to User class or what?
So I am stuck with this simple goal, and cannot move any further. Just need to check if token exists -> show home, if not - show login. After we've obtained correct token - we can use whole app, making each request with an auth token received at first login screen.
Where to initialize token? Where should I inherit InheritedWidget? Model? ViewPage? Where?
Architecture failure :(
Help is much appreciated! Thanks in advance
Related
I have an app which gets data from the backend. I used a FutureBuilder and for the most classes when i try to pass the variable that im getting data, it's successfully being send to the other classes and i use the variable. But in another case i try to pass the variable to the other class and it's failing saying that null is being returned from the api.
return Scaffold(
body: FutureBuilder<Response>(
future: futureDataForStatus,
builder: (context, snapshot) {
if (snapshot.hasData) {
WorkingLocationStatus accountInfo = WorkingLocationStatus.fromJson(
json.decode(snapshot.data!.body),
);
sendAccountInfo(){
return IsUserWorking(accountStatus: accountInfo.status,);
}
-------------
class IsUserWorking extends StatelessWidget {
const IsUserWorking({
Key? key,
this.accountStatus,
}) : super(key: key);
final String? accountStatus; // according to debugger its returning null
#override
Widget build(BuildContext context) {
if (accountStatus == 'NOT_WORKING') {
return const WorkingScreen();
}
return const StopWorkingScreen();
}
}
------
// what the api returns
when the user is not working api returns this
{"status":"NOT_WORKING"}
but if the user is working it returns this
{"status":"WORKING","name":{"locationName":"Gjakova e Re","location":"Rruga Besmir Haxhi Koci, nr 577","startTime":"2022-02-28T21:16:38.510879+01:00","endTime":null,"duration":"PT10.256862755S"}}
// same variable passing to other classes
return Scaffold(
body: FutureBuilder<Response>(
future: futureDataForStatus,
builder: (context, snapshot) {
if (snapshot.hasData) {
WorkingLocationStatus accountInfo = WorkingLocationStatus.fromJson(
json.decode(snapshot.data!.body),
);
LocationNameData(accountInfo: accountInfo), //works fine
LocationData(accountInfo: accountInfo), // works fine
WorkingStartTime(accountInfo: accountInfo), // works fine
class LocationNameData extends StatelessWidget {
const LocationNameData({
Key? key,
required this.accountInfo,
}) : super(key: key);
final WorkingLocationStatus accountInfo;
#override
Widget build(BuildContext context) {
return Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 30),
child: RichText(
text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: [
const TextSpan(
text: 'Status: ',
style: TextStyle(
fontSize: 18,
color: Color(0xFF616161),
),
),
TextSpan(
text: accountInfo.status, // works just fine
style: const TextStyle(
fontSize: 18,
color: Colors.green,
fontWeight: FontWeight.bold),
),
],
),
),
),
),
],
);
// function to fetch the data
Future<Response> getLocationStatus(BuildContext context) async {
final navigator = GlobalKey<NavigatorState>();
SharedPreferences prefs = await SharedPreferences.getInstance();
String? authorization = prefs.getString('authorization');
var url = 'url';
locationStatus = await http.get(
Uri.parse(url),
headers: <String, String>{
'authorization': authorization ?? basicAuth.toString(),
"Content-Type": "application/json"
},
);
print('qqqqqqqqqqqq${locationStatus!.statusCode}');
return locationStatus!;
}
I want to authenticate user for uploading video in their channel ,required parameter is just authentication successful or not, currently i am working with these 3 plugins google_sign_in, googleapis extension_google_sign_in_as_googleapis_auth , by these i can only able to sign in into google account of user with the help of firebase.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.dart';
import 'package:googleapis/youtube/v3.dart';
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: <String>[
'email',
//'https://www.googleapis.com/auth/youtube.readonly',
],
);
void main() {
runApp(
MaterialApp(
title: 'Google Sign In',
home: SignInDemo(),
),
);
}
class SignInDemo extends StatefulWidget {
#override
State createState() => SignInDemoState();
}
class SignInDemoState extends State<SignInDemo> {
GoogleSignInAccount? _currentUser;
String? _contactText;
#override
void initState() {
super.initState();
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount? account) async {
setState(() {
_currentUser = account;
});
if (_currentUser != null) {
_handleGetChannels();
}
});
_googleSignIn.signInSilently();
}
Future<void> _handleGetChannels() async {
setState(() {
_contactText = 'Loading subscription info...';
});
var httpClient = (await _googleSignIn.authenticatedClient())!;
print("hello${httpClient.credentials.accessToken}");
var youTubeApi = YouTubeApi(httpClient);
var favorites = await youTubeApi.playlistItems.list(
['snippet'],
playlistId: 'LL', // Liked List
);
print("hey $favorites");
// final youtubeApi = YouTubeApi(await _googleSignIn.authenticatedClient());
// final response = await youtubeApi.subscriptions.list('snippet', mine: true);
setState(() {
if (favorites.items!.isNotEmpty) {
final channels =
favorites.items!.map((sub) => sub.snippet!.title).join(', ');
_contactText = 'I see you follow: ${channels}!';
} else {
_contactText = 'No channels to display.';
}
});
}
Future<void> _handleSignIn() async {
try {
await _googleSignIn.signIn();
} catch (error) {
print(error);
}
}
Future<void> _handleSignOut() => _googleSignIn.disconnect();
Widget _buildBody() {
if (_currentUser != null) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ListTile(
leading: GoogleUserCircleAvatar(
identity: _currentUser!,
),
title: Text(_currentUser!.displayName ?? ''),
subtitle: Text(_currentUser!.email),
),
const Text('Signed in successfully.'),
Text(_contactText ?? ''),
RaisedButton(
child: const Text('SIGN OUT'),
onPressed: _handleSignOut,
),
RaisedButton(
child: const Text('REFRESH'),
onPressed: _handleGetChannels,
),
],
);
} else {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
const Text('You are not currently signed in.'),
RaisedButton(
child: const Text('SIGN IN'),
onPressed: _handleSignIn,
),
],
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Google Sign In'),
),
body: ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: _buildBody(),
));
}
}
if i pass 'https://www.googleapis.com/auth/youtube.readonly' in scopes it shows loading only, and from email scopes i got the error Unhandled Exception: Access was denied (www-authenticate header was: Bearer realm="https://accounts.google.com/", error="insufficient_scope"
kindly help , i just want to authenticate user for their you tube channel.
The YouTube Data API supports the OAuth 2.0 protocol for authorizing access to private user data.
insufficient_scope
Means that the access token you sent was not authorized with the scope needed by the method you are calling.
For example video.insert requires authorization with one of the following scopes
You should check Google apis dart client library
here is my code :
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
final AuthService _authService = AuthService();
return StreamProvider<QuerySnapshot>.value(
initialData: ,
value: DataBase('ydhjvhjvjhbvyrduychkch').brews,
child: Scaffold(
backgroundColor: Colors.brown[50],
appBar: AppBar(
backgroundColor: Colors.brown[400],
elevation: 12,
title: Text('Brew Crew'),
actions: <Widget>[
TextButton.icon(
onPressed: () async {
await _authService.signOut();
},
icon: Icon(
Icons.person,
color: Colors.brown[800],
),
label: Text(
'log out',
style: TextStyle(color: Colors.brown[800]),
),
),
],
),
),
);
}
}
i have no idea what to ptovide here as i am a beginner in statemanagement ...please help me find a solution to thsi problem ...
here is also my database file:
class DataBase {
DataBase(this.uid);
late final String uid;
// collection refrence
final CollectionReference brewCollection =
FirebaseFirestore.instance.collection('brews');
Future updateUserData(String sugars, String name, int strength) async {
return await brewCollection.doc(uid).set({
'sugers': sugars,
'name': name,
'strength': strength,
});
}
//get brews stream
Stream<QuerySnapshot> get brews {
return brewCollection.snapshots();
}
}
Note : i have used provider 5
Note : just trying to work with firestore and provider package.
i appreciate your help in advance.
I'm kinda new to flutter, I've been building app using rest API as the backend, whenever I try to load data to display on home page screen from GET API I'm not able to fetch the value until I reload or refresh the widget on the app after pushandreplacement from login screen. Please help me!!! already a week, I still stuck at that bugs.
My code:
class HomePage extends StatefulWidget{
final String name;
HomePage(this.name);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
String token;
void iniState() {
super.initState();
GetRequest().getUserName(token);
}
Widget build(BuildContext context){
return Scaffold(
body: Container(
alignment: Alignment.topCenter,
child: SafeArea(
child: bodyPage(context),
),
),
);
}
}
Widget bodyPage(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 15),
_appBar(context),
],
),
),
),
),
);
}
_appBar(context) {
String myName = mData.fullname;
return Row(
children: <Widget>[
CircleAvatar(
backgroundImage: NetworkImage(
"https://jshopping.in/images/detailed/591/ibboll-Fashion-Mens-Optical-Glasses-Frames-Classic-Square-Wrap-Frame-Luxury-Brand-Men-Clear-Eyeglasses-Frame.jpg"),
),
SizedBox(width: 15),
Text("Hello, ",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
Text(
myName ?? 'KOOMPI',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.blueAccent)),
],
);
}
EDIT
getUserName code:
class GetRequest{
String messageAlert;
Future<void> getUserName(String _token) async {
var response = await http.get("${ApiService.url}/dashboard",
headers: <String, String>{
"accept": "application/json",
"authorization": "Bearer " + _token,
//"token": _token,
});
var responseBody = json.decode(response.body);
mData = ModelUserData.fromJson(responseBody);
}
}
Here is mData:
class ModelUserData{
String fullname;
ModelUserData({
this.fullname
});
ModelUserData.fromJson(Map<String,dynamic> parseJson){
fullname = parseJson['full_name'];
}
}
var mData = ModelUserData();
Firstly, there's a typo and no #override tag to your initState. It should be like that :
#override
void initState() {
super.initState();
Then, you don't show where mData comes from. The business logic code is generally essential to give. So that would be GetRequest().
Anyway, the simplest way would be to make getUserName like this :
Future<String> getUserName(String token) async {
// do your thing
return userName;
}
With that, you can await getUserName and rebuild when it comes back. So like that in the initState (like token, make a userName State variable) :
() async {
userName = await GetRequest().getUserName(token);
setState((){}); // this triggers the widget to rebuild
}();
Now, the widget rebuilds when the request resolves, and you can directly use the userName variable in your UI code.
However, as your app grows, this will quickly become tedious. To make it cleaner, learn state management.
I changed your method getUserName to return data instead of assigning mData inside it
class GetRequest{
String messageAlert;
Future<ModelUserData> getUserName(String _token) async {
var response = await http.get("${ApiService.url}/dashboard",
headers: <String, String>{
"accept": "application/json",
"authorization": "Bearer " + _token,
//"token": _token,
});
var responseBody = json.decode(response.body);
return ModelUserData.fromJson(responseBody);
}
}
Then on initState you should do
#override
void initState() {
GetRequest().getUserName(token).then((model) => {
setState(() => mData = model);
})
super.initState();
}
The important thing here is to cal setState when model arrives
Please help.. i'm trying to make update data page, but this error come out in this line..
Firestore.instance.collection('reg').add({'name':controllerName})
here is the code:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class EditList extends StatefulWidget {
#override
_EditListState createState() => _EditListState();
}
class _EditListState extends State<EditList> {
TextEditingController controllerName;
#override
void initState() {
controllerName = new TextEditingController();
super.initState();
}
var name;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Registration'),
backgroundColor: Colors.blue,
),
body: Container(
child: SingleChildScrollView(
padding: const EdgeInsets.all(30.0),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 20.0),
),
Text('GROUP'),
TextField(
controller: controllerName,
onChanged: (String str) {
setState(() {
name= str;
});
},
decoration: InputDecoration(
labelText: 'Name',
)),
//paste here
const SizedBox(height: 30),
RaisedButton(
onPressed: () {
if (controllerName.text.isNotEmpty) {
Firestore.instance.collection('reg').add({'name':controllerName})
.then((result){
Navigator.pop(context);
controllerName.clear();
}).catchError((err) =>print(err));
}
},
child: const Text('Submit', style: TextStyle(fontSize: 20)),
),
],
),
),
),
);
}
}
This line:
Firestore.instance.collection('reg').add({'name':controllerName})
should be replaced with:
Firestore.instance.collection('reg').add({'name':controllerName.text})
Also, you should probably give your TextField an initial value of an empty string so that it can't be null.
controllerName is not a String,
controllerName.text
use that
How do I add uuid inside the document? Tried adding .docs(uuid) before .add({ it's having an error.
CollectionReference users = FirebaseFirestore.instance.collection('users');
String? uuid = " ";
Future<void> addUser() {
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
uuid = user.uid;
print(uuid);
}
});
// Call the user's CollectionReference to add a new user
return users
.add({
'uuid': uuid, // John Doe
'first': firstNameController.text, // John Doe
'middle': middleNameController.text, // Stokes and Sons
'surname': surNameController.text // 42
})
.then((value) => print("User Added"))
.catchError((error) => print("Failed to add user: $error"));
}
Passing TextEditingController will definetly cause error because it just have instance of controller but you need text data to pass to function in upper most line. Controller attached to a textfield contains many of the property along with text inside the textfield.
You need to get the text from controller and pass it to the firebase function.
The line causing error:
Firestore.instance.collection('reg').add({'name':controllerName})
should be like this,
Firestore.instance.collection('reg').add({'name':controllerName.text})
and will work for sure.
Change this line
Firestore.instance.collection('reg').add({'name':controllerName})
To this line
Firestore.instance.collection('reg').add({'name':controllerName.text})
The difference is controllerName.text