Random Posts




banner



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.

Flowchart

And here is a short clip of the app in activeness.

Gif of the project

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 TextFormFieldsouthward 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 the Landing() 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

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel