Did You Use Any Login Button in Your Framework? How Do You Log? Updated FREE
Did You Use Any Login Button in Your Framework? How Do You Log?
Suggested laptop for programming | |
Lenovo IdeaPad S145 AMD Ryzen v 15.6" FHD Thin and Light Laptop (8GB/512GB SSD/Windows10/Function/Platinum Grey/1.85Kg) Buy on Amazon |
Introduction
In this article we will discuss how to use a Balance api backend to authenticate users from a Flutter app. We volition build a very basic nodejs REST api backend. If y'all already have a backend then you can use that also. And and so we will be building a basic Flutter app to connect to this backend and login to the app. It is causeless that the reader has a bones understanding of nodejs, rest api principles and flutter.
Overview of the app
The flowchart beneath shows the menses of this app.
And here is a short clip of the app in activeness.
Edifice the REST api backend
Nosotros will exist using nodejs to build this backend. Lets begin.
We create a new binder, proper noun it as flutter-backend
.
user@localhost:~/projects$ cd flutter-backend user@localhost:~/projects/flutter-backend$ npm init
Accept all prompts of npm
. We volition be using express
framework to build this. We volition install two dependencies, express
and trunk-parser
.
user@localhost:~/projects/flutter-backend$ npm i limited body-parser
Once these are installed, we write our api. Here is the code.
const limited = crave('express'); const bodyParser = require('trunk-parser'); var app = express(); app.use(bodyParser.urlencoded({ extended : truthful })); app.utilise(bodyParser.json()); app.use(function(req, res, next) { res.header("Content-Type", 'awarding/json'); res.header("Access-Control-Permit-Origin", "*"); next(); }); app.post('/user/login', function(req, res) { var username = req.body.username, password = req.torso.password; if (! username || ! password) { return res .status(400) .jsonp({ error : "Needs a json trunk with { username: <username>, password: <password>}" }); } if (username !== password) { return res .status(401) .jsonp({ mistake : "Authentication failied.", }); } render res .status(200) .jsonp({ 'userId' : '1908789', 'username' : username, 'proper noun' : 'Peter Clarke', 'lastLogin' : "23 March 2020 03:34 PM", 'email' : 'x7uytx@mundanecode.com' }); }); app.become('/user/:id', function(req, res) { var userId = req.params.id; if (! userId) { return res .condition(400) .jsonp({ error : "Needs a json body with { username: <username>, password: <password>}", }); } render res .status(200) .jsonp({ 'userId' : '1908789', 'username' : 'pclarketx', 'proper name' : 'Peter Clarke', 'lastLogin' : "23 March 2020 03:34 PM", 'e-mail' : 'x7uytx@mundanecode.com' }); }); // catch 404 and forward to error handler app.employ(function(req, res, next) { /*var err = new Error('Not Found'); err.condition = 404; next(err);*/ return res.condition(404).json({ success : false, bulletin : "not plant" }); }); if (app.become('env') === 'development') { app.employ(function(err, req, res, next) { panel.log(err); return res.status(err.status || 500).jsonp({ success : fake, "data" : [{ bulletin : err.message }] }); }); } var port = process.env.PORT || 9001; const server = app.listen(port, role() { console.log('Server upwardly at http://localhost:' + port); }); process.on('SIGTERM', () => { console.info('SIGTERM bespeak received.'); console.log('Closing http server.'); server.close(() => { panel.log('Http server airtight.'); }); }); process.on('SIGINT', () => { panel.info('SIGINT signal received.'); panel.log('Closing http server.'); server.close(() => { panel.log('Http server closed.'); }); });
Relieve it equally index.js
and to run this, do node index.js
at the terminal.
user@localhost:~/projects/flutter-backend$ node index.js Server up at http://localhost:9001
We are exposing two endpoints:
POST \user\login GET \user\:userId
\user\login
is a Mail
endpoint accepting a json
input of username and password of the post-obit format:
As this is just a demonstration api, we are simply checking if the username and countersign are same and if they are we are returning a mock user object in json
format. If the username and countersign are not aforementioned, we respond back with a Authentication failed
message.
\user\:userId
returns the user data of the userId passed in the parameter. For demonstration purpose, we merely return a mocked user json object.
Edifice the Palpitate app
This app volition consist of only ii screens, the login screen and the home screen. Merely before we dive into the screens lets hash out how nosotros can eat the api from flutter.
Interfacing with the REST api
Now that we have our Balance api, lets run across how we tin can connect to it from our Flutter app. We volition write our api interaction code in the api.dart
file inside the service
package. Nosotros volition he using the http
library to connect to the api. Our api has merely two methods - one for authenticating and another for getting user details. Lets write some code to call these.
Authentication
For authentication we will be doing POST \user\login
passing the username and password as JSON in the post-obit format.
{ username: password: }
Here is the palpitate method for it:
String _baseUrl = "http://192.168.1.8:9001/"; Futurity<ApiResponse> authenticateUser(String username, String password) async { ApiResponse _apiResponse = new ApiResponse(); effort { final response = expect http.post(' ${_baseUrl} user/login', body: { 'username' : username, 'password' : password, }); switch (response.statusCode) { instance 200 : _apiResponse.Data = User.fromJson(json.decode(response.body)); break; case 401 : _apiResponse.ApiError = ApiError.fromJson(json.decode(response.torso)); break; default : _apiResponse.ApiError = ApiError.fromJson(json.decode(response.body)); intermission; } } on SocketException { _apiResponse.ApiError = ApiError(error: "Server error. Delight retry"); } render _apiResponse; }
Find that this is an async
office. This is because fetching data over the net may accept an unkown amount of time and we do non want our UI to exist blocked during that. This office returns a Future
. Specifically, it returns a Future
of type ApiResponse
.
A future (lower case "f") is an instance of the Futurity (capitalized "F") grade. A future represents the result of an asynchronous operation, and tin can have two states: uncompleted or completed.
A Future<T> instance produces a value of blazon T.
If a futurity doesn't produce a usable value, then the futurity's type is Future<void>.
A futurity can be in i of 2 states: uncompleted or completed.
When you call a part that returns a future, the office queues upward work to be done and returns an uncompleted time to come.
When a future's operation finishes, the future completes with a value or with an error.
The http.post
method calls the REST api POST
endpoint /user/login
and passes the following json body.
{ username: countersign: }
The api will answer back with a response code of 200
when the auth is successful and a json will be sent back:
{ 'userId': '1908789', 'username': username, 'name': 'Peter Clarke', 'lastLogin': "23 March 2020 03:34 PM", 'email': 'x7uytx@mundanecode.com' }
ApiResponse
class encapsulates the response from the api. Here is how the class looks:
class ApiResponse { // _data volition agree any response converted into // its own object. For instance user. Object _data; // _apiError will concord the error object Object _apiError; Object go Data => _data; set Data(Object information) => _data = data; Object get ApiError => _apiError as Object; prepare ApiError(Object mistake) => _apiError = mistake; }
Here is how the ApiError looks like. Its a flake of overkill, nosotros could take done it by only using a cord field in the ApiResponse
.
class ApiError { String _error; ApiError({String error}) { this._error = error; } String go error => _error; set error(Cord mistake) => _error = error; ApiError.fromJson(Map<Cord, dynamic> json) { _error = json['error']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<Cord, dynamic>(); data['error'] = this._error; return data; } }
This is how nosotros are using this course in our authenticateUser
method:
. . . . case 200: _apiResponse.Data = User.fromJson(json.decode(response.body)); break; case 401: _apiResponse.ApiError = ApiError.fromJson(json.decode(response.body)); break; . . . .
When we get a 200
from our Residuum api, we instantiate the User
course and assign it to the ApiResponse.Data
. And if we get an error, we instantiate the ApiError
class and assign it to the ApiResponse.ApiError
. Nosotros take the mistake message that we receive from the api and assign it to ApiError.error
.
Permit'southward now take a expect at the User
class, this course is built based on the response that we are getting from our Rest api.
grade User { /* This class encapsulates the json response from the api { 'userId': '1908789', 'username': username, 'name': 'Peter Clarke', 'lastLogin': "23 March 2020 03:34 PM", 'email': 'x7uytx@mundanecode.com' } */ String _userId; String _username; String _name; Cord _lastLogin; String _email; // constructorUser( {String userId, Cord username, String proper noun, Cord lastLogin, Cord email}) { this._userId = userId; this._username = username; this._name = proper noun; this._lastLogin = lastLogin; this._email = email; } // Properties String get userId => _userId; set userId(String userId) => _userId = userId; String get username => _username; set up username(String username) => _username = username; String get name => _name; gear up proper noun(Cord name) => _name = proper noun; String go lastLogin => _lastLogin; gear up lastLogin(String lastLogin) => _lastLogin = lastLogin; String get email => _email; set electronic mail(Cord email) => _email = e-mail; // create the user object from json input User.fromJson(Map< Cord, dynamic > json) { _userId = json['userId']; _username = json['username']; _name = json['name']; _lastLogin = json['lastLogin']; _email = json['e-mail']; } // exports to json Map< Cord, dynamic > toJson() { final Map< String, dynamic > data = new Map< String, dynamic >(); data['userId'] = this._userId; data['username'] = this._username; data['proper name'] = this._name; data['lastLogin'] = this._lastLogin; information['email'] = this._email; return data; } }
Nosotros have likewise enclosed our code in a endeavour catch
cake to catch any exceptions similar uncreachable server or other errors and populate the ApiError
class.
Getting user details from the api
Our REST api also has a Go
method to become details of a specific user. The endpoint is \user\:userId
. Nosotros will write a flutter method to call this. This method will take the userId
as a parameter. Different the authentication method, instead of doing a POST
we will be doing a GET
hither.
Future<ApiResponse> getUserDetails(String userId) async { ApiResponse _apiResponse = new ApiResponse(); endeavor { final response = look http.go(' ${_baseUrl} user/ $userId'); switch (response.statusCode) { case 200 : _apiResponse.Information = User.fromJson(json.decode(response.trunk)); suspension; case 401 : print((_apiResponse.ApiError as ApiError).mistake); _apiResponse.ApiError = ApiError.fromJson(json.decode(response.body)); break; default : impress((_apiResponse.ApiError as ApiError).error); _apiResponse.ApiError = ApiError.fromJson(json.decode(response.body)); break; } } on SocketException { _apiResponse.ApiError = ApiError(fault: "Server error. Delight retry"); } return _apiResponse; }
Building the UI
Login screen
Our login screen will have ii fields to enter the username and password along with a button. Here is the snippet of the lawmaking that builds the UI.
. . . . . . . . . . . . Widget build(BuildContext context) { return Scaffold( key: _scaffoldKey, appBar: AppBar( title: Text('Login'), ), torso: SafeArea( top: false, bottom: imitation, child: Class( autovalidate: true, key: _formKey, kid: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 16.0), kid: Column( crossAxisAlignment: CrossAxisAlignment.starting time, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Column( crossAxisAlignment: CrossAxisAlignment.kickoff, children: <Widget>[ TextFormField( central: Key("_username"), ornamentation: InputDecoration(labelText: "Username"), keyboardType: TextInputType.text, onSaved: (String value) { _username = value; }, validator: (value) { if (value.isEmpty) { return 'Username is required'; } return cipher; }, ), TextFormField( decoration: InputDecoration(labelText: "Password"), obscureText: true, onSaved: (String value) { _password = value; }, validator: (value) { if (value.isEmpty) { render 'Password is required'; } return zilch; }, ), const SizedBox(elevation: 10.0), ButtonBar( children: <Widget>[ RaisedButton.icon( onPressed: _handleSubmitted, icon: Icon(Icons.arrow_forward), label: Text('Sign in')), ], ), ], ), ]), ), ), )); } . . . . . . . . . . . .
The lawmaking is not and so complicated. Uses two TextFormField
southward and a RaisedButton
in a ButtonBar
inside a Grade
. On pressing the button we will validate the inputs and also phone call the REST api for authentication. This logic will exist written in the _handleSubmitted
method.
Here is the _handleSubmitted
method:
void _handleSubmitted() async { last FormState form = _formKey.currentState; if (!grade.validate()) { showInSnackBar('Please fix the errors in ruby before submitting.'); } else { form.save(); _apiResponse = await authenticateUser(_username, _password); if ((_apiResponse.ApiError as ApiError) == null) { _saveAndRedirectToHome(); } else { showInSnackBar((_apiResponse.ApiError equally ApiError).fault); } } }
This method validates the class, in case of whatever validation errors we testify them in the snackbar. If the user has entered both the username and password, we go along to call the authenticateUser
method. Notice that the authenticateUser
method is called with an await
. Nosotros do this because the authenticateUser
method is an async
method.
To use authenticateUser
method we take to import the following packages:
import 'parcel:flutterloginrestapi/models/user.dart'; import 'package:flutterloginrestapi/models/api_error.dart'; import 'bundle:flutterloginrestapi/models/api_response.sprint'; import 'package:flutterloginrestapi/service/api.sprint';
The authenticateUser
returns an object of blazon ApiResponse
. We check if the ApiResponse.ApiError
has any errors, if it has we show the error else we redirect the user to the home screen. The redirection happens in the _saveAndRedirectToHome()
method.
void _saveAndRedirectToHome() async { SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setString("userId", (_apiResponse.Data every bit User).userId); Navigator.pushNamedAndRemoveUntil( context, '/habitation', ModalRoute.withName('/dwelling'), arguments: (_apiResponse.Data every bit User)); }
One time the user is successfully authenticated we load upward the home screen and we exercise not want that the user should be able to come back to the login screen. To attain this we apply the Navigator.pushNamedAndRemoveUntil
method. The documentation says:
Push the route with the given name onto the navigator that most tightly encloses the given context, and and then remove all the previous routes until the predicate returns truthful.
We are saving the userId
of the user in the SharedPreferences
. We are also passing the ApiResponse
object as an argument.
Home Screen
We show the user'southward proper noun, last login engagement time and the email address. We do this by extracting the User
object from the navigator'south argument.
The logout button unproblematic removes the userId
that we had saved in the login screen from the SharedPreferences.
import 'packet:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'packet:flutterloginrestapi/models/user.sprint'; import 'packet:shared_preferences/shared_preferences.dart'; class MyHomePage extends StatefulWidget { MyHomePage({Primal key, this.title}) : super(key: primal); terminal String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { void _handleLogout() async { SharedPreferences prefs = look SharedPreferences.getInstance(); prefs.remove('userId'); Navigator.pushNamedAndRemoveUntil( context, '/login', ModalRoute.withName('/login')); } @override Widget build(BuildContext context) { final User args = ModalRoute.of(context).settings.arguments; return Scaffold( appBar: AppBar( title: Text("Home"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.centre, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Text("Welcome back " + args.proper name + "!"), Text("Last login was on " + args.lastLogin), Text("Your Email is " + args.email), RaisedButton( onPressed: _handleLogout, child: Text("Logout"), ) ], ), )); } }
Wrapping upwards
We have at present seen both the login screen and the abode screen. We will now expect at the rest of the code of the app and how these screens are chosen. The file structure of the app is:
| +---+models | +--api_error.sprint | +--api_response.dart | +--user.dart | +---screens | +--dwelling.sprint | +--login.dart | +---service | +--api.dart | +--landing.dart +--main.dart
The main.dart
file is the starting point of this app. We have the principal()
method in this file.
import 'packet:flutter/material.dart'; import 'package:flutterloginrestapi/screens/home.sprint'; import 'package:flutterloginrestapi/screens/login.dart'; import 'landing.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { render MaterialApp( title: 'Login Demo', routes: { '/' : (context) => Landing(), '/login' : (context) => Login(), '/home' : (context) => MyHomePage(title: 'Login Demo'), }, theme: ThemeData( primarySwatch: Colors.deepOrange, ), ); } }
We are creating a MaterialApp
widget, giving it a championship
, giving it a theme
and also setting up the routes
. The routes
we are setting upwards are:
-
'/'
- This route is the default route when the app is opened and volition take the execution to theLanding()
file. -
/login
- This route is for our login screen. -
/home
- This is for our abode screen.
The Landing
class in landing.dart
file checks if the user has already logged in and if finds this to be true, loads the domicile screen else loads the login screen.
We will check if the user is logged in past checking the SharedPreferences
in the initState()
method. Nosotros will write a separate method _loadUserInfo()
, we volition call this from initState()
.
If we detect userId
in the SharedPreferences
then we phone call the getUserDetails()
method to fetch the details from our residuum api. We then bear witness the home screen.
In case userId
is not institute, we evidence the login screen.
. . . . . . . . . . @override void initState() { super.initState(); _loadUserInfo(); } _loadUserInfo() async { SharedPreferences prefs = expect SharedPreferences.getInstance(); _userId = (prefs.getString('userId') ?? ""); if (_userId == "") { Navigator.pushNamedAndRemoveUntil( context, '/login', ModalRoute.withName('/login')); } else { ApiResponse _apiResponse = await getUserDetails(_userId); if ((_apiResponse.ApiError equally ApiError) == null) { Navigator.pushNamedAndRemoveUntil( context, '/abode', ModalRoute.withName('/home'), arguments: (_apiResponse.Data every bit User)); } else { Navigator.pushNamedAndRemoveUntil( context, '/login', ModalRoute.withName('/login')); } } } . . . . . . . . . .
Below is the complete code for the landing.dart
file. The only UI element is the CircularProgressIndicator()
. It will be displayed utill initState()
execution is completed. It indicates to the users that a background procedure is running and the app is not stuck.
import 'package:palpitate/fabric.sprint'; import 'packet:flutterloginrestapi/models/api_response.sprint'; import 'package:flutterloginrestapi/service/api.sprint'; import 'package:shared_preferences/shared_preferences.dart'; import 'models/api_error.dart'; import 'models/user.dart'; form Landing extends StatefulWidget { @override _LandingState createState() => _LandingState(); } class _LandingState extends State<Landing> { String _userId = ""; @override void initState() { super.initState(); _loadUserInfo(); } _loadUserInfo() async { SharedPreferences prefs = expect SharedPreferences.getInstance(); _userId = (prefs.getString('userId') ?? ""); if (_userId == "") { Navigator.pushNamedAndRemoveUntil( context, '/login', ModalRoute.withName('/login')); } else { ApiResponse _apiResponse = wait getUserDetails(_userId); if ((_apiResponse.ApiError equally ApiError) == null) { Navigator.pushNamedAndRemoveUntil( context, '/home', ModalRoute.withName('/habitation'), arguments: (_apiResponse.Data as User)); } else { Navigator.pushNamedAndRemoveUntil( context, '/login', ModalRoute.withName('/login')); } } } @override Widget build(BuildContext context) { return Scaffold(torso: Middle(child: CircularProgressIndicator())); } }
Hope this is of assistance. Please post us in case of whatsoever questions.
Did You Use Any Login Button in Your Framework? How Do You Log?
DOWNLOAD HERE
Source: https://mundanecode.com/posts/flutter-restapi-login/
Posted by: venturaandithers.blogspot.com
0 Response to "Did You Use Any Login Button in Your Framework? How Do You Log? Updated FREE"
Post a Comment